feat(agents): add configurable DeepSeek Harness model catalog
- Separate default model selection from catalog configuration - Use API metadata with per-model and provider overrides - Expose documented capability, reasoning, protocol, and request settings - Preserve customizations with conflict detection and transactional rollback
This commit is contained in:
1 parent
145b585c3f
commit
4d8d549e04
28 files changed
+2047
-20
No files matched your search
@@ -63,6 +63,104 @@ export const jaOverrides = {
|
||||
'config.software.defaultTerminal': '既定のターミナル',
|
||||
'config.software.defaultTerminalDescription': 'CLI エージェントの起動に使用するターミナルアプリ',
|
||||
'config.software.terminal.auto': '自動選択',
|
||||
"agents.harness.button": "モデル設定",
|
||||
"agents.harness.providerInput": "プロバイダーの既定入力を使用",
|
||||
"agents.harness.defaultModel": "デフォルトモデル",
|
||||
"agents.harness.defaultHint": "新しいセッションの初期モデルです。DSH 内で自由に切り替えられます。",
|
||||
"agents.harness.title": "DeepSeek Harness モデル設定",
|
||||
"agents.harness.subtitle": "API のモデル一覧を取得し、モデル能力とプロバイダー設定を調整します。",
|
||||
"agents.harness.provider": "プロバイダー設定",
|
||||
"agents.harness.auto": "自動",
|
||||
"agents.harness.apiSource": "API に従う",
|
||||
"agents.harness.inheritHint": "空欄は API または DSH の既定値に従います。",
|
||||
"agents.harness.apiInput": "API の入力形式:{value}",
|
||||
"agents.harness.unknown": "未提供(非対応とは限りません)",
|
||||
"agents.harness.managedConnection": "接続先:{url}。URL と認証は EasyCLIProxyAPI が管理します。",
|
||||
"agents.harness.reset": "自動に戻す",
|
||||
"agents.harness.resetGroup": "このグループをリセット",
|
||||
"agents.harness.yes": "有効",
|
||||
"agents.harness.no": "無効",
|
||||
"agents.harness.textOnly": "テキストのみ",
|
||||
"agents.harness.textImage": "テキストと画像",
|
||||
"agents.harness.reasoningDisabled": "推論なし",
|
||||
"agents.harness.reasoningCustom": "推論レベルを指定",
|
||||
"agents.harness.noWireValue": "パラメーターを省略",
|
||||
"agents.harness.invalid": "設定値を確認してください:{field}",
|
||||
"agents.harness.changed": "モデル一覧または設定が変更されました。再読み込みしてください。",
|
||||
"agents.harness.saved": "保存しました。次のリクエストから適用されます。デフォルトモデルは変更されません。",
|
||||
"agents.harness.savedDraft": "保存しました。DSH 接続時に適用されます。",
|
||||
"agents.harness.saveHint": "デフォルトモデルを変更せずに一覧の設定を保存します。",
|
||||
"agents.harness.hint.input": "API の情報を使用し、未提供の場合はプロバイダーの既定値に従います。",
|
||||
"agents.harness.hint.defaultInput": "API に入力情報がないモデルの既定値です。画像を有効にして、例外をモデルごとに変更できます。",
|
||||
"agents.harness.hint.maxTokens": "設定すると、リクエストごとの既定の出力上限にもなります。",
|
||||
"agents.harness.hint.defaultMaxTokens": "モデル能力の既定値です。リクエストの出力制限にはなりません。",
|
||||
"agents.harness.hint.api": "全モデルに同じプロトコルを使用します。変更後は互換性設定を確認してください。",
|
||||
"agents.harness.hint.headers": "ヘッダー名と値の JSON オブジェクト。空欄は既定値を使用します。",
|
||||
"agents.harness.hint.reasoningEfforts": "利用可能なレベルと送信する reasoning_effort 値を指定します。off のみ空欄にできます。",
|
||||
"agents.harness.hint.compat": "現在のプロトコルの互換性設定です。モデル設定が優先され、自動値は DSH が決定します。",
|
||||
"agents.harness.hint.thinkingBudgets": "推論予算に対応するプロバイダーの各レベルのトークン数。",
|
||||
"agents.harness.hint.retryPolicy": "normal は既定で最大 5 回再試行します。always は成功またはキャンセルまで続けます。",
|
||||
"agents.harness.hint.backoff": "再試行間隔とジッターを設定します。初期間隔は最大間隔以下にしてください。",
|
||||
"agents.harness.field.name": "表示名",
|
||||
"agents.harness.field.contextWindow": "コンテキスト(tokens)",
|
||||
"agents.harness.field.maxTokens": "出力上限(tokens)",
|
||||
"agents.harness.field.input": "入力形式",
|
||||
"agents.harness.field.reasoningEfforts": "推論レベル",
|
||||
"agents.harness.field.compat": "プロトコル互換性",
|
||||
"agents.harness.field.displayName": "プロバイダー名",
|
||||
"agents.harness.field.api": "API プロトコル",
|
||||
"agents.harness.field.defaultContextWindow": "既定コンテキスト(tokens)",
|
||||
"agents.harness.field.defaultMaxTokens": "既定出力能力(tokens)",
|
||||
"agents.harness.field.defaultInput": "既定入力形式",
|
||||
"agents.harness.field.reasoning": "既定推論レベル",
|
||||
"agents.harness.field.thinkingBudgets": "推論予算(tokens)",
|
||||
"agents.harness.field.cacheRetention": "キャッシュ保持",
|
||||
"agents.harness.field.transport": "ストリーミング方式",
|
||||
"agents.harness.field.timeoutMs": "リクエストタイムアウト(ms)",
|
||||
"agents.harness.field.websocketConnectTimeoutMs": "WebSocket 接続タイムアウト(ms)",
|
||||
"agents.harness.field.streamIdleTimeoutMs": "ストリーム待機タイムアウト(ms)",
|
||||
"agents.harness.field.maxRequestImageBytes": "リクエスト画像合計(bytes)",
|
||||
"agents.harness.field.requestImagePixelBudget": "画像ピクセル予算",
|
||||
"agents.harness.field.requestImageMaxBytes": "画像サイズ(bytes)",
|
||||
"agents.harness.field.headers": "追加ヘッダー(JSON)",
|
||||
"agents.harness.field.retryPolicy": "再試行ポリシー",
|
||||
"agents.harness.field.minimal": "minimal",
|
||||
"agents.harness.field.low": "low",
|
||||
"agents.harness.field.medium": "medium",
|
||||
"agents.harness.field.high": "high",
|
||||
"agents.harness.field.mode": "再試行モード",
|
||||
"agents.harness.field.maxRetries": "最大再試行回数",
|
||||
"agents.harness.field.retryableCodes": "再試行するエラー(JSON)",
|
||||
"agents.harness.field.backoff": "再試行間隔",
|
||||
"agents.harness.field.initialDelayMs": "初期間隔(ms)",
|
||||
"agents.harness.field.maxDelayMs": "最大間隔(ms)",
|
||||
"agents.harness.field.jitterRatio": "ジッター比率(0–1)",
|
||||
"agents.harness.field.supportsStore": "supportsStore",
|
||||
"agents.harness.field.supportsDeveloperRole": "supportsDeveloperRole",
|
||||
"agents.harness.field.supportsReasoningEffort": "supportsReasoningEffort",
|
||||
"agents.harness.field.supportsUsageInStreaming": "supportsUsageInStreaming",
|
||||
"agents.harness.field.supportsFinishReason": "supportsFinishReason",
|
||||
"agents.harness.field.maxTokensField": "maxTokensField",
|
||||
"agents.harness.field.requiresToolResultName": "requiresToolResultName",
|
||||
"agents.harness.field.requiresAssistantAfterToolResult": "requiresAssistantAfterToolResult",
|
||||
"agents.harness.field.requiresThinkingAsText": "requiresThinkingAsText",
|
||||
"agents.harness.field.requiresReasoningContentOnAssistantMessages": "requiresReasoningContentOnAssistantMessages",
|
||||
"agents.harness.field.thinkingFormat": "thinkingFormat",
|
||||
"agents.harness.field.chatTemplateKwargs": "chatTemplateKwargs",
|
||||
"agents.harness.field.chatTemplateArgs": "chatTemplateArgs",
|
||||
"agents.harness.field.supportsThinkingTokenBudget": "supportsThinkingTokenBudget",
|
||||
"agents.harness.field.thinkingTokenBudgetField": "thinkingTokenBudgetField",
|
||||
"agents.harness.field.vllmPriority": "vllmPriority",
|
||||
"agents.harness.field.supportsMaxOutputTokens": "supportsMaxOutputTokens",
|
||||
"agents.harness.field.supportsStrictMode": "supportsStrictMode",
|
||||
"agents.harness.field.cacheControlFormat": "cacheControlFormat",
|
||||
"agents.harness.field.supportsLongCacheRetention": "supportsLongCacheRetention",
|
||||
"agents.harness.field.supportsEagerToolInputStreaming": "supportsEagerToolInputStreaming",
|
||||
"agents.harness.field.supportsCacheControlOnTools": "supportsCacheControlOnTools",
|
||||
"agents.harness.field.supportsTemperature": "supportsTemperature",
|
||||
"agents.harness.field.forceAdaptiveThinking": "forceAdaptiveThinking",
|
||||
"agents.harness.field.allowEmptySignature": "allowEmptySignature",
|
||||
"agents.harness.field.supportsStrictTools": "supportsStrictTools",
|
||||
'agents.catalog.button': 'モデル一覧の設定',
|
||||
'agents.catalog.title': 'Codex モデル一覧',
|
||||
'agents.catalog.subtitle': 'コンテキストの既定値は CPA コア API から取得します。モデルのパラメーターを編集し、Codex モデル一覧に保存できます。',
|
||||
|
||||
@@ -13,6 +13,104 @@ export const en: Record<MessageKey, string> = {
|
||||
'apiAccess.cloak.never': 'Never enabled',
|
||||
'usage.unit.requests': 'requests',
|
||||
'usage.unit.tokens': 'Token',
|
||||
"agents.harness.button": "Configure models",
|
||||
"agents.harness.providerInput": "Use provider default input",
|
||||
"agents.harness.defaultModel": "Default model",
|
||||
"agents.harness.defaultHint": "Used for new sessions. You can switch to any catalog model inside DSH.",
|
||||
"agents.harness.title": "DeepSeek Harness model configuration",
|
||||
"agents.harness.subtitle": "Load the model catalog from the API and customize models and provider settings.",
|
||||
"agents.harness.provider": "Provider settings",
|
||||
"agents.harness.auto": "Automatic",
|
||||
"agents.harness.apiSource": "Follow API",
|
||||
"agents.harness.inheritHint": "Leave fields empty to follow the API or DSH defaults.",
|
||||
"agents.harness.apiInput": "API input modalities: {value}",
|
||||
"agents.harness.unknown": "Not provided (capability unknown)",
|
||||
"agents.harness.managedConnection": "Endpoint: {url}. EasyCLIProxyAPI manages the endpoint and credentials.",
|
||||
"agents.harness.reset": "Restore automatic",
|
||||
"agents.harness.resetGroup": "Reset this group",
|
||||
"agents.harness.yes": "Enabled",
|
||||
"agents.harness.no": "Disabled",
|
||||
"agents.harness.textOnly": "Text only",
|
||||
"agents.harness.textImage": "Text and image",
|
||||
"agents.harness.reasoningDisabled": "No reasoning",
|
||||
"agents.harness.reasoningCustom": "Custom reasoning levels",
|
||||
"agents.harness.noWireValue": "Omit parameter",
|
||||
"agents.harness.invalid": "Check the configuration value: {field}",
|
||||
"agents.harness.changed": "The model list or configuration changed. Reload before saving.",
|
||||
"agents.harness.saved": "Saved. Applies on the next model request; the default model is unchanged.",
|
||||
"agents.harness.savedDraft": "Saved. These settings will be used when connecting DSH.",
|
||||
"agents.harness.saveHint": "Save catalog settings without changing the selected default model.",
|
||||
"agents.harness.hint.input": "Use API metadata automatically; otherwise inherit the provider default input.",
|
||||
"agents.harness.hint.defaultInput": "Fallback for models without API input metadata. Set text and image here, then override exceptions per model.",
|
||||
"agents.harness.hint.maxTokens": "An explicit value also becomes the model’s default output limit per request.",
|
||||
"agents.harness.hint.defaultMaxTokens": "Fallback output capacity; does not set a per-request output limit.",
|
||||
"agents.harness.hint.api": "One protocol applies to the whole provider. Review compatibility settings after changing it.",
|
||||
"agents.harness.hint.headers": "JSON object of header names and values; empty uses default headers.",
|
||||
"agents.harness.hint.reasoningEfforts": "Choose offered levels and their wire reasoning_effort values. Only off may have an empty wire value.",
|
||||
"agents.harness.hint.compat": "Compatibility switches for the current protocol. Model settings override provider settings; DSH resolves automatic values.",
|
||||
"agents.harness.hint.thinkingBudgets": "Token budgets for providers that support reasoning budgets.",
|
||||
"agents.harness.hint.retryPolicy": "Normal retries up to five times by default. Always retries until success or cancellation.",
|
||||
"agents.harness.hint.backoff": "Configure retry delays and jitter. Initial delay must not exceed maximum delay.",
|
||||
"agents.harness.field.name": "Display name",
|
||||
"agents.harness.field.contextWindow": "Context window (tokens)",
|
||||
"agents.harness.field.maxTokens": "Maximum output (tokens)",
|
||||
"agents.harness.field.input": "Input modalities",
|
||||
"agents.harness.field.reasoningEfforts": "Reasoning levels",
|
||||
"agents.harness.field.compat": "Protocol compatibility",
|
||||
"agents.harness.field.displayName": "Provider name",
|
||||
"agents.harness.field.api": "API protocol",
|
||||
"agents.harness.field.defaultContextWindow": "Default context (tokens)",
|
||||
"agents.harness.field.defaultMaxTokens": "Default output capacity (tokens)",
|
||||
"agents.harness.field.defaultInput": "Default input modalities",
|
||||
"agents.harness.field.reasoning": "Default reasoning level",
|
||||
"agents.harness.field.thinkingBudgets": "Thinking budgets (tokens)",
|
||||
"agents.harness.field.cacheRetention": "Cache retention",
|
||||
"agents.harness.field.transport": "Streaming transport",
|
||||
"agents.harness.field.timeoutMs": "Request timeout (ms)",
|
||||
"agents.harness.field.websocketConnectTimeoutMs": "WebSocket connection timeout (ms)",
|
||||
"agents.harness.field.streamIdleTimeoutMs": "Stream idle timeout (ms)",
|
||||
"agents.harness.field.maxRequestImageBytes": "Total request image budget (bytes)",
|
||||
"agents.harness.field.requestImagePixelBudget": "Pixels per image",
|
||||
"agents.harness.field.requestImageMaxBytes": "Encoded bytes per image",
|
||||
"agents.harness.field.headers": "Additional headers (JSON)",
|
||||
"agents.harness.field.retryPolicy": "Retry policy",
|
||||
"agents.harness.field.minimal": "minimal",
|
||||
"agents.harness.field.low": "low",
|
||||
"agents.harness.field.medium": "medium",
|
||||
"agents.harness.field.high": "high",
|
||||
"agents.harness.field.mode": "Retry mode",
|
||||
"agents.harness.field.maxRetries": "Maximum retries",
|
||||
"agents.harness.field.retryableCodes": "Retryable error codes (JSON)",
|
||||
"agents.harness.field.backoff": "Retry backoff",
|
||||
"agents.harness.field.initialDelayMs": "Initial delay (ms)",
|
||||
"agents.harness.field.maxDelayMs": "Maximum delay (ms)",
|
||||
"agents.harness.field.jitterRatio": "Jitter ratio (0–1)",
|
||||
"agents.harness.field.supportsStore": "supportsStore",
|
||||
"agents.harness.field.supportsDeveloperRole": "supportsDeveloperRole",
|
||||
"agents.harness.field.supportsReasoningEffort": "supportsReasoningEffort",
|
||||
"agents.harness.field.supportsUsageInStreaming": "supportsUsageInStreaming",
|
||||
"agents.harness.field.supportsFinishReason": "supportsFinishReason",
|
||||
"agents.harness.field.maxTokensField": "maxTokensField",
|
||||
"agents.harness.field.requiresToolResultName": "requiresToolResultName",
|
||||
"agents.harness.field.requiresAssistantAfterToolResult": "requiresAssistantAfterToolResult",
|
||||
"agents.harness.field.requiresThinkingAsText": "requiresThinkingAsText",
|
||||
"agents.harness.field.requiresReasoningContentOnAssistantMessages": "requiresReasoningContentOnAssistantMessages",
|
||||
"agents.harness.field.thinkingFormat": "thinkingFormat",
|
||||
"agents.harness.field.chatTemplateKwargs": "chatTemplateKwargs",
|
||||
"agents.harness.field.chatTemplateArgs": "chatTemplateArgs",
|
||||
"agents.harness.field.supportsThinkingTokenBudget": "supportsThinkingTokenBudget",
|
||||
"agents.harness.field.thinkingTokenBudgetField": "thinkingTokenBudgetField",
|
||||
"agents.harness.field.vllmPriority": "vllmPriority",
|
||||
"agents.harness.field.supportsMaxOutputTokens": "supportsMaxOutputTokens",
|
||||
"agents.harness.field.supportsStrictMode": "supportsStrictMode",
|
||||
"agents.harness.field.cacheControlFormat": "cacheControlFormat",
|
||||
"agents.harness.field.supportsLongCacheRetention": "supportsLongCacheRetention",
|
||||
"agents.harness.field.supportsEagerToolInputStreaming": "supportsEagerToolInputStreaming",
|
||||
"agents.harness.field.supportsCacheControlOnTools": "supportsCacheControlOnTools",
|
||||
"agents.harness.field.supportsTemperature": "supportsTemperature",
|
||||
"agents.harness.field.forceAdaptiveThinking": "forceAdaptiveThinking",
|
||||
"agents.harness.field.allowEmptySignature": "allowEmptySignature",
|
||||
"agents.harness.field.supportsStrictTools": "supportsStrictTools",
|
||||
'agents.catalog.button': 'Model list settings',
|
||||
'agents.catalog.title': 'Codex model catalog',
|
||||
'agents.catalog.subtitle': 'Default context settings come from the CPA core API. Customize model parameters and save them to the Codex catalog.',
|
||||
|
||||
@@ -12,6 +12,104 @@ export const zhCN = {
|
||||
'apiAccess.cloak.never': '从不启用',
|
||||
'usage.unit.requests': '次请求',
|
||||
'usage.unit.tokens': 'Token',
|
||||
"agents.harness.button": "配置模型",
|
||||
"agents.harness.providerInput": "使用供应商默认输入",
|
||||
"agents.harness.defaultModel": "默认模型",
|
||||
"agents.harness.defaultHint": "仅作为新会话的默认模型;启动 DSH 后仍可自由切换整个目录中的模型。",
|
||||
"agents.harness.title": "DeepSeek Harness 模型配置",
|
||||
"agents.harness.subtitle": "从 API 获取整个模型目录,按需覆盖模型能力和供应商参数。",
|
||||
"agents.harness.provider": "供应商设置",
|
||||
"agents.harness.auto": "自动",
|
||||
"agents.harness.apiSource": "跟随 API",
|
||||
"agents.harness.inheritHint": "留空即跟随 API 或 DSH 默认值。",
|
||||
"agents.harness.apiInput": "API 声明的输入类型:{value}",
|
||||
"agents.harness.unknown": "未提供(不代表不支持)",
|
||||
"agents.harness.managedConnection": "连接地址:{url}。地址和鉴权由 EasyCLIProxyAPI 管理。",
|
||||
"agents.harness.reset": "恢复自动",
|
||||
"agents.harness.resetGroup": "此组恢复自动",
|
||||
"agents.harness.yes": "开启",
|
||||
"agents.harness.no": "关闭",
|
||||
"agents.harness.textOnly": "仅文本",
|
||||
"agents.harness.textImage": "文本和图像",
|
||||
"agents.harness.reasoningDisabled": "不支持推理",
|
||||
"agents.harness.reasoningCustom": "自定义推理等级",
|
||||
"agents.harness.noWireValue": "不发送参数",
|
||||
"agents.harness.invalid": "请检查配置值:{field}",
|
||||
"agents.harness.changed": "模型列表或配置已变化,请重新加载后再保存。",
|
||||
"agents.harness.saved": "已保存,下次模型请求时生效;默认模型保持不变。",
|
||||
"agents.harness.savedDraft": "已保存,接入 DSH 时会使用这些配置。",
|
||||
"agents.harness.saveHint": "保存整个目录的配置,不改变页面选择的默认模型。",
|
||||
"agents.harness.hint.input": "自动使用 API 声明;未声明时继承供应商默认输入类型。",
|
||||
"agents.harness.hint.defaultInput": "仅用于 API 未声明输入类型的模型。可统一设为文本和图像,再单独修正例外模型。",
|
||||
"agents.harness.hint.maxTokens": "手动设置后也会成为该模型每次请求的默认输出上限。",
|
||||
"agents.harness.hint.defaultMaxTokens": "模型的输出能力兜底值,不会自动成为每次请求的输出限制。",
|
||||
"agents.harness.hint.api": "整个供应商使用同一种协议;切换协议后请检查兼容选项。",
|
||||
"agents.harness.hint.headers": "JSON 键值对象;留空使用默认请求头。",
|
||||
"agents.harness.hint.reasoningEfforts": "勾选 DSH 中可选的等级,并填写实际发送的 reasoning_effort 值。只有 off 可留空。",
|
||||
"agents.harness.hint.compat": "仅显示当前协议的兼容开关。单个模型的设置优先于供应商设置,自动值由 DSH 决定。",
|
||||
"agents.harness.hint.thinkingBudgets": "为支持推理预算的供应商指定各等级的 token 预算。",
|
||||
"agents.harness.hint.retryPolicy": "normal 默认最多重试 5 次;always 会持续重试直到成功或取消。",
|
||||
"agents.harness.hint.backoff": "配置重试间隔与抖动;初始间隔不能大于最大间隔。",
|
||||
"agents.harness.field.name": "显示名称",
|
||||
"agents.harness.field.contextWindow": "上下文窗口(tokens)",
|
||||
"agents.harness.field.maxTokens": "输出上限(tokens)",
|
||||
"agents.harness.field.input": "输入类型",
|
||||
"agents.harness.field.reasoningEfforts": "推理等级",
|
||||
"agents.harness.field.compat": "协议兼容选项",
|
||||
"agents.harness.field.displayName": "供应商名称",
|
||||
"agents.harness.field.api": "API 协议",
|
||||
"agents.harness.field.defaultContextWindow": "默认上下文(tokens)",
|
||||
"agents.harness.field.defaultMaxTokens": "默认输出能力(tokens)",
|
||||
"agents.harness.field.defaultInput": "默认输入类型",
|
||||
"agents.harness.field.reasoning": "默认推理等级",
|
||||
"agents.harness.field.thinkingBudgets": "推理预算(tokens)",
|
||||
"agents.harness.field.cacheRetention": "缓存保留",
|
||||
"agents.harness.field.transport": "流式传输方式",
|
||||
"agents.harness.field.timeoutMs": "请求超时(ms)",
|
||||
"agents.harness.field.websocketConnectTimeoutMs": "WebSocket 连接超时(ms)",
|
||||
"agents.harness.field.streamIdleTimeoutMs": "流式空闲超时(ms)",
|
||||
"agents.harness.field.maxRequestImageBytes": "请求图像总预算(bytes)",
|
||||
"agents.harness.field.requestImagePixelBudget": "单图像素预算",
|
||||
"agents.harness.field.requestImageMaxBytes": "单图编码预算(bytes)",
|
||||
"agents.harness.field.headers": "额外请求头(JSON)",
|
||||
"agents.harness.field.retryPolicy": "重试策略",
|
||||
"agents.harness.field.minimal": "minimal",
|
||||
"agents.harness.field.low": "low",
|
||||
"agents.harness.field.medium": "medium",
|
||||
"agents.harness.field.high": "high",
|
||||
"agents.harness.field.mode": "重试模式",
|
||||
"agents.harness.field.maxRetries": "最大重试次数",
|
||||
"agents.harness.field.retryableCodes": "可重试错误码(JSON)",
|
||||
"agents.harness.field.backoff": "重试间隔",
|
||||
"agents.harness.field.initialDelayMs": "初始间隔(ms)",
|
||||
"agents.harness.field.maxDelayMs": "最大间隔(ms)",
|
||||
"agents.harness.field.jitterRatio": "抖动比例(0–1)",
|
||||
"agents.harness.field.supportsStore": "supportsStore",
|
||||
"agents.harness.field.supportsDeveloperRole": "supportsDeveloperRole",
|
||||
"agents.harness.field.supportsReasoningEffort": "supportsReasoningEffort",
|
||||
"agents.harness.field.supportsUsageInStreaming": "supportsUsageInStreaming",
|
||||
"agents.harness.field.supportsFinishReason": "supportsFinishReason",
|
||||
"agents.harness.field.maxTokensField": "maxTokensField",
|
||||
"agents.harness.field.requiresToolResultName": "requiresToolResultName",
|
||||
"agents.harness.field.requiresAssistantAfterToolResult": "requiresAssistantAfterToolResult",
|
||||
"agents.harness.field.requiresThinkingAsText": "requiresThinkingAsText",
|
||||
"agents.harness.field.requiresReasoningContentOnAssistantMessages": "requiresReasoningContentOnAssistantMessages",
|
||||
"agents.harness.field.thinkingFormat": "thinkingFormat",
|
||||
"agents.harness.field.chatTemplateKwargs": "chatTemplateKwargs",
|
||||
"agents.harness.field.chatTemplateArgs": "chatTemplateArgs",
|
||||
"agents.harness.field.supportsThinkingTokenBudget": "supportsThinkingTokenBudget",
|
||||
"agents.harness.field.thinkingTokenBudgetField": "thinkingTokenBudgetField",
|
||||
"agents.harness.field.vllmPriority": "vllmPriority",
|
||||
"agents.harness.field.supportsMaxOutputTokens": "supportsMaxOutputTokens",
|
||||
"agents.harness.field.supportsStrictMode": "supportsStrictMode",
|
||||
"agents.harness.field.cacheControlFormat": "cacheControlFormat",
|
||||
"agents.harness.field.supportsLongCacheRetention": "supportsLongCacheRetention",
|
||||
"agents.harness.field.supportsEagerToolInputStreaming": "supportsEagerToolInputStreaming",
|
||||
"agents.harness.field.supportsCacheControlOnTools": "supportsCacheControlOnTools",
|
||||
"agents.harness.field.supportsTemperature": "supportsTemperature",
|
||||
"agents.harness.field.forceAdaptiveThinking": "forceAdaptiveThinking",
|
||||
"agents.harness.field.allowEmptySignature": "allowEmptySignature",
|
||||
"agents.harness.field.supportsStrictTools": "supportsStrictTools",
|
||||
'agents.catalog.button': '模型列表设置',
|
||||
'agents.catalog.title': 'Codex 模型列表',
|
||||
'agents.catalog.subtitle': '默认上下文参数读取自 CPA 内核 API;可自定义当前模型参数并保存到 Codex 模型列表。',
|
||||
|
||||
@@ -63,6 +63,7 @@ import type { ModelOption } from '../services/modelService';
|
||||
import { getCurrentLocale, translate, useI18n } from '../i18n';
|
||||
import { CodexSessionsPanel } from './CodexSessionsPanel';
|
||||
import { CodexModelCatalogDialog } from './CodexModelCatalogDialog';
|
||||
import { DeepSeekHarnessCatalogDialog } from './DeepSeekHarnessCatalogDialog';
|
||||
import { AgentConfigBackupDialog } from './AgentConfigBackupDialog';
|
||||
import { AgentConfigManagementPanel, AgentConfigurationFeedback, AgentRunControls } from './AgentControls';
|
||||
|
||||
@@ -753,6 +754,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
|
||||
const [clearConfirmOpen, setClearConfirmOpen] = useState(false);
|
||||
const [launchDirectoryDialogOpen, setLaunchDirectoryDialogOpen] = useState(false);
|
||||
const [codexCatalogDialogOpen, setCodexCatalogDialogOpen] = useState(false);
|
||||
const [harnessCatalogDialogOpen, setHarnessCatalogDialogOpen] = useState(false);
|
||||
const [launchDirectory, setLaunchDirectory] = useState('');
|
||||
const [launchDirectoryTarget, setLaunchDirectoryTarget] = useState<AgentLaunchTarget | null>(null);
|
||||
const [launchDirectoryError, setLaunchDirectoryError] = useState('');
|
||||
@@ -1326,6 +1328,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
|
||||
claudeModelMappingsDirtyRef.current[selected] = false;
|
||||
}
|
||||
await reloadStatusesAfterAction();
|
||||
if (isDeepSeekHarnessClient) await loadModels('deepseek-harness');
|
||||
setOauthConfigurationDraft(null);
|
||||
setConfigurationNotice(t(result.outcome === 'unchanged' ? 'agents.backup.unchanged' : 'agents.backup.updated'));
|
||||
onConfigurationApplied?.();
|
||||
@@ -1686,6 +1689,13 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
|
||||
</button>
|
||||
) : null;
|
||||
|
||||
const harnessCatalogButton = isDeepSeekHarnessClient ? (
|
||||
<button type="button" className="secondary-button agent-codex-catalog-button"
|
||||
onClick={() => setHarnessCatalogDialogOpen(true)} disabled={busy || configurationWriteBlocked}>
|
||||
<SlidersHorizontal size={16} />{t('agents.harness.button')}
|
||||
</button>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<section className={`page management-page agents-page${embedded ? ' agents-page-embedded' : ''}`}>
|
||||
<header className="management-header">
|
||||
@@ -1796,7 +1806,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
|
||||
|
||||
{selected === 'claude-desktop' && !activeStatus?.claudeDesktopModelMappings ? <p className="agent-inline-message warning">{t('agents.backup.mappingRequired')}</p> : null}
|
||||
<div className="agent-minimal-field">
|
||||
<label htmlFor="embedded-agent-model">{t('agents.useModel')}</label>
|
||||
<label htmlFor="embedded-agent-model">{t(isDeepSeekHarnessClient ? 'agents.harness.defaultModel' : 'agents.useModel')}</label>
|
||||
<AgentModelPicker
|
||||
models={isClaudeModelMappingClient ? claudeMappingModels : models}
|
||||
value={isClaudeModelMappingClient ? claudeModelMappingsDraft.sonnet : selectedModel}
|
||||
@@ -1806,9 +1816,11 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
|
||||
onChange={selectEmbeddedModel}
|
||||
onRefresh={refreshModels}
|
||||
/>
|
||||
{codexCatalogButton}
|
||||
{codexCatalogButton}{harnessCatalogButton}
|
||||
</div>
|
||||
|
||||
{isDeepSeekHarnessClient ? <p className="agent-model-hint">{t('agents.harness.defaultHint')}</p> : null}
|
||||
|
||||
<div className="agent-minimal-actions">
|
||||
<button
|
||||
type="button"
|
||||
@@ -1894,7 +1906,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
|
||||
{!isClaudeModelMappingClient ? (
|
||||
<section className="agent-core-setting-section agent-model-section">
|
||||
<div className="agent-section-heading">
|
||||
<div><strong>{t('agents.useModel')}</strong></div>
|
||||
<div><strong>{t(isDeepSeekHarnessClient ? 'agents.harness.defaultModel' : 'agents.useModel')}</strong></div>
|
||||
</div>
|
||||
<AgentModelPicker
|
||||
models={models}
|
||||
@@ -1914,6 +1926,8 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
|
||||
{modelHint}
|
||||
</span>
|
||||
) : null}
|
||||
{isDeepSeekHarnessClient ? <p className="agent-model-hint">{t('agents.harness.defaultHint')}</p> : null}
|
||||
{harnessCatalogButton}
|
||||
{selected === 'codex' ? (
|
||||
<div className="agent-codex-options">
|
||||
<div className="agent-auth-method">
|
||||
@@ -2140,6 +2154,10 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
|
||||
setConfigurationNotice(t('agents.backup.restored'));
|
||||
}} /> : null}
|
||||
|
||||
{harnessCatalogDialogOpen ? (
|
||||
<DeepSeekHarnessCatalogDialog onClose={() => setHarnessCatalogDialogOpen(false)}
|
||||
onSaved={async () => { await loadModels('deepseek-harness', selectedModel); await loadStatuses(true); }} />
|
||||
) : null}
|
||||
{codexCatalogDialogOpen ? (
|
||||
<CodexModelCatalogDialog
|
||||
onClose={() => setCodexCatalogDialogOpen(false)}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { LoaderCircle, RefreshCw, RotateCcw, Search, Settings2, X } from 'lucide-react';
|
||||
import { useI18n } from '../i18n';
|
||||
import type { MessageKey } from '../i18n/resources';
|
||||
import {
|
||||
harnessDraft, harnessReasoningLevels, harnessSchema, isHarnessRecord, parseHarnessDraft, sameHarnessDraft, updateHarnessDraft,
|
||||
type HarnessDraft, type HarnessEditorSnapshot, type HarnessProfile,
|
||||
} from '../services/deepSeekHarnessCatalog';
|
||||
|
||||
function HarnessFields({ group, draft, defaults = {}, prefix = '', api, onChange }: {
|
||||
group: string; draft: HarnessDraft; defaults?: HarnessProfile; prefix?: string; api: string;
|
||||
onChange: (next: HarnessDraft) => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const label = (name: string) => t(`agents.harness.field.${name}` as MessageKey);
|
||||
return <div className="codex-catalog-form harness-catalog-form">{harnessSchema[group].map(field => {
|
||||
const key = `${prefix}${field.name}`;
|
||||
const value = draft[key] ?? '';
|
||||
const supplied = defaults[field.name] ?? field.default;
|
||||
const automatic = `${t('agents.harness.auto')}${supplied === undefined ? '' : ` (${typeof supplied === 'string' ? supplied : JSON.stringify(supplied)})`}`;
|
||||
const set = (next: string) => onChange(updateHarnessDraft(draft, key, next));
|
||||
if (field.apis && !field.apis.includes(api) && !value) return null;
|
||||
if (field.group) {
|
||||
const customized = Object.keys(draft).some(k => k.startsWith(`${key}.`));
|
||||
return <details className="harness-catalog-group" key={key}>
|
||||
<summary>{label(field.name)}{customized ? ` · ${t('agents.catalog.customized')}` : ''}</summary>
|
||||
<p className="codex-catalog-hint">{t(`agents.harness.hint.${field.name}` as MessageKey)}</p>
|
||||
<HarnessFields group={field.group} draft={draft} defaults={isHarnessRecord(supplied) ? supplied : {}} prefix={`${key}.`} api={api} onChange={onChange} />
|
||||
{customized ? <button type="button" className="secondary-button" onClick={() => onChange(Object.fromEntries(Object.entries(draft).filter(([k]) => !k.startsWith(`${key}.`))))}>{t('agents.harness.resetGroup')}</button> : null}
|
||||
</details>;
|
||||
}
|
||||
if (field.kind === 'reasoning') {
|
||||
let mapping: HarnessProfile = {};
|
||||
try { const parsed: unknown = JSON.parse(value || '{}'); if (isHarnessRecord(parsed)) mapping = parsed; } catch {}
|
||||
return <div className="harness-reasoning wide" key={key}>
|
||||
<label><span>{label(field.name)}</span><select aria-label={label(field.name)} value={!value ? '' : value === 'false' ? 'false' : 'custom'}
|
||||
onChange={e => set(e.currentTarget.value === 'custom' ? JSON.stringify({ off: null, low: 'low', medium: 'medium', high: 'high' }) : e.currentTarget.value)}>
|
||||
<option value="">{automatic}</option><option value="false">{t('agents.harness.reasoningDisabled')}</option><option value="custom">{t('agents.harness.reasoningCustom')}</option>
|
||||
</select></label>
|
||||
{value && value !== 'false' ? <div className="harness-reasoning-levels">{harnessReasoningLevels.map(level => <div key={level}>
|
||||
<label><input type="checkbox" checked={Object.prototype.hasOwnProperty.call(mapping, level)} onChange={e => {
|
||||
const next = { ...mapping }; if (e.currentTarget.checked) next[level] = level === 'off' ? null : level; else delete next[level]; set(JSON.stringify(next));
|
||||
}} />{level}</label>
|
||||
{Object.prototype.hasOwnProperty.call(mapping, level) ? <input aria-label={`${level} reasoning_effort`} value={String(mapping[level] ?? '')} placeholder={level === 'off' ? t('agents.harness.noWireValue') : level}
|
||||
onChange={e => set(JSON.stringify({ ...mapping, [level]: level === 'off' && !e.currentTarget.value ? null : e.currentTarget.value }))} /> : null}
|
||||
</div>)}</div> : null}
|
||||
<small>{t('agents.harness.hint.reasoningEfforts')}</small>
|
||||
</div>;
|
||||
}
|
||||
const jsonField = ['headers', 'kwargs', 'strings'].includes(field.kind);
|
||||
return <label key={key} className={jsonField ? 'wide' : undefined}>
|
||||
<span title={field.name}>{label(field.name)}{Object.prototype.hasOwnProperty.call(draft, key) ? <small className="harness-override">{t('agents.catalog.customized')}</small> : null}</span>
|
||||
{['enum', 'boolean', 'modalities'].includes(field.kind) ? <select aria-label={label(field.name)} value={value} onChange={e => set(e.currentTarget.value)}>
|
||||
<option value="">{automatic}</option>
|
||||
{field.kind === 'boolean' ? <><option value="true">{t('agents.harness.yes')}</option><option value="false">{t('agents.harness.no')}</option></> : null}
|
||||
{field.kind === 'enum' ? field.values!.map(v => <option key={v} value={v}>{v}</option>) : null}
|
||||
{field.kind === 'modalities' ? <>{field.name === 'input' ? <option value="[]">{t('agents.harness.providerInput')}</option> : null}<option value='["text"]'>{t('agents.harness.textOnly')}</option><option value='["text","image"]'>{t('agents.harness.textImage')}</option><option value='["image"]'>{t('agents.catalog.image')}</option></> : null}
|
||||
</select> : jsonField ? <textarea aria-label={label(field.name)} rows={4} spellCheck={false} value={value} placeholder={JSON.stringify(supplied ?? field.example, null, 2) ?? automatic} onChange={e => set(e.currentTarget.value)} />
|
||||
: <input aria-label={label(field.name)} type={['integer', 'number'].includes(field.kind) ? 'number' : 'text'} min={field.min ?? field.exclusiveMin} max={field.max} step={field.kind === 'integer' ? 1 : 'any'} value={value} placeholder={automatic} onChange={e => set(e.currentTarget.value)} />}
|
||||
{['input', 'defaultInput', 'maxTokens', 'defaultMaxTokens', 'api', 'headers'].includes(field.name) ? <small>{t(`agents.harness.hint.${field.name}` as MessageKey)}</small> : null}
|
||||
</label>;
|
||||
})}</div>;
|
||||
}
|
||||
|
||||
export function DeepSeekHarnessCatalogDialog({ onClose, onSaved }: { onClose: () => void; onSaved: () => void | Promise<void> }) {
|
||||
const { t } = useI18n();
|
||||
const [snapshot, setSnapshot] = useState<HarnessEditorSnapshot | null>(null);
|
||||
const [drafts, setDrafts] = useState<Record<string, HarnessDraft>>({});
|
||||
const [provider, setProvider] = useState<HarnessDraft>({});
|
||||
const [selected, setSelected] = useState<string | null>(null);
|
||||
const [search, setSearch] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [notice, setNotice] = useState('');
|
||||
const [confirmation, setConfirmation] = useState<'close' | 'reload' | null>(null);
|
||||
const dialogRef = useRef<HTMLElement>(null);
|
||||
const install = (next: HarnessEditorSnapshot) => {
|
||||
setSnapshot(next);
|
||||
setDrafts(Object.fromEntries(next.models.map(m => [m.id, harnessDraft(m.configuration, 'model')])));
|
||||
setProvider(harnessDraft(next.provider, 'provider'));
|
||||
};
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true); setError(''); setNotice('');
|
||||
try {
|
||||
const next = await invoke<HarnessEditorSnapshot>('get_deepseek_harness_model_catalog_editor');
|
||||
install(next);
|
||||
setSelected(current => current && next.models.some(m => m.id === current) ? current : next.defaultModel ?? next.models[0]?.id ?? null);
|
||||
} catch (e) { setError(String(e)); }
|
||||
finally { setLoading(false); }
|
||||
}, []);
|
||||
useEffect(() => { void load(); }, [load]);
|
||||
const dirty = !!snapshot && (!sameHarnessDraft(provider, harnessDraft(snapshot.provider, 'provider'))
|
||||
|| snapshot.models.some(m => !sameHarnessDraft(drafts[m.id] ?? {}, harnessDraft(m.configuration, 'model'))));
|
||||
const close = () => { if (!saving) { if (dirty) setConfirmation('close'); else onClose(); } };
|
||||
useEffect(() => {
|
||||
const previous = document.activeElement as HTMLElement | null;
|
||||
dialogRef.current?.querySelector<HTMLElement>('button')?.focus();
|
||||
return () => previous?.focus();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const handleKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') { event.stopPropagation(); if (confirmation) setConfirmation(null); else close(); }
|
||||
if (event.key === 'Tab') {
|
||||
const scope = confirmation ? dialogRef.current?.querySelector('[role="alertdialog"]') : dialogRef.current;
|
||||
const targets = [...(scope?.querySelectorAll<HTMLElement>('button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), summary') ?? [])].filter(el => el.getClientRects().length);
|
||||
const first = targets[0], last = targets[targets.length - 1];
|
||||
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus(); }
|
||||
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus(); }
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKey);
|
||||
return () => window.removeEventListener('keydown', handleKey);
|
||||
});
|
||||
const active = snapshot?.models.find(m => m.id === selected);
|
||||
const api = provider.api || 'openai-completions';
|
||||
const changed = () => { setNotice(''); setError(''); };
|
||||
const save = async () => {
|
||||
if (!snapshot || saving || loading) return;
|
||||
let request;
|
||||
try {
|
||||
let configuration;
|
||||
try { configuration = parseHarnessDraft(provider, 'provider', api); }
|
||||
catch (e) { setSelected(null); throw e; }
|
||||
request = { revision: snapshot.revision, provider: configuration, models: snapshot.models.map(m => {
|
||||
try { return { ...m, configuration: parseHarnessDraft(drafts[m.id] ?? {}, 'model', api) }; }
|
||||
catch (e) { setSelected(m.id); throw e; }
|
||||
}) };
|
||||
} catch (e) { setError(t('agents.harness.invalid', { field: String(e).replace(/^Error: /, '') })); return; }
|
||||
setSaving(true); setError(''); setNotice('');
|
||||
try {
|
||||
const next = await invoke<HarnessEditorSnapshot>('save_deepseek_harness_model_catalog_editor', { request });
|
||||
install(next); setNotice(t(next.configured ? 'agents.harness.saved' : 'agents.harness.savedDraft'));
|
||||
await onSaved();
|
||||
} catch (e) { setError(String(e).includes('DSH_MODEL_CATALOG_CHANGED') ? t('agents.harness.changed') : String(e)); }
|
||||
finally { setSaving(false); }
|
||||
};
|
||||
return <div className="config-dialog-backdrop codex-catalog-backdrop" onMouseDown={e => { if (e.target === e.currentTarget) close(); }}>
|
||||
<section ref={dialogRef} className="config-dialog codex-catalog-dialog harness-catalog-dialog" role="dialog" aria-modal="true" aria-labelledby="harness-catalog-title">
|
||||
<header className="config-dialog-heading codex-catalog-heading"><div><h2 id="harness-catalog-title">{t('agents.harness.title')}</h2><p>{t('agents.harness.subtitle')}</p></div><button className="icon-button quiet" onClick={close} disabled={saving} aria-label={t('common.close')}><X size={18} /></button></header>
|
||||
<div className="codex-catalog-body">
|
||||
<aside className="codex-catalog-sidebar">
|
||||
<button className={`secondary-button harness-provider-button${selected === null ? ' active' : ''}`} onClick={() => setSelected(null)}><Settings2 size={16} />{t('agents.harness.provider')}</button>
|
||||
<div className="codex-catalog-search"><Search size={15} /><input aria-label={t('agents.catalog.search')} value={search} placeholder={t('agents.catalog.search')} onChange={e => setSearch(e.currentTarget.value)} /></div>
|
||||
<div className="codex-catalog-models" role="listbox" aria-label={t('agents.harness.title')}>
|
||||
{loading ? <div className="codex-catalog-state"><LoaderCircle className="spin" size={18} />{t('agents.catalog.loading')}</div> : snapshot?.models.filter(m => `${m.id} ${drafts[m.id]?.name ?? m.defaults.name ?? ''}`.toLowerCase().includes(search.toLowerCase())).map(m => <button role="option" aria-selected={selected === m.id} className={selected === m.id ? 'active' : ''} key={m.id} onClick={() => setSelected(m.id)}>
|
||||
<strong>{drafts[m.id]?.name || String(m.defaults.name ?? m.id)}</strong><span>{m.id}</span><small>{t(Object.keys(drafts[m.id] ?? {}).length ? 'agents.catalog.customized' : 'agents.harness.apiSource')}{m.id === snapshot.defaultModel ? ` · ${t('agents.harness.defaultModel')}` : ''}</small>
|
||||
</button>)}
|
||||
{!loading && snapshot?.models.length === 0 ? <div className="codex-catalog-state">{t('agents.catalog.empty')}</div> : null}
|
||||
</div>
|
||||
<button className="secondary-button codex-catalog-reload" disabled={loading || saving} onClick={() => dirty ? setConfirmation('reload') : void load()}><RefreshCw size={15} />{t('agents.catalog.reload')}</button>
|
||||
</aside>
|
||||
<main className="codex-catalog-editor"><fieldset disabled={loading || saving} className="harness-editor-fieldset">
|
||||
{snapshot && (active || selected === null) ? <>
|
||||
<div className="codex-catalog-model-heading"><div><h3>{active ? active.id : t('agents.harness.provider')}</h3><span>{t('agents.harness.inheritHint')}</span></div><button className="secondary-button" onClick={() => { if (active) setDrafts(current => ({ ...current, [active.id]: {} })); else setProvider({}); changed(); }}><RotateCcw size={14} />{t('agents.harness.reset')}</button></div>
|
||||
{active ? <p className="codex-catalog-hint" role="note">{t('agents.harness.apiInput', { value: Array.isArray(active.defaults.input) ? active.defaults.input.join(', ') : t('agents.harness.unknown') })}</p> : <p className="codex-catalog-hint">{t('agents.harness.managedConnection', { url: api === 'anthropic-messages' ? snapshot.baseUrl.replace(/\/v1$/, '') : snapshot.baseUrl })}</p>}
|
||||
<HarnessFields key={active?.id ?? 'provider'} group={active ? 'model' : 'provider'} draft={active ? drafts[active.id] ?? {} : provider} defaults={active?.defaults} api={api} onChange={next => { if (active) setDrafts(current => ({ ...current, [active.id]: next })); else setProvider(next); changed(); }} />
|
||||
</> : null}
|
||||
</fieldset></main>
|
||||
</div>
|
||||
<footer className="codex-catalog-footer"><div aria-live="polite">{error ? <span className="agent-inline-message error" role="alert">{error}</span> : notice ? <span role="status">{notice}</span> : <span>{t(dirty ? 'agents.catalog.unsaved' : 'agents.harness.saveHint')}</span>}</div><div>
|
||||
<button className="secondary-button" disabled={loading || saving || !snapshot} onClick={() => { setDrafts(Object.fromEntries(snapshot!.models.map(m => [m.id, {}]))); setProvider({}); changed(); }}>{t('agents.catalog.resetAll')}</button>
|
||||
<button className="secondary-button" disabled={saving} onClick={close}>{t('common.cancel')}</button><button className="primary-button" disabled={!dirty || loading || saving} onClick={() => void save()}>{saving ? <LoaderCircle size={16} className="spin" /> : null}{t(saving ? 'common.saving' : 'common.save')}</button>
|
||||
</div></footer>
|
||||
{confirmation ? <div className="codex-catalog-confirm"><div role="alertdialog" aria-modal="true" aria-label={t('agents.catalog.unsaved')}><strong>{t('agents.catalog.unsaved')}</strong><span>{t('agents.catalog.discardHint')}</span><div><button autoFocus className="secondary-button" onClick={() => setConfirmation(null)}>{t('agents.catalog.keepEditing')}</button><button className="danger-button" onClick={() => { if (confirmation === 'close') onClose(); else { setConfirmation(null); void load(); } }}>{t('agents.catalog.discard')}</button></div></div></div> : null}
|
||||
</section>
|
||||
</div>;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import schemaSource from './deepSeekHarnessSchema.json';
|
||||
|
||||
export type HarnessProfile = Record<string, unknown>;
|
||||
export type HarnessDraft = Record<string, string>;
|
||||
export type HarnessField = {
|
||||
name: string;
|
||||
kind: string;
|
||||
group?: string;
|
||||
values?: string[];
|
||||
apis?: string[];
|
||||
default?: unknown;
|
||||
example?: unknown;
|
||||
min?: number;
|
||||
max?: number;
|
||||
exclusiveMin?: number;
|
||||
};
|
||||
export const harnessSchema = schemaSource as unknown as Record<string, HarnessField[]>;
|
||||
export const harnessReasoningLevels = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
||||
export type HarnessEditorModel = { id: string; defaults: HarnessProfile; configuration: HarnessProfile };
|
||||
export type HarnessEditorSnapshot = {
|
||||
revision: string;
|
||||
models: HarnessEditorModel[];
|
||||
provider: HarnessProfile;
|
||||
baseUrl: string;
|
||||
defaultModel: string | null;
|
||||
configured: boolean;
|
||||
};
|
||||
export const isHarnessRecord = (value: unknown): value is HarnessProfile =>
|
||||
typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
|
||||
export function harnessDraft(profile: HarnessProfile, group: string, prefix = ''): HarnessDraft {
|
||||
return Object.fromEntries(harnessSchema[group].flatMap(field => {
|
||||
const value = profile[field.name];
|
||||
const key = `${prefix}${field.name}`;
|
||||
if (value === undefined) return [];
|
||||
if (field.group && isHarnessRecord(value)) return Object.entries(harnessDraft(value, field.group, `${key}.`));
|
||||
return [[key, ['string', 'enum'].includes(field.kind) ? String(value) : JSON.stringify(value)]];
|
||||
}));
|
||||
}
|
||||
|
||||
export function updateHarnessDraft(draft: HarnessDraft, key: string, value: string): HarnessDraft {
|
||||
const next = { ...draft };
|
||||
if (!value.length) delete next[key];
|
||||
else return { ...next, [key]: value };
|
||||
return next;
|
||||
}
|
||||
|
||||
export function parseHarnessDraft(draft: HarnessDraft, group: string, api: string, prefix = ''): HarnessProfile {
|
||||
const profile: HarnessProfile = {};
|
||||
for (const field of harnessSchema[group]) {
|
||||
const key = `${prefix}${field.name}`;
|
||||
if (field.group) {
|
||||
const nested = parseHarnessDraft(draft, field.group, api, `${key}.`);
|
||||
if (Object.keys(nested).length) profile[field.name] = nested;
|
||||
continue;
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(draft, key) || !draft[key].trim()) continue;
|
||||
let value: unknown;
|
||||
try {
|
||||
value = ['string', 'enum'].includes(field.kind) ? draft[key].trim() : JSON.parse(draft[key]);
|
||||
} catch { throw new Error(`${key}: JSON`); }
|
||||
if (field.apis && !field.apis.includes(api)) throw new Error(`${key}: ${api}`);
|
||||
let valid = true;
|
||||
if (['integer', 'number'].includes(field.kind)) valid = typeof value === 'number' && Number.isFinite(value)
|
||||
&& Math.abs(value) <= Number.MAX_SAFE_INTEGER
|
||||
&& (field.kind !== 'integer' || Number.isInteger(value))
|
||||
&& (field.min === undefined || value >= field.min)
|
||||
&& (field.max === undefined || value <= field.max)
|
||||
&& (field.exclusiveMin === undefined || value > field.exclusiveMin);
|
||||
if (field.kind === 'enum') valid = field.values!.includes(value as string);
|
||||
if (field.kind === 'boolean') valid = typeof value === 'boolean';
|
||||
if (['modalities', 'strings'].includes(field.kind)) valid = Array.isArray(value) && (value.length > 0 || field.name === 'input')
|
||||
&& value.every(v => typeof v === 'string' && v.trim() && (field.kind !== 'modalities' || ['text', 'image'].includes(v)))
|
||||
&& new Set(value).size === value.length;
|
||||
if (field.kind === 'reasoning') valid = value === false || (isHarnessRecord(value) && Object.keys(value).length > 0
|
||||
&& Object.entries(value).every(([level, wire]) => harnessReasoningLevels.includes(level)
|
||||
&& ((level === 'off' && wire === null) || (typeof wire === 'string' && wire.trim()))));
|
||||
if (field.kind === 'headers') valid = isHarnessRecord(value)
|
||||
&& Object.entries(value).every(([name, v]) => /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)
|
||||
&& typeof v === 'string' && [...v].every(c => c.charCodeAt(0) <= 255) && !/[\r\n\0]/.test(v));
|
||||
if (field.kind === 'kwargs') valid = isHarnessRecord(value) && Object.values(value).every(v => !Array.isArray(v)
|
||||
&& (!isHarnessRecord(v) || (Object.keys(v).every(key => ['$var', 'omitWhenOff'].includes(key))
|
||||
&& ['thinking.enabled', 'thinking.effort', 'thinking.budget'].includes(v.$var as string)
|
||||
&& (v.omitWhenOff === undefined || typeof v.omitWhenOff === 'boolean'))));
|
||||
if (!valid) throw new Error(key);
|
||||
profile[field.name] = value;
|
||||
}
|
||||
if (group === 'retryPolicy' && Object.keys(profile).length && profile.mode === undefined) profile.mode = 'normal';
|
||||
if (group === 'backoff' && Number(profile.initialDelayMs ?? 500) > Number(profile.maxDelayMs ?? 10000)) throw new Error(`${prefix}initialDelayMs > maxDelayMs`);
|
||||
return profile;
|
||||
}
|
||||
|
||||
export function sameHarnessDraft(left: HarnessDraft, right: HarnessDraft): boolean {
|
||||
return Object.keys(left).length === Object.keys(right).length && Object.keys(left).every(key => left[key] === right[key]);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"sources": [
|
||||
"https://deepseek-harness.github.io/deepseek-harness/guide/providers",
|
||||
"https://deepseek-harness.github.io/deepseek-harness/reference/config-catalog#deepseek-ai-dsh-llm-pi-ai",
|
||||
"https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/llm/llm/src/retry-policy.ts"
|
||||
],
|
||||
"model": [
|
||||
{"name":"name","kind":"string"},
|
||||
{"name":"contextWindow","kind":"integer","min":1},
|
||||
{"name":"maxTokens","kind":"integer","min":1},
|
||||
{"name":"input","kind":"modalities"},
|
||||
{"name":"reasoningEfforts","kind":"reasoning","example":{"off":null,"low":"low","high":"high"}},
|
||||
{"name":"compat","kind":"group","group":"compat"}
|
||||
],
|
||||
"provider": [
|
||||
{"name":"displayName","kind":"string","default":"EasyCLIProxyAPI"},
|
||||
{"name":"api","kind":"enum","values":["openai-completions","openai-responses","anthropic-messages"],"default":"openai-completions"},
|
||||
{"name":"defaultContextWindow","kind":"integer","min":1,"default":262144},
|
||||
{"name":"defaultMaxTokens","kind":"integer","min":1,"default":32768},
|
||||
{"name":"defaultInput","kind":"modalities","default":["text"]},
|
||||
{"name":"reasoning","kind":"enum","values":["off","minimal","low","medium","high","xhigh","max"]},
|
||||
{"name":"thinkingBudgets","kind":"group","group":"thinkingBudgets"},
|
||||
{"name":"cacheRetention","kind":"enum","values":["none","short","long"]},
|
||||
{"name":"transport","kind":"enum","values":["sse","websocket","websocket-cached","auto"]},
|
||||
{"name":"timeoutMs","kind":"integer","min":0},
|
||||
{"name":"websocketConnectTimeoutMs","kind":"integer","min":0},
|
||||
{"name":"streamIdleTimeoutMs","kind":"number","exclusiveMin":0,"max":2147483647,"default":300000},
|
||||
{"name":"maxRequestImageBytes","kind":"integer","min":1,"default":20971520},
|
||||
{"name":"requestImagePixelBudget","kind":"integer","min":1,"default":4194304},
|
||||
{"name":"requestImageMaxBytes","kind":"integer","min":1,"default":1048576},
|
||||
{"name":"headers","kind":"headers","example":{"X-Custom-Header":"value"}},
|
||||
{"name":"retryPolicy","kind":"group","group":"retryPolicy"},
|
||||
{"name":"compat","kind":"group","group":"compat"}
|
||||
],
|
||||
"thinkingBudgets": [
|
||||
{"name":"minimal","kind":"number"},
|
||||
{"name":"low","kind":"number"},
|
||||
{"name":"medium","kind":"number"},
|
||||
{"name":"high","kind":"number"}
|
||||
],
|
||||
"retryPolicy": [
|
||||
{"name":"mode","kind":"enum","values":["normal","always"],"default":"normal","required":true},
|
||||
{"name":"maxRetries","kind":"integer","min":0,"default":5},
|
||||
{"name":"retryableCodes","kind":"strings","default":["EMPTY_RESPONSE","RATE_LIMIT","SERVER","TIMEOUT","TRANSPORT"]},
|
||||
{"name":"backoff","kind":"group","group":"backoff"}
|
||||
],
|
||||
"backoff": [
|
||||
{"name":"initialDelayMs","kind":"number","exclusiveMin":0,"max":2147483647,"default":500},
|
||||
{"name":"maxDelayMs","kind":"number","exclusiveMin":0,"max":2147483647,"default":10000},
|
||||
{"name":"jitterRatio","kind":"number","min":0,"max":1,"default":0.1}
|
||||
],
|
||||
"compat": [
|
||||
{"name":"supportsStore","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"supportsDeveloperRole","kind":"boolean","apis":["openai-completions","openai-responses"]},
|
||||
{"name":"supportsReasoningEffort","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"supportsUsageInStreaming","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"supportsFinishReason","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"maxTokensField","kind":"enum","values":["max_completion_tokens","max_tokens"],"apis":["openai-completions"]},
|
||||
{"name":"requiresToolResultName","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"requiresAssistantAfterToolResult","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"requiresThinkingAsText","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"requiresReasoningContentOnAssistantMessages","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"thinkingFormat","kind":"enum","values":["openai","deepseek","openrouter","together","baseten","zai","qwen","chat-template","qwen-chat-template","string-thinking","ant-ling"],"apis":["openai-completions"]},
|
||||
{"name":"chatTemplateKwargs","kind":"kwargs","example":{"enable_thinking":{"$var":"thinking.enabled"}},"apis":["openai-completions"]},
|
||||
{"name":"chatTemplateArgs","kind":"kwargs","example":{"reasoning_effort":{"$var":"thinking.effort","omitWhenOff":true}},"apis":["openai-completions"]},
|
||||
{"name":"supportsThinkingTokenBudget","kind":"boolean","apis":["openai-completions"]},
|
||||
{"name":"thinkingTokenBudgetField","kind":"enum","values":["thinking_token_budget","thinking_budget","thinking_budget_tokens"],"apis":["openai-completions"]},
|
||||
{"name":"vllmPriority","kind":"integer","apis":["openai-completions"]},
|
||||
{"name":"supportsMaxOutputTokens","kind":"boolean","apis":["openai-responses"]},
|
||||
{"name":"supportsStrictMode","kind":"boolean","apis":["openai-completions","openai-responses"]},
|
||||
{"name":"cacheControlFormat","kind":"enum","values":["anthropic"],"apis":["openai-completions"]},
|
||||
{"name":"supportsLongCacheRetention","kind":"boolean","apis":["openai-completions","openai-responses","anthropic-messages"]},
|
||||
{"name":"supportsEagerToolInputStreaming","kind":"boolean","apis":["anthropic-messages"]},
|
||||
{"name":"supportsCacheControlOnTools","kind":"boolean","apis":["anthropic-messages"]},
|
||||
{"name":"supportsTemperature","kind":"boolean","apis":["anthropic-messages"]},
|
||||
{"name":"forceAdaptiveThinking","kind":"boolean","apis":["anthropic-messages"]},
|
||||
{"name":"allowEmptySignature","kind":"boolean","apis":["anthropic-messages"]},
|
||||
{"name":"supportsStrictTools","kind":"boolean","apis":["anthropic-messages"]}
|
||||
]
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export type ModelOption = {
|
||||
alias?: string;
|
||||
isAlias?: boolean;
|
||||
contextWindow?: number;
|
||||
inputModalities?: Array<'text' | 'image'>;
|
||||
thinking?: Record<string, unknown>;
|
||||
};
|
||||
export type ModelProvider = 'gemini' | 'codex' | 'claude' | 'openai';
|
||||
|
||||
@@ -13828,3 +13828,35 @@ body.table-col-resizing * {
|
||||
flex: 0 0 42px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.harness-catalog-dialog .codex-catalog-sidebar { grid-template-rows: auto auto minmax(0,1fr) auto; }
|
||||
.harness-editor-fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
|
||||
.harness-provider-button { flex-shrink: 0; justify-content: flex-start; }
|
||||
.harness-provider-button.active { border-color: var(--theme-bd5e31); }
|
||||
.harness-catalog-form > label > span { display: flex; flex-wrap: wrap; gap: 6px; overflow-wrap: anywhere; }
|
||||
.harness-catalog-form small, .harness-reasoning small { color: var(--theme-716b61); font-size: 12px; line-height: 1.6; }
|
||||
.harness-override { font-weight: 400; }
|
||||
.harness-catalog-group { grid-column: 1 / -1; border-top: 1px solid var(--theme-d5d2cb); padding-top: 14px; min-width: 0; }
|
||||
.harness-catalog-group > summary { cursor: pointer; font-size: 13px; font-weight: 600; padding: 4px 0; }
|
||||
.harness-catalog-group > button { margin-top: 12px; }
|
||||
.harness-reasoning { grid-column: 1 / -1; display: grid; gap: 10px; }
|
||||
.harness-reasoning > label { display: grid; gap: 8px; font-size: 12px; font-weight: 600; }
|
||||
.harness-reasoning-levels { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 8px 16px; }
|
||||
.harness-reasoning-levels > div { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
||||
.harness-reasoning-levels label { display: flex; align-items: center; gap: 6px; flex: 0 0 75px; font-size: 12px; }
|
||||
.harness-reasoning-levels input:not([type=checkbox]) { min-width: 0; width: 100%; }
|
||||
.harness-catalog-form textarea { font-family: var(--font-mono, monospace); }
|
||||
@media (max-width: 640px) { .harness-reasoning-levels { grid-template-columns: 1fr; } }
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.harness-catalog-dialog .codex-catalog-body { grid-template-rows: 210px minmax(0,1fr); }
|
||||
.harness-catalog-dialog .codex-catalog-sidebar { grid-template-rows: auto auto minmax(0,1fr); }
|
||||
.harness-catalog-dialog .harness-provider-button { grid-column: 1 / -1; }
|
||||
.harness-catalog-dialog .codex-catalog-search { grid-column: 1; grid-row: 2; }
|
||||
.harness-catalog-dialog .codex-catalog-reload { grid-column: 2; grid-row: 2; }
|
||||
.harness-catalog-dialog .codex-catalog-models { grid-row: 3; }
|
||||
}
|
||||
|
||||
.harness-catalog-dialog .secondary-button { display: inline-flex; align-items: center; justify-content: center; gap: 7px; }
|
||||
.harness-catalog-form small { font-weight: 400; }
|
||||
.harness-catalog-form { margin-top: 12px; }
|
||||
Reference in new issue
Block a user