mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 20:08:18 +08:00
feat(llm): live model discovery for ChatGPT OAuth provider
This commit is contained in:
parent
bf7bc73f5f
commit
704c6317e3
@ -11,6 +11,7 @@ import org.springframework.util.StringUtils;
|
|||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
import vip.mate.exception.MateClawException;
|
import vip.mate.exception.MateClawException;
|
||||||
import vip.mate.llm.model.*;
|
import vip.mate.llm.model.*;
|
||||||
|
import vip.mate.llm.oauth.OpenAIOAuthService;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -30,6 +31,36 @@ public class ModelDiscoveryService {
|
|||||||
private final ModelProviderService modelProviderService;
|
private final ModelProviderService modelProviderService;
|
||||||
private final ModelConfigService modelConfigService;
|
private final ModelConfigService modelConfigService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
private final OpenAIOAuthService openAIOAuthService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Codex models endpoint that the ChatGPT subscription OAuth path exposes.
|
||||||
|
* Returns a JSON object {@code {"models": [{slug, supported_in_api, visibility,
|
||||||
|
* priority, ...}]}} once authenticated with a Bearer access token.
|
||||||
|
*/
|
||||||
|
static final String CHATGPT_CODEX_MODELS_URL =
|
||||||
|
"https://chatgpt.com/backend-api/codex/models?client_version=1.0.0";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synthetic forward-compat catalog: when a newer Codex slug is not surfaced
|
||||||
|
* by the live API but a known older sibling is, append the newer slug so
|
||||||
|
* users can opt into models OpenAI is rolling out without waiting for the
|
||||||
|
* metadata to flip. Mirrors the upstream Codex CLI behaviour.
|
||||||
|
*/
|
||||||
|
private static final List<Map.Entry<String, List<String>>> CHATGPT_FORWARD_COMPAT =
|
||||||
|
List.of(
|
||||||
|
Map.entry("gpt-5.5", List.of("gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex")),
|
||||||
|
Map.entry("gpt-5.4-mini", List.of("gpt-5.3-codex", "gpt-5.2-codex")),
|
||||||
|
Map.entry("gpt-5.4", List.of("gpt-5.3-codex", "gpt-5.2-codex")),
|
||||||
|
Map.entry("gpt-5.3-codex", List.of("gpt-5.2-codex"))
|
||||||
|
);
|
||||||
|
|
||||||
|
private RestClient chatgptCodexClient = RestClient.create();
|
||||||
|
|
||||||
|
/** Test seam — let unit tests point this at a {@link org.springframework.test.web.client.MockRestServiceServer}. */
|
||||||
|
void setChatgptCodexClient(RestClient client) {
|
||||||
|
this.chatgptCodexClient = client;
|
||||||
|
}
|
||||||
|
|
||||||
private static final Duration TIMEOUT = Duration.ofSeconds(10);
|
private static final Duration TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
@ -364,10 +395,12 @@ public class ModelDiscoveryService {
|
|||||||
case DASHSCOPE_NATIVE -> fetchDashScopeModels(provider);
|
case DASHSCOPE_NATIVE -> fetchDashScopeModels(provider);
|
||||||
case GEMINI_NATIVE -> fetchGeminiModels(provider);
|
case GEMINI_NATIVE -> fetchGeminiModels(provider);
|
||||||
case ANTHROPIC_MESSAGES -> fetchAnthropicModels(provider);
|
case ANTHROPIC_MESSAGES -> fetchAnthropicModels(provider);
|
||||||
// Claude Code OAuth provider has a fixed model catalog (Anthropic
|
// ChatGPT OAuth has its own discovery endpoint at chatgpt.com/backend-api/codex.
|
||||||
// doesn't expose model discovery on Bearer-auth requests). Models
|
case OPENAI_CHATGPT -> fetchChatGPTOAuthModels(provider);
|
||||||
// are seeded via Flyway, not discovered.
|
// Claude Code OAuth has a fixed model catalog — Anthropic doesn't
|
||||||
case OPENAI_CHATGPT, ANTHROPIC_CLAUDE_CODE ->
|
// expose a discovery endpoint to Bearer-auth requests, so models
|
||||||
|
// are seeded via Flyway.
|
||||||
|
case ANTHROPIC_CLAUDE_CODE ->
|
||||||
throw new MateClawException("err.llm.oauth_no_discovery",
|
throw new MateClawException("err.llm.oauth_no_discovery",
|
||||||
"OAuth provider 不支持模型发现");
|
"OAuth provider 不支持模型发现");
|
||||||
};
|
};
|
||||||
@ -453,6 +486,112 @@ public class ModelDiscoveryService {
|
|||||||
return parseAnthropicModelsResponse(body);
|
return parseAnthropicModelsResponse(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the ChatGPT subscription OAuth model catalog. Uses the user's
|
||||||
|
* already-stored OAuth access token (auto-refreshed if it's near expiry)
|
||||||
|
* and hits the same Codex endpoint the upstream client uses. Filters out
|
||||||
|
* models the API marks as not exposed ({@code supported_in_api == false})
|
||||||
|
* or hidden, sorts by priority, then layers in synthetic forward-compat
|
||||||
|
* entries (e.g. surface {@code gpt-5.5} when only older siblings are
|
||||||
|
* returned).
|
||||||
|
*/
|
||||||
|
private List<ModelInfoDTO> fetchChatGPTOAuthModels(ModelProviderEntity provider) {
|
||||||
|
String accessToken;
|
||||||
|
try {
|
||||||
|
accessToken = openAIOAuthService.ensureValidAccessToken();
|
||||||
|
} catch (MateClawException e) {
|
||||||
|
// Surface the precise i18n key from OpenAIOAuthService (e.g.
|
||||||
|
// err.llm.oauth_not_connected) so the UI can prompt the user to
|
||||||
|
// sign in. Wrapping would lose that signal.
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
String body;
|
||||||
|
try {
|
||||||
|
body = chatgptCodexClient.get()
|
||||||
|
.uri(CHATGPT_CODEX_MODELS_URL)
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
|
||||||
|
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
.retrieve()
|
||||||
|
.body(String.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[ModelDiscovery] ChatGPT OAuth models fetch failed: {}", e.getMessage());
|
||||||
|
throw new MateClawException("err.llm.chatgpt_models_fetch_failed",
|
||||||
|
"拉取 ChatGPT 可用模型失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return addChatGPTForwardCompatModels(parseChatGPTCodexModelsResponse(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the {@code {"models": [{slug, supported_in_api, visibility, priority}]}}
|
||||||
|
* response. Drops entries the API hides from the OAuth catalog and orders
|
||||||
|
* the rest by ascending {@code priority} (lower = higher precedence in
|
||||||
|
* the upstream client's UX).
|
||||||
|
*/
|
||||||
|
List<ModelInfoDTO> parseChatGPTCodexModelsResponse(String body) {
|
||||||
|
if (body == null || body.isBlank()) return List.of();
|
||||||
|
try {
|
||||||
|
JsonNode root = objectMapper.readTree(body);
|
||||||
|
JsonNode entries = root.path("models");
|
||||||
|
if (!entries.isArray()) return List.of();
|
||||||
|
|
||||||
|
// Sort by priority ascending while preserving the API-listed slug
|
||||||
|
List<int[]> indices = new ArrayList<>();
|
||||||
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
|
JsonNode item = entries.get(i);
|
||||||
|
if (!item.isObject()) continue;
|
||||||
|
String slug = item.path("slug").asText("").trim();
|
||||||
|
if (slug.isEmpty()) continue;
|
||||||
|
if (item.path("supported_in_api").asBoolean(true) == false) continue;
|
||||||
|
String vis = item.path("visibility").asText("").trim().toLowerCase();
|
||||||
|
if ("hide".equals(vis) || "hidden".equals(vis)) continue;
|
||||||
|
int priority = item.has("priority") && item.get("priority").isNumber()
|
||||||
|
? item.get("priority").asInt()
|
||||||
|
: 10_000;
|
||||||
|
indices.add(new int[]{priority, i});
|
||||||
|
}
|
||||||
|
indices.sort(Comparator.comparingInt((int[] a) -> a[0]).thenComparingInt(a -> a[1]));
|
||||||
|
|
||||||
|
Set<String> seen = new LinkedHashSet<>();
|
||||||
|
List<ModelInfoDTO> out = new ArrayList<>();
|
||||||
|
for (int[] idx : indices) {
|
||||||
|
JsonNode item = entries.get(idx[1]);
|
||||||
|
String slug = item.path("slug").asText("").trim();
|
||||||
|
if (!seen.add(slug)) continue;
|
||||||
|
out.add(new ModelInfoDTO(slug, slug));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[ModelDiscovery] Failed to parse ChatGPT codex models response: {}",
|
||||||
|
e.getMessage());
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append synthetic forward-compat entries for newer slugs that the API
|
||||||
|
* has not yet surfaced but a known older sibling is present for. Mirrors
|
||||||
|
* the reference client's behaviour so users can opt into {@code gpt-5.5}
|
||||||
|
* during a staged rollout.
|
||||||
|
*/
|
||||||
|
static List<ModelInfoDTO> addChatGPTForwardCompatModels(List<ModelInfoDTO> input) {
|
||||||
|
Set<String> seen = new LinkedHashSet<>();
|
||||||
|
List<ModelInfoDTO> out = new ArrayList<>(input.size() + CHATGPT_FORWARD_COMPAT.size());
|
||||||
|
for (ModelInfoDTO m : input) {
|
||||||
|
if (m.getId() != null && seen.add(m.getId())) out.add(m);
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, List<String>> e : CHATGPT_FORWARD_COMPAT) {
|
||||||
|
String synthetic = e.getKey();
|
||||||
|
if (seen.contains(synthetic)) continue;
|
||||||
|
if (e.getValue().stream().anyMatch(seen::contains)) {
|
||||||
|
seen.add(synthetic);
|
||||||
|
out.add(new ModelInfoDTO(synthetic, synthetic));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 协议分派:单模型测试 ====================
|
// ==================== 协议分派:单模型测试 ====================
|
||||||
|
|
||||||
private String sendTestPrompt(ModelProviderEntity provider, ModelProtocol protocol, String modelId) {
|
private String sendTestPrompt(ModelProviderEntity provider, ModelProtocol protocol, String modelId) {
|
||||||
|
|||||||
@ -120,7 +120,7 @@ VALUES ('volcengine-plan', 'Volcano Engine Coding Plan', '', 'OpenAIChatModel',
|
|||||||
|
|
||||||
MERGE INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
|
MERGE INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
|
||||||
KEY (provider_id)
|
KEY (provider_id)
|
||||||
VALUES ('openai-chatgpt', 'OpenAI ChatGPT (OAuth)', '', 'ChatGPTChatModel', '', 'https://chatgpt.com/backend-api', '{}', FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, 'oauth', NOW(), NOW());
|
VALUES ('openai-chatgpt', 'OpenAI ChatGPT (OAuth)', '', 'ChatGPTChatModel', '', 'https://chatgpt.com/backend-api', '{}', FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, 'oauth', NOW(), NOW());
|
||||||
|
|
||||||
-- RFC-062: Anthropic Claude Code OAuth provider. Credentials live on local
|
-- RFC-062: Anthropic Claude Code OAuth provider. Credentials live on local
|
||||||
-- disk (Keychain / ~/.claude/.credentials.json), not in this row — leave
|
-- disk (Keychain / ~/.claude/.credentials.json), not in this row — leave
|
||||||
@ -270,6 +270,7 @@ MERGE INTO mate_model_config (id, name, provider, model_name, description, tempe
|
|||||||
(1000000240, 'Kimi for Coding', 'kimi-code', 'kimi-for-coding', 'Kimi Code dedicated coding model', 0.2, 32768, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000240, 'Kimi for Coding', 'kimi-code', 'kimi-for-coding', 'Kimi Code dedicated coding model', 0.2, 32768, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
(1000000250, 'GPT-5.4', 'openai-chatgpt', 'gpt-5.4', 'ChatGPT Plus/Pro member model (OAuth login)', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000250, 'GPT-5.4', 'openai-chatgpt', 'gpt-5.4', 'ChatGPT Plus/Pro member model (OAuth login)', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
(1000000251, 'GPT-5.4 Mini', 'openai-chatgpt', 'gpt-5.4-mini', 'ChatGPT member lightweight model', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000251, 'GPT-5.4 Mini', 'openai-chatgpt', 'gpt-5.4-mini', 'ChatGPT member lightweight model', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
|
(1000000252, 'GPT-5.5', 'openai-chatgpt', 'gpt-5.5', 'ChatGPT Plus/Pro flagship model', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
-- GPT-5.5 series (direct OpenAI / Azure / OpenRouter / ChatGPT)
|
-- GPT-5.5 series (direct OpenAI / Azure / OpenRouter / ChatGPT)
|
||||||
(1000000260, 'GPT-5.5', 'openai', 'gpt-5.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000260, 'GPT-5.5', 'openai', 'gpt-5.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
(1000000261, 'GPT-5.5 Mini', 'openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000261, 'GPT-5.5 Mini', 'openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
|
|||||||
@ -120,7 +120,7 @@ VALUES ('volcengine-plan', 'Volcano Engine Coding Plan (火山方舟代码计划
|
|||||||
|
|
||||||
MERGE INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
|
MERGE INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
|
||||||
KEY (provider_id)
|
KEY (provider_id)
|
||||||
VALUES ('openai-chatgpt', 'OpenAI ChatGPT (OAuth)', '', 'ChatGPTChatModel', '', 'https://chatgpt.com/backend-api', '{}', FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, 'oauth', NOW(), NOW());
|
VALUES ('openai-chatgpt', 'OpenAI ChatGPT (OAuth)', '', 'ChatGPTChatModel', '', 'https://chatgpt.com/backend-api', '{}', FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, 'oauth', NOW(), NOW());
|
||||||
|
|
||||||
-- RFC-062:Anthropic Claude Code OAuth 订阅 provider。凭据存储在本地磁盘
|
-- RFC-062:Anthropic Claude Code OAuth 订阅 provider。凭据存储在本地磁盘
|
||||||
-- (macOS Keychain 或 ~/.claude/.credentials.json),不写入该行。
|
-- (macOS Keychain 或 ~/.claude/.credentials.json),不写入该行。
|
||||||
@ -273,6 +273,7 @@ MERGE INTO mate_model_config (id, name, provider, model_name, description, tempe
|
|||||||
(1000000240, 'Kimi for Coding', 'kimi-code', 'kimi-for-coding', 'Kimi Code 专用编码模型', 0.2, 32768, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000240, 'Kimi for Coding', 'kimi-code', 'kimi-for-coding', 'Kimi Code 专用编码模型', 0.2, 32768, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
(1000000250, 'GPT-5.4', 'openai-chatgpt', 'gpt-5.4', 'ChatGPT Plus/Pro 会员模型(OAuth 登录)', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000250, 'GPT-5.4', 'openai-chatgpt', 'gpt-5.4', 'ChatGPT Plus/Pro 会员模型(OAuth 登录)', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
(1000000251, 'GPT-5.4 Mini', 'openai-chatgpt', 'gpt-5.4-mini', 'ChatGPT 会员轻量模型', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000251, 'GPT-5.4 Mini', 'openai-chatgpt', 'gpt-5.4-mini', 'ChatGPT 会员轻量模型', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
|
(1000000252, 'GPT-5.5', 'openai-chatgpt', 'gpt-5.5', 'ChatGPT Plus/Pro 会员旗舰模型', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
-- GPT-5.5 系列(OpenAI / Azure / OpenRouter)
|
-- GPT-5.5 系列(OpenAI / Azure / OpenRouter)
|
||||||
(1000000260, 'GPT-5.5', 'openai', 'gpt-5.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000260, 'GPT-5.5', 'openai', 'gpt-5.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
(1000000261, 'GPT-5.5 Mini', 'openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
(1000000261, 'GPT-5.5 Mini', 'openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
-- Enable model discovery on the ChatGPT OAuth provider so the catalog can be
|
||||||
|
-- pulled live from chatgpt.com/backend-api/codex/models, and seed the GPT-5.5
|
||||||
|
-- flagship row alongside the existing GPT-5.4 / GPT-5.4 Mini entries. The
|
||||||
|
-- guard clause + MERGE keep the migration idempotent.
|
||||||
|
|
||||||
|
UPDATE mate_model_provider
|
||||||
|
SET support_model_discovery = TRUE,
|
||||||
|
update_time = CURRENT_TIMESTAMP
|
||||||
|
WHERE provider_id = 'openai-chatgpt'
|
||||||
|
AND support_model_discovery <> TRUE;
|
||||||
|
|
||||||
|
MERGE INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
|
||||||
|
KEY (id)
|
||||||
|
VALUES (1000000252, 'GPT-5.5', 'openai-chatgpt', 'gpt-5.5', 'ChatGPT Plus/Pro flagship model', NULL, 128000, NULL, TRUE, TRUE, FALSE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0);
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
-- Enable model discovery on the ChatGPT OAuth provider so the catalog can be
|
||||||
|
-- pulled live from chatgpt.com/backend-api/codex/models, and seed the GPT-5.5
|
||||||
|
-- flagship row alongside the existing GPT-5.4 / GPT-5.4 Mini entries. The
|
||||||
|
-- ON DUPLICATE KEY UPDATE clause keeps the migration idempotent.
|
||||||
|
|
||||||
|
UPDATE mate_model_provider
|
||||||
|
SET support_model_discovery = TRUE,
|
||||||
|
update_time = CURRENT_TIMESTAMP
|
||||||
|
WHERE provider_id = 'openai-chatgpt'
|
||||||
|
AND support_model_discovery <> TRUE;
|
||||||
|
|
||||||
|
INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
|
||||||
|
VALUES (1000000252, 'GPT-5.5', 'openai-chatgpt', 'gpt-5.5', 'ChatGPT Plus/Pro flagship model', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
description = VALUES(description),
|
||||||
|
update_time = NOW();
|
||||||
@ -231,6 +231,7 @@ err.llm.oauth_no_token=OAuth \u54cd\u5e94\u4e2d\u7f3a\u5c11 access_token
|
|||||||
err.llm.chatgpt_not_configured=ChatGPT provider \u672a\u914d\u7f6e
|
err.llm.chatgpt_not_configured=ChatGPT provider \u672a\u914d\u7f6e
|
||||||
err.llm.pkce_failed=PKCE \u751f\u6210\u5931\u8d25
|
err.llm.pkce_failed=PKCE \u751f\u6210\u5931\u8d25
|
||||||
err.llm.device_code_start_failed=Device code \u7533\u8bf7\u5931\u8d25
|
err.llm.device_code_start_failed=Device code \u7533\u8bf7\u5931\u8d25
|
||||||
|
err.llm.chatgpt_models_fetch_failed=\u62c9\u53d6 ChatGPT \u53ef\u7528\u6a21\u578b\u5931\u8d25
|
||||||
err.llm.chatgpt_stream_failed=ChatGPT \u6d41\u5f0f\u8c03\u7528\u5931\u8d25
|
err.llm.chatgpt_stream_failed=ChatGPT \u6d41\u5f0f\u8c03\u7528\u5931\u8d25
|
||||||
err.llm.chatgpt_error=ChatGPT \u8fd4\u56de\u9519\u8bef
|
err.llm.chatgpt_error=ChatGPT \u8fd4\u56de\u9519\u8bef
|
||||||
err.llm.chatgpt_account_missing=chatgpt-account-id \u7f3a\u5931
|
err.llm.chatgpt_account_missing=chatgpt-account-id \u7f3a\u5931
|
||||||
|
|||||||
@ -243,6 +243,7 @@ err.llm.oauth_no_token=access_token missing in OAuth response
|
|||||||
err.llm.chatgpt_not_configured=ChatGPT provider not configured, check database initialization
|
err.llm.chatgpt_not_configured=ChatGPT provider not configured, check database initialization
|
||||||
err.llm.pkce_failed=PKCE code_challenge generation failed
|
err.llm.pkce_failed=PKCE code_challenge generation failed
|
||||||
err.llm.device_code_start_failed=Device code request failed
|
err.llm.device_code_start_failed=Device code request failed
|
||||||
|
err.llm.chatgpt_models_fetch_failed=Failed to fetch available ChatGPT models
|
||||||
err.llm.chatgpt_stream_failed=ChatGPT streaming call failed
|
err.llm.chatgpt_stream_failed=ChatGPT streaming call failed
|
||||||
err.llm.chatgpt_error=ChatGPT returned an error
|
err.llm.chatgpt_error=ChatGPT returned an error
|
||||||
err.llm.chatgpt_account_missing=chatgpt-account-id missing, disconnect and re-login via OAuth
|
err.llm.chatgpt_account_missing=chatgpt-account-id missing, disconnect and re-login via OAuth
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user