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:
lzt404 committed 2026-09-11 16:25:14 +08:00
1 parent 145b585c3f
commit 4d8d549e04
28 files changed
+2047 -20

No files matched your search

+98
View File
@@ -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 モデル一覧に保存できます。',
+98
View File
@@ -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.',
+98
View File
@@ -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 模型列表。',
+21 -3
View File
@@ -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)}
+169
View File
@@ -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>;
}
+95
View File
@@ -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]);
}
+80
View File
@@ -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"]}
]
}
+1
View File
@@ -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';
+32
View File
@@ -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; }