mirror of
https://github.com/we-promise/sure.git
synced 2026-07-27 04:02:15 +00:00
Revert "Refactor application workflows and update test coverage"
This reverts commit 565e049f89.
This commit is contained in:
@@ -25,12 +25,6 @@ OPENAI_ACCESS_TOKEN=
|
||||
OPENAI_MODEL=
|
||||
OPENAI_URI_BASE=
|
||||
|
||||
# Alternative: use a ChatGPT-backed Codex OAuth token for subscription inference.
|
||||
# OAuth takes precedence over OPENAI_ACCESS_TOKEN and uses the Codex Responses endpoint.
|
||||
# OPENAI_ACCOUNT_ID is optional when it can be read from the token's JWT claims.
|
||||
# OPENAI_OAUTH_TOKEN=
|
||||
# OPENAI_ACCOUNT_ID=
|
||||
|
||||
# Optional: LLM token budget (applies to chat, auto-categorize, merchant detection, PDF processing).
|
||||
# Lower these for small-context local models (Ollama, LM Studio, LocalAI).
|
||||
# For larger local models, raise the context window to match the model you actually run.
|
||||
|
||||
@@ -32,12 +32,6 @@ OPENAI_ACCESS_TOKEN =
|
||||
OPENAI_URI_BASE =
|
||||
OPENAI_MODEL =
|
||||
|
||||
# Alternative: ChatGPT-backed Codex OAuth subscription inference.
|
||||
# OAuth takes precedence over OPENAI_ACCESS_TOKEN and uses the Codex Responses endpoint.
|
||||
# OPENAI_ACCOUNT_ID is optional when it can be read from the token's JWT claims.
|
||||
# OPENAI_OAUTH_TOKEN =
|
||||
# OPENAI_ACCOUNT_ID =
|
||||
|
||||
# LLM token budget. Applies to ALL outbound LLM calls: chat history,
|
||||
# auto-categorize, merchant detection, provider enhancer, PDF processing.
|
||||
# Defaults to Ollama's historical 2048-token baseline so small local models
|
||||
|
||||
@@ -152,19 +152,6 @@ class Settings::HostingsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
if hosting_params.key?(:openai_oauth_token)
|
||||
token_param = hosting_params[:openai_oauth_token].to_s.strip
|
||||
unless token_param.blank? || token_param == "********"
|
||||
Setting.openai_oauth_token = token_param
|
||||
inferred_account_id = Provider::Openai.oauth_account_id(token_param)
|
||||
Setting.openai_oauth_account_id = inferred_account_id if inferred_account_id.present? && Setting.openai_oauth_account_id.blank?
|
||||
end
|
||||
end
|
||||
|
||||
if hosting_params.key?(:openai_oauth_account_id)
|
||||
Setting.openai_oauth_account_id = hosting_params[:openai_oauth_account_id].to_s.strip.presence
|
||||
end
|
||||
|
||||
# Validate OpenAI configuration before updating
|
||||
if hosting_params.key?(:openai_uri_base) || hosting_params.key?(:openai_model)
|
||||
Setting.validate_openai_config!(
|
||||
@@ -293,7 +280,7 @@ class Settings::HostingsController < ApplicationController
|
||||
private
|
||||
def hosting_params
|
||||
return ActionController::Parameters.new unless params.key?(:setting)
|
||||
params.require(:setting).permit(:onboarding_state, :require_email_confirmation, :invite_only_default_family_id, :brand_fetch_client_id, :brand_fetch_high_res_logos, :twelve_data_api_key, :tiingo_api_key, :eodhd_api_key, :alpha_vantage_api_key, :tinkoff_invest_api_key, :openai_access_token, :openai_oauth_token, :openai_oauth_account_id, :openai_uri_base, :openai_model, :openai_json_mode, :anthropic_access_token, :anthropic_base_url, :anthropic_model, :llm_provider, :llm_context_window, :llm_max_response_tokens, :llm_max_items_per_call, :exchange_rate_provider, :securities_provider, :syncs_include_pending, :auto_sync_enabled, :auto_sync_time, :external_assistant_url, :external_assistant_token, :external_assistant_agent_id, securities_providers: [])
|
||||
params.require(:setting).permit(:onboarding_state, :require_email_confirmation, :invite_only_default_family_id, :brand_fetch_client_id, :brand_fetch_high_res_logos, :twelve_data_api_key, :tiingo_api_key, :eodhd_api_key, :alpha_vantage_api_key, :tinkoff_invest_api_key, :openai_access_token, :openai_uri_base, :openai_model, :openai_json_mode, :anthropic_access_token, :anthropic_base_url, :anthropic_model, :llm_provider, :llm_context_window, :llm_max_response_tokens, :llm_max_items_per_call, :exchange_rate_provider, :securities_provider, :syncs_include_pending, :auto_sync_enabled, :auto_sync_time, :external_assistant_url, :external_assistant_token, :external_assistant_agent_id, securities_providers: [])
|
||||
end
|
||||
|
||||
def update_assistant_type
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
class Provider::Github
|
||||
CACHE_TTL = 2.hours
|
||||
|
||||
attr_reader :name, :owner, :branch, :client
|
||||
|
||||
def initialize
|
||||
@@ -19,7 +17,7 @@ class Provider::Github
|
||||
|
||||
def fetch_latest_release_notes
|
||||
begin
|
||||
Rails.cache.fetch(release_notes_cache_key, expires_in: CACHE_TTL) do
|
||||
Rails.cache.fetch("latest_github_release_notes", expires_in: 2.hours) do
|
||||
release = client.releases(repo).first
|
||||
if release
|
||||
{
|
||||
@@ -44,8 +42,4 @@ class Provider::Github
|
||||
def repo
|
||||
"#{owner}/#{name}"
|
||||
end
|
||||
|
||||
def release_notes_cache_key
|
||||
"latest_github_release_notes/#{repo}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
require "base64"
|
||||
|
||||
class Provider::Openai < Provider
|
||||
include LlmConcept
|
||||
|
||||
@@ -7,65 +5,32 @@ class Provider::Openai < Provider
|
||||
Error = Class.new(Provider::Error)
|
||||
|
||||
DEFAULT_MODEL = "gpt-4.1".freeze
|
||||
CODEX_DEFAULT_MODEL = "gpt-5.6".freeze
|
||||
CODEX_URI_BASE = "https://chatgpt.com/backend-api/codex".freeze
|
||||
CODEX_ORIGINATOR = "codex_cli_rs".freeze
|
||||
SUPPORTED_MODELS = %w[gpt-4 gpt-5 o1 o3].freeze
|
||||
VISION_CAPABLE_MODEL_PREFIXES = %w[gpt-4o gpt-4-turbo gpt-4.1 gpt-5 o1 o3].freeze
|
||||
|
||||
# Returns the effective model that would be used by the provider.
|
||||
# Priority: explicit ENV > Setting > DEFAULT_MODEL.
|
||||
def self.effective_model
|
||||
configured_model = ENV.fetch("OPENAI_MODEL") { Setting.openai_model }.presence
|
||||
configured_model || (oauth_configured? ? CODEX_DEFAULT_MODEL : DEFAULT_MODEL)
|
||||
ENV.fetch("OPENAI_MODEL") { Setting.openai_model }.presence || DEFAULT_MODEL
|
||||
end
|
||||
|
||||
def self.configured?
|
||||
oauth_configured? || ENV["OPENAI_ACCESS_TOKEN"].present? || Setting.openai_access_token.present?
|
||||
ENV["OPENAI_ACCESS_TOKEN"].present? || Setting.openai_access_token.present?
|
||||
end
|
||||
|
||||
def self.oauth_configured?
|
||||
ENV["OPENAI_OAUTH_TOKEN"].present? || Setting.openai_oauth_token.present?
|
||||
end
|
||||
|
||||
def self.oauth_account_id(access_token)
|
||||
payload = access_token.to_s.split(".", 3).second
|
||||
return if payload.blank?
|
||||
|
||||
payload = payload.ljust((payload.length + 3) / 4 * 4, "=")
|
||||
claims = JSON.parse(Base64.urlsafe_decode64(payload))
|
||||
claims["chatgpt_account_id"].presence ||
|
||||
claims.dig("https://api.openai.com/auth", "chatgpt_account_id").presence
|
||||
rescue ArgumentError, JSON::ParserError
|
||||
nil
|
||||
end
|
||||
|
||||
def initialize(access_token, uri_base: nil, model: nil, oauth: false, account_id: nil)
|
||||
def initialize(access_token, uri_base: nil, model: nil)
|
||||
client_options = { access_token: access_token }
|
||||
@oauth = oauth
|
||||
llm_uri_base = oauth? ? CODEX_URI_BASE : uri_base.presence
|
||||
llm_uri_base = uri_base.presence
|
||||
llm_model = model.presence
|
||||
client_options[:uri_base] = llm_uri_base if llm_uri_base.present?
|
||||
client_options[:request_timeout] = ENV.fetch("OPENAI_REQUEST_TIMEOUT", 60).to_i
|
||||
|
||||
if oauth?
|
||||
oauth_account_id = account_id.presence || self.class.oauth_account_id(access_token)
|
||||
if oauth_account_id.blank?
|
||||
raise Error, "ChatGPT account ID is required when using a Codex OAuth token"
|
||||
end
|
||||
|
||||
client_options[:extra_headers] = {
|
||||
"ChatGPT-Account-ID" => oauth_account_id,
|
||||
"originator" => CODEX_ORIGINATOR
|
||||
}
|
||||
end
|
||||
|
||||
@client = ::OpenAI::Client.new(**client_options)
|
||||
@uri_base = llm_uri_base
|
||||
if custom_provider? && llm_model.blank?
|
||||
raise Error, "Model is required when using a custom OpenAI‑compatible provider"
|
||||
end
|
||||
@default_model = llm_model.presence || (oauth? ? CODEX_DEFAULT_MODEL : self.class.effective_model)
|
||||
@default_model = llm_model.presence || self.class.effective_model
|
||||
end
|
||||
|
||||
def supports_model?(model)
|
||||
@@ -88,8 +53,6 @@ class Provider::Openai < Provider
|
||||
end
|
||||
|
||||
def provider_name
|
||||
return "OpenAI (Codex subscription)" if oauth?
|
||||
|
||||
custom_provider? ? "Custom OpenAI-compatible (#{@uri_base})" : "OpenAI"
|
||||
end
|
||||
|
||||
@@ -102,11 +65,7 @@ class Provider::Openai < Provider
|
||||
end
|
||||
|
||||
def custom_provider?
|
||||
@uri_base.present? && !oauth?
|
||||
end
|
||||
|
||||
def oauth?
|
||||
@oauth
|
||||
@uri_base.present?
|
||||
end
|
||||
|
||||
# Token-budget knobs. Precedence: ENV > Setting > default. Defaults match
|
||||
|
||||
@@ -77,19 +77,13 @@ class Provider::Registry
|
||||
end
|
||||
|
||||
def openai
|
||||
oauth_token = ENV["OPENAI_OAUTH_TOKEN"].presence || Setting.openai_oauth_token
|
||||
access_token = oauth_token || ENV["OPENAI_ACCESS_TOKEN"].presence || Setting.openai_access_token
|
||||
access_token = ENV["OPENAI_ACCESS_TOKEN"].presence || Setting.openai_access_token
|
||||
|
||||
return nil unless access_token.present?
|
||||
|
||||
uri_base = ENV["OPENAI_URI_BASE"].presence || Setting.openai_uri_base
|
||||
model = ENV["OPENAI_MODEL"].presence || Setting.openai_model
|
||||
|
||||
if oauth_token.present?
|
||||
account_id = ENV["OPENAI_ACCOUNT_ID"].presence || Setting.openai_oauth_account_id
|
||||
return Provider::Openai.new(access_token, model: model, oauth: true, account_id: account_id)
|
||||
end
|
||||
|
||||
if uri_base.present? && model.blank?
|
||||
Rails.logger.error("Custom OpenAI provider configured without a model; please set OPENAI_MODEL or Setting.openai_model")
|
||||
return nil
|
||||
|
||||
@@ -7,8 +7,6 @@ class Setting < RailsSettings::Base
|
||||
# Third-party API keys
|
||||
field :twelve_data_api_key, type: :string, default: ENV["TWELVE_DATA_API_KEY"]
|
||||
field :openai_access_token, type: :string, default: ENV["OPENAI_ACCESS_TOKEN"]
|
||||
field :openai_oauth_token, type: :string, default: ENV["OPENAI_OAUTH_TOKEN"]
|
||||
field :openai_oauth_account_id, type: :string, default: ENV["OPENAI_ACCOUNT_ID"]
|
||||
field :openai_uri_base, type: :string, default: ENV["OPENAI_URI_BASE"]
|
||||
field :openai_model, type: :string, default: ENV["OPENAI_MODEL"]
|
||||
field :openai_json_mode, type: :string, default: ENV["LLM_JSON_MODE"]
|
||||
@@ -78,7 +76,6 @@ class Setting < RailsSettings::Base
|
||||
alpha_vantage_api_key
|
||||
tinkoff_invest_api_key
|
||||
openai_access_token
|
||||
openai_oauth_token
|
||||
anthropic_access_token
|
||||
external_assistant_token
|
||||
].freeze
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h2 class="font-medium mb-1"><%= t(".title") %></h2>
|
||||
<% if ENV["OPENAI_ACCESS_TOKEN"].present? || ENV["OPENAI_OAUTH_TOKEN"].present? %>
|
||||
<% if ENV["OPENAI_ACCESS_TOKEN"].present? %>
|
||||
<p class="text-sm text-secondary"><%= t(".env_configured_message") %></p>
|
||||
<% else %>
|
||||
<p class="text-secondary text-sm mb-4"><%= t(".description") %></p>
|
||||
@@ -27,34 +27,6 @@
|
||||
disabled: ENV["OPENAI_ACCESS_TOKEN"].present?,
|
||||
data: { "auto-submit-form-target": "auto" } %>
|
||||
|
||||
<div class="pt-4 border-t border-secondary">
|
||||
<h3 class="font-medium mb-1"><%= t(".oauth_heading") %></h3>
|
||||
<p class="text-xs text-secondary mb-3"><%= t(".oauth_description") %></p>
|
||||
|
||||
<%= form.password_field :openai_oauth_token,
|
||||
label: t(".oauth_token_label"),
|
||||
placeholder: t(".oauth_token_placeholder"),
|
||||
value: (Setting.openai_oauth_token.present? ? "********" : nil),
|
||||
autocomplete: "off",
|
||||
autocapitalize: "none",
|
||||
spellcheck: "false",
|
||||
inputmode: "text",
|
||||
disabled: ENV["OPENAI_OAUTH_TOKEN"].present?,
|
||||
data: { "auto-submit-form-target": "auto" } %>
|
||||
|
||||
<%= form.text_field :openai_oauth_account_id,
|
||||
label: t(".oauth_account_id_label"),
|
||||
placeholder: t(".oauth_account_id_placeholder"),
|
||||
value: Setting.openai_oauth_account_id,
|
||||
autocomplete: "off",
|
||||
autocapitalize: "none",
|
||||
spellcheck: "false",
|
||||
inputmode: "text",
|
||||
disabled: ENV["OPENAI_ACCOUNT_ID"].present?,
|
||||
data: { "auto-submit-form-target": "auto" } %>
|
||||
<p class="text-xs text-secondary mt-1"><%= t(".oauth_account_id_help") %></p>
|
||||
</div>
|
||||
|
||||
<%= form.text_field :openai_uri_base,
|
||||
label: t(".uri_base_label"),
|
||||
placeholder: t(".uri_base_placeholder"),
|
||||
|
||||
@@ -118,17 +118,10 @@ en:
|
||||
model_placeholder: "claude-sonnet-4-6 (default)"
|
||||
model_help: Used for chat and PDF processing. Batch operations (categorize, merchant detection) default to Haiku for cost.
|
||||
openai_settings:
|
||||
description: Enter an API key, a Codex OAuth token, or configure a custom OpenAI-compatible provider
|
||||
description: Enter the access token and optionally configure a custom OpenAI-compatible provider
|
||||
env_configured_message: Successfully configured through environment variables.
|
||||
access_token_label: API Key
|
||||
access_token_placeholder: Enter your OpenAI-compatible API key here
|
||||
oauth_heading: Codex Subscription (OAuth)
|
||||
oauth_description: Use a ChatGPT-backed Codex OAuth access token instead of API billing. OAuth takes precedence when both credential types are configured. Tokens expire and must be replaced after you sign in again.
|
||||
oauth_token_label: OAuth Access Token
|
||||
oauth_token_placeholder: Enter your Codex OAuth access token here
|
||||
oauth_account_id_label: ChatGPT Account ID (Optional)
|
||||
oauth_account_id_placeholder: Account ID from your Codex login
|
||||
oauth_account_id_help: Usually read from the token automatically. Enter it when your token does not contain the ChatGPT account claim.
|
||||
access_token_label: Access Token
|
||||
access_token_placeholder: Enter your access token here
|
||||
uri_base_label: API Base URL (Optional)
|
||||
uri_base_placeholder: "https://api.openai.com/v1 (default)"
|
||||
model_label: Model (Optional)
|
||||
|
||||
1777
db/schema.rb
generated
1777
db/schema.rb
generated
File diff suppressed because it is too large
Load Diff
@@ -48,22 +48,6 @@ The easiest way to get started with AI features in Sure is to use OpenAI:
|
||||
|
||||
That's it! Sure will use OpenAI's with a default model (currently `gpt-4.1`) for all AI operations.
|
||||
|
||||
## Alternative: Codex Subscription OAuth
|
||||
|
||||
Sure can send Responses API inference through a ChatGPT-backed Codex subscription instead of billing an OpenAI Platform API key:
|
||||
|
||||
```bash
|
||||
OPENAI_OAUTH_TOKEN=your-codex-oauth-access-token
|
||||
# Optional when the account ID cannot be read from the token:
|
||||
OPENAI_ACCOUNT_ID=your-chatgpt-account-id
|
||||
# Optional; defaults to the current Codex default:
|
||||
OPENAI_MODEL=gpt-5.6
|
||||
```
|
||||
|
||||
When `OPENAI_OAUTH_TOKEN` is present, it takes precedence over `OPENAI_ACCESS_TOKEN`. Sure sends requests to the Codex Responses endpoint and adds the ChatGPT account header required by subscription authentication. This credential is only used for LLM inference; it is not treated as a general OpenAI Platform API key for hosted vector stores or other Platform APIs.
|
||||
|
||||
OAuth access tokens expire. Sign in again with an official Codex client and replace the configured token when that happens. Store the token like a password and never commit it to an environment file.
|
||||
|
||||
## Local vs. Cloud Inference
|
||||
|
||||
### Cloud Inference (Recommended for Most Users)
|
||||
|
||||
@@ -25,7 +25,7 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest
|
||||
teardown do
|
||||
# These tests persist global Setting.* values; reset them so state can't
|
||||
# leak into later (order-dependent) tests.
|
||||
%i[openai_oauth_token openai_oauth_account_id anthropic_access_token anthropic_base_url anthropic_model llm_provider].each do |key|
|
||||
%i[anthropic_access_token anthropic_base_url anthropic_model llm_provider].each do |key|
|
||||
Setting.public_send("#{key}=", nil)
|
||||
end
|
||||
end
|
||||
@@ -156,43 +156,6 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
test "can update Codex OAuth credentials when self hosting is enabled" do
|
||||
with_self_hosting do
|
||||
patch settings_hosting_url, params: {
|
||||
setting: {
|
||||
openai_oauth_token: "oauth-token",
|
||||
openai_oauth_account_id: "account-123"
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal "oauth-token", Setting.openai_oauth_token
|
||||
assert_equal "account-123", Setting.openai_oauth_account_id
|
||||
end
|
||||
end
|
||||
|
||||
test "infers ChatGPT account ID from Codex OAuth token" do
|
||||
payload = Base64.urlsafe_encode64({
|
||||
"https://api.openai.com/auth" => { "chatgpt_account_id" => "account-from-token" }
|
||||
}.to_json, padding: false)
|
||||
token = "header.#{payload}.signature"
|
||||
|
||||
with_self_hosting do
|
||||
patch settings_hosting_url, params: { setting: { openai_oauth_token: token } }
|
||||
|
||||
assert_equal "account-from-token", Setting.openai_oauth_account_id
|
||||
end
|
||||
end
|
||||
|
||||
test "ignores redacted Codex OAuth token placeholder" do
|
||||
with_self_hosting do
|
||||
Setting.openai_oauth_token = "previous-token"
|
||||
|
||||
patch settings_hosting_url, params: { setting: { openai_oauth_token: "********" } }
|
||||
|
||||
assert_equal "previous-token", Setting.openai_oauth_token
|
||||
end
|
||||
end
|
||||
|
||||
test "can update anthropic access token when self hosting is enabled" do
|
||||
with_self_hosting do
|
||||
patch settings_hosting_url, params: { setting: { anthropic_access_token: "fake-anthropic-key-for-tests" } }
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class Provider::GithubTest < ActiveSupport::TestCase
|
||||
test "release notes cache is scoped to the configured repository" do
|
||||
provider = Provider::Github.new
|
||||
|
||||
Rails.cache.expects(:fetch)
|
||||
.with("latest_github_release_notes/we-promise/sure", expires_in: Provider::Github::CACHE_TTL)
|
||||
.yields
|
||||
.returns(nil)
|
||||
|
||||
provider.client.expects(:releases).with("we-promise/sure").returns([])
|
||||
|
||||
assert_nil provider.fetch_latest_release_notes
|
||||
end
|
||||
end
|
||||
@@ -8,51 +8,6 @@ class Provider::OpenaiTest < ActiveSupport::TestCase
|
||||
@subject_model = "gpt-4.1"
|
||||
end
|
||||
|
||||
test "decodes ChatGPT account ID from OAuth token claims" do
|
||||
token = oauth_token_for("account-123")
|
||||
|
||||
assert_equal "account-123", Provider::Openai.oauth_account_id(token)
|
||||
end
|
||||
|
||||
test "returns nil when OAuth token claims cannot be decoded" do
|
||||
assert_nil Provider::Openai.oauth_account_id("not-a-jwt")
|
||||
end
|
||||
|
||||
test "configures Codex subscription endpoint and headers for OAuth" do
|
||||
provider = Provider::Openai.new(oauth_token_for("account-123"), oauth: true)
|
||||
client = provider.send(:client)
|
||||
|
||||
assert provider.oauth?
|
||||
refute provider.custom_provider?
|
||||
assert provider.supports_responses_endpoint?
|
||||
assert_equal Provider::Openai::CODEX_URI_BASE, client.uri_base
|
||||
assert_equal "account-123", client.extra_headers["ChatGPT-Account-ID"]
|
||||
assert_equal Provider::Openai::CODEX_ORIGINATOR, client.extra_headers["originator"]
|
||||
assert_equal "OpenAI (Codex subscription)", provider.provider_name
|
||||
end
|
||||
|
||||
test "uses explicit account ID for opaque OAuth tokens" do
|
||||
provider = Provider::Openai.new("opaque-token", oauth: true, account_id: "account-456")
|
||||
|
||||
assert_equal "account-456", provider.send(:client).extra_headers["ChatGPT-Account-ID"]
|
||||
end
|
||||
|
||||
test "requires an account ID for opaque OAuth tokens" do
|
||||
error = assert_raises(Provider::Openai::Error) do
|
||||
Provider::Openai.new("opaque-token", oauth: true)
|
||||
end
|
||||
|
||||
assert_match(/ChatGPT account ID is required/, error.message)
|
||||
end
|
||||
|
||||
test "uses Codex default model when OAuth is configured" do
|
||||
ClimateControl.modify("OPENAI_OAUTH_TOKEN" => oauth_token_for("account-123"), "OPENAI_MODEL" => nil) do
|
||||
Setting.stubs(:openai_model).returns(nil)
|
||||
|
||||
assert_equal Provider::Openai::CODEX_DEFAULT_MODEL, Provider::Openai.effective_model
|
||||
end
|
||||
end
|
||||
|
||||
test "openai errors are automatically raised" do
|
||||
VCR.use_cassette("openai/chat/error") do
|
||||
response = @openai.chat_response("Test", model: "invalid-model-that-will-trigger-api-error")
|
||||
@@ -549,14 +504,4 @@ class Provider::OpenaiTest < ActiveSupport::TestCase
|
||||
config.build_input(prompt: "hi", messages: [ { role: "user", content: "old" } ])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def oauth_token_for(account_id)
|
||||
header = Base64.urlsafe_encode64({ alg: "none", typ: "JWT" }.to_json, padding: false)
|
||||
payload = Base64.urlsafe_encode64({
|
||||
"https://api.openai.com/auth" => { "chatgpt_account_id" => account_id }
|
||||
}.to_json, padding: false)
|
||||
"#{header}.#{payload}.signature"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,12 +5,10 @@ class Provider::RegistryTest < ActiveSupport::TestCase
|
||||
# Ensure no LLM provider is configured
|
||||
ClimateControl.modify(
|
||||
"OPENAI_ACCESS_TOKEN" => nil,
|
||||
"OPENAI_OAUTH_TOKEN" => nil,
|
||||
"ANTHROPIC_ACCESS_TOKEN" => nil,
|
||||
"ANTHROPIC_API_KEY" => nil
|
||||
) do
|
||||
Setting.stubs(:openai_access_token).returns(nil)
|
||||
Setting.stubs(:openai_oauth_token).returns(nil)
|
||||
Setting.stubs(:anthropic_access_token).returns(nil)
|
||||
|
||||
registry = Provider::Registry.for_concept(:llm)
|
||||
@@ -42,9 +40,8 @@ class Provider::RegistryTest < ActiveSupport::TestCase
|
||||
|
||||
test "get_provider returns nil when provider not configured" do
|
||||
# Ensure OpenAI is not configured
|
||||
ClimateControl.modify("OPENAI_ACCESS_TOKEN" => nil, "OPENAI_OAUTH_TOKEN" => nil) do
|
||||
ClimateControl.modify("OPENAI_ACCESS_TOKEN" => nil) do
|
||||
Setting.stubs(:openai_access_token).returns(nil)
|
||||
Setting.stubs(:openai_oauth_token).returns(nil)
|
||||
|
||||
registry = Provider::Registry.for_concept(:llm)
|
||||
|
||||
@@ -96,12 +93,10 @@ class Provider::RegistryTest < ActiveSupport::TestCase
|
||||
# Use stub_env helper which properly stubs ENV access
|
||||
ClimateControl.modify(
|
||||
"OPENAI_ACCESS_TOKEN" => "",
|
||||
"OPENAI_OAUTH_TOKEN" => "",
|
||||
"OPENAI_URI_BASE" => "",
|
||||
"OPENAI_MODEL" => ""
|
||||
) do
|
||||
Setting.stubs(:openai_access_token).returns("test-token-from-setting")
|
||||
Setting.stubs(:openai_oauth_token).returns(nil)
|
||||
Setting.stubs(:openai_uri_base).returns(nil)
|
||||
Setting.stubs(:openai_model).returns(nil)
|
||||
|
||||
@@ -113,22 +108,6 @@ class Provider::RegistryTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
test "openai provider uses Codex OAuth credentials when configured" do
|
||||
ClimateControl.modify(
|
||||
"OPENAI_OAUTH_TOKEN" => "oauth-token",
|
||||
"OPENAI_ACCOUNT_ID" => "account-123",
|
||||
"OPENAI_ACCESS_TOKEN" => "api-key",
|
||||
"OPENAI_MODEL" => "gpt-5.6"
|
||||
) do
|
||||
provider = Provider::Registry.get_provider(:openai)
|
||||
|
||||
assert provider.oauth?
|
||||
assert_equal "account-123", provider.send(:client).extra_headers["ChatGPT-Account-ID"]
|
||||
assert_equal Provider::Openai::CODEX_URI_BASE, provider.send(:client).uri_base
|
||||
end
|
||||
end
|
||||
|
||||
test "preferred_llm_provider returns openai by default" do
|
||||
openai = mock("openai")
|
||||
Provider::Registry.stubs(:openai).returns(openai)
|
||||
|
||||
Reference in New Issue
Block a user