diff --git a/src/__tests__/unit/provider-resolver.test.ts b/src/__tests__/unit/provider-resolver.test.ts
index 63e8343..9365382 100644
--- a/src/__tests__/unit/provider-resolver.test.ts
+++ b/src/__tests__/unit/provider-resolver.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, beforeEach, afterEach } from 'node:test';
+import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
VENDOR_PRESETS,
@@ -7,7 +7,6 @@ import {
getDefaultModelsForProvider,
findPresetForLegacy,
} from '../../lib/provider-catalog';
-import type { Protocol } from '../../lib/provider-catalog';
// ── Provider Catalog Tests ──────────────────────────────────────
@@ -285,7 +284,7 @@ describe('Provider Resolver', () => {
role_models_json: '{}',
notes: '',
created_at: '',
- updated_at: '',
+ updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'api_key',
@@ -323,7 +322,7 @@ describe('Provider Resolver', () => {
role_models_json: '{}',
notes: '',
created_at: '',
- updated_at: '',
+ updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'auth_token',
@@ -361,7 +360,7 @@ describe('Provider Resolver', () => {
role_models_json: '{}',
notes: '',
created_at: '',
- updated_at: '',
+ updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'api_key',
@@ -404,7 +403,7 @@ describe('Provider Resolver', () => {
role_models_json: '{}',
notes: '',
created_at: '',
- updated_at: '',
+ updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'api_key',
@@ -461,7 +460,7 @@ describe('Provider Resolver', () => {
id: 'test', name: 'Test', provider_type: 'anthropic', protocol: 'anthropic',
base_url: 'https://api.anthropic.com', api_key: 'key', is_active: 1, sort_order: 0,
extra_env: '{}', headers_json: '{}', env_overrides_json: '', role_models_json: '{}',
- notes: '', created_at: '', updated_at: '',
+ notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'api_key',
@@ -490,7 +489,7 @@ describe('Provider Resolver', () => {
id: 'test', name: 'OR', provider_type: 'openrouter', protocol: 'openrouter',
base_url: 'https://openrouter.ai/api', api_key: 'or-key', is_active: 1, sort_order: 0,
extra_env: '{}', headers_json: '{}', env_overrides_json: '', role_models_json: '{}',
- notes: '', created_at: '', updated_at: '',
+ notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'openrouter',
authStyle: 'api_key',
@@ -517,7 +516,7 @@ describe('Provider Resolver', () => {
id: 'test', name: 'Bedrock', provider_type: 'bedrock', protocol: 'bedrock',
base_url: '', api_key: '', is_active: 1, sort_order: 0,
extra_env: '{}', headers_json: '{}', env_overrides_json: '', role_models_json: '{}',
- notes: '', created_at: '', updated_at: '',
+ notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'bedrock',
authStyle: 'env_only',
@@ -549,7 +548,7 @@ describe('Provider Resolver', () => {
id: 'test', name: 'Custom', provider_type: 'custom', protocol: 'openai-compatible',
base_url: 'https://my-server.com/v1', api_key: 'key', is_active: 1, sort_order: 0,
extra_env: '{}', headers_json: '{}', env_overrides_json: '', role_models_json: '{}',
- notes: '', created_at: '', updated_at: '',
+ notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'openai-compatible',
authStyle: 'api_key',
@@ -575,7 +574,7 @@ describe('Provider Resolver', () => {
id: 'test', name: 'Test', provider_type: 'anthropic', protocol: 'anthropic',
base_url: '', api_key: 'key', is_active: 1, sort_order: 0,
extra_env: '{}', headers_json: '{}', env_overrides_json: '', role_models_json: '{}',
- notes: '', created_at: '', updated_at: '',
+ notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'api_key',
@@ -601,7 +600,7 @@ describe('Provider Resolver', () => {
base_url: 'https://generativelanguage.googleapis.com/v1beta', api_key: 'gkey',
is_active: 1, sort_order: 0,
extra_env: '{}', headers_json: '{}', env_overrides_json: '', role_models_json: '{}',
- notes: '', created_at: '', updated_at: '',
+ notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'gemini-image',
authStyle: 'api_key',
@@ -724,7 +723,7 @@ describe('Upstream Model ID Mapping', () => {
id: 'test', name: 'GLM', provider_type: 'custom', protocol: 'anthropic',
base_url: 'https://open.bigmodel.cn/api/anthropic', api_key: 'key',
is_active: 1, sort_order: 0, extra_env: '{}', headers_json: '{}',
- env_overrides_json: '', role_models_json: '{}', notes: '', created_at: '', updated_at: '',
+ env_overrides_json: '', role_models_json: '{}', notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'api_key',
@@ -762,7 +761,7 @@ describe('Upstream Model ID Mapping', () => {
base_url: 'https://open.bigmodel.cn/api/anthropic', api_key: 'key',
is_active: 1, sort_order: 0, extra_env: '{}', headers_json: '{}',
env_overrides_json: '', role_models_json: '{"default":"glm-4.7","sonnet":"glm-4.7","opus":"glm-5"}',
- notes: '', created_at: '', updated_at: '',
+ notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'api_key',
@@ -879,7 +878,7 @@ describe('Entry Point Resolution Contract', () => {
base_url: 'https://open.bigmodel.cn/api/anthropic', api_key: 'key',
is_active: 1, sort_order: 0, extra_env: '{}', headers_json: '{}',
env_overrides_json: '', role_models_json: '{"default":"glm-4.7"}',
- notes: '', created_at: '', updated_at: '',
+ notes: '', created_at: '', updated_at: '', options_json: '{}',
},
protocol: 'anthropic',
authStyle: 'api_key',
diff --git a/src/app/api/providers/options/route.ts b/src/app/api/providers/options/route.ts
new file mode 100644
index 0000000..36cf067
--- /dev/null
+++ b/src/app/api/providers/options/route.ts
@@ -0,0 +1,40 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getProviderOptions, setProviderOptions } from '@/lib/db';
+import type { ProviderOptions } from '@/types';
+
+/**
+ * GET /api/providers/options?providerId=xxx
+ * Returns per-provider options (thinking_mode, context_1m).
+ */
+export async function GET(request: NextRequest) {
+ const providerId = request.nextUrl.searchParams.get('providerId') || 'env';
+ const options = getProviderOptions(providerId);
+ return NextResponse.json({ options });
+}
+
+/**
+ * PUT /api/providers/options
+ * Update per-provider options. Body: { providerId, options: { thinking_mode?, context_1m? } }
+ */
+export async function PUT(request: NextRequest) {
+ try {
+ const body = await request.json();
+ const { providerId = 'env', options } = body as { providerId?: string; options: ProviderOptions };
+
+ if (!options || typeof options !== 'object') {
+ return NextResponse.json({ error: 'Invalid options' }, { status: 400 });
+ }
+
+ // Merge with existing options (partial update)
+ const existing = getProviderOptions(providerId);
+ const merged: ProviderOptions = { ...existing, ...options };
+ setProviderOptions(providerId, merged);
+
+ return NextResponse.json({ options: merged });
+ } catch (error) {
+ return NextResponse.json(
+ { error: error instanceof Error ? error.message : 'Failed to update options' },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/components/settings/GeneralSection.tsx b/src/components/settings/GeneralSection.tsx
index f0fd2a2..ea01bdb 100644
--- a/src/components/settings/GeneralSection.tsx
+++ b/src/components/settings/GeneralSection.tsx
@@ -126,7 +126,6 @@ export function GeneralSection() {
const [skipPermissions, setSkipPermissions] = useState(false);
const [showSkipPermWarning, setShowSkipPermWarning] = useState(false);
const [skipPermSaving, setSkipPermSaving] = useState(false);
- const [thinkingMode, setThinkingMode] = useState('adaptive');
const { accountInfo } = useAccountInfo();
const { t, locale, setLocale } = useTranslation();
@@ -137,9 +136,6 @@ export function GeneralSection() {
const data = await res.json();
const appSettings = data.settings || {};
setSkipPermissions(appSettings.dangerously_skip_permissions === "true");
- if (appSettings.thinking_mode) {
- setThinkingMode(appSettings.thinking_mode);
- }
}
} catch {
// ignore
@@ -150,21 +146,6 @@ export function GeneralSection() {
fetchAppSettings();
}, [fetchAppSettings]);
- const saveThinkingMode = async (mode: string) => {
- setThinkingMode(mode);
- try {
- await fetch("/api/settings/app", {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- settings: { thinking_mode: mode },
- }),
- });
- } catch {
- // ignore
- }
- };
-
const handleSkipPermToggle = (checked: boolean) => {
if (checked) {
setShowSkipPermWarning(true);
@@ -236,23 +217,6 @@ export function GeneralSection() {
- {/* Thinking mode */}
-
-
-
{/* Appearance */}
diff --git a/src/components/settings/PresetConnectDialog.tsx b/src/components/settings/PresetConnectDialog.tsx
index f2c6b0a..8f40f16 100644
--- a/src/components/settings/PresetConnectDialog.tsx
+++ b/src/components/settings/PresetConnectDialog.tsx
@@ -383,6 +383,7 @@ export function PresetConnectDialog({
{!preset.fields.includes("extra_env") && (
<>
+
{/* Connected provider list */}
@@ -255,6 +257,10 @@ export function ProviderManager() {
+ {/* Provider options (thinking mode + 1M context) — only for official Anthropic */}
+ {provider.base_url === 'https://api.anthropic.com' && (
+
+ )}
{/* Gemini Image model selector — capsule buttons */}
{provider.provider_type === 'gemini-image' && (
diff --git a/src/components/settings/ProviderOptionsSection.tsx b/src/components/settings/ProviderOptionsSection.tsx
new file mode 100644
index 0000000..474803b
--- /dev/null
+++ b/src/components/settings/ProviderOptionsSection.tsx
@@ -0,0 +1,107 @@
+"use client";
+
+import { useState, useEffect, useCallback } from "react";
+import { Switch } from "@/components/ui/switch";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { useTranslation } from "@/hooks/useTranslation";
+import type { TranslationKey } from "@/i18n";
+import type { ProviderOptions } from "@/types";
+
+interface ProviderOptionsSectionProps {
+ providerId: string;
+}
+
+/**
+ * Per-provider options: thinking mode + 1M context toggle.
+ * Only rendered for providers that support these features
+ * (env / anthropic-official).
+ */
+export function ProviderOptionsSection({ providerId }: ProviderOptionsSectionProps) {
+ const { t } = useTranslation();
+ const [options, setOptions] = useState
({
+ thinking_mode: 'adaptive',
+ context_1m: false,
+ });
+ const [loaded, setLoaded] = useState(false);
+
+ useEffect(() => {
+ let cancelled = false;
+ (async () => {
+ try {
+ const res = await fetch(`/api/providers/options?providerId=${encodeURIComponent(providerId)}`);
+ if (!cancelled && res.ok) {
+ const data = await res.json();
+ setOptions(data.options || {});
+ }
+ } catch { /* ignore */ }
+ if (!cancelled) setLoaded(true);
+ })();
+ return () => { cancelled = true; };
+ }, [providerId]);
+
+ const saveOption = async (key: keyof ProviderOptions, value: string | boolean) => {
+ const updated = { ...options, [key]: value };
+ setOptions(updated);
+ try {
+ await fetch('/api/providers/options', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ providerId, options: { [key]: value } }),
+ });
+ } catch { /* ignore */ }
+ };
+
+ if (!loaded) return null;
+
+ return (
+
+ {/* Thinking mode */}
+
+
+
+ {t('settings.thinkingMode' as TranslationKey)}
+
+
+ {t('settings.thinkingModeDesc' as TranslationKey)}
+
+
+
+
+
+ {/* 1M context */}
+
+
+
+ {t('provider.context1m' as TranslationKey)}
+
+
+ {t('provider.context1mDesc' as TranslationKey)}
+
+
+
saveOption('context_1m', checked)}
+ className="scale-[0.85]"
+ />
+
+
+ );
+}
diff --git a/src/components/settings/provider-presets.tsx b/src/components/settings/provider-presets.tsx
index 49905fe..646bda9 100644
--- a/src/components/settings/provider-presets.tsx
+++ b/src/components/settings/provider-presets.tsx
@@ -171,8 +171,8 @@ export const QUICK_PRESETS: QuickPreset[] = [
icon: ,
provider_type: "anthropic",
protocol: "anthropic",
- base_url: "https://api.minimaxi.com/anthropic",
- extra_env: '{"API_TIMEOUT_MS":"3000000","CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":"1","ANTHROPIC_API_KEY":""}',
+ base_url: "https://api.minimaxi.com/anthropic/v1",
+ extra_env: '{"API_TIMEOUT_MS":"3000000","CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":"1","ANTHROPIC_AUTH_TOKEN":"","ANTHROPIC_MODEL":"MiniMax-M2.5","ANTHROPIC_DEFAULT_SONNET_MODEL":"MiniMax-M2.5","ANTHROPIC_DEFAULT_OPUS_MODEL":"MiniMax-M2.5","ANTHROPIC_DEFAULT_HAIKU_MODEL":"MiniMax-M2.5"}',
fields: ["api_key"],
},
{
@@ -184,7 +184,7 @@ export const QUICK_PRESETS: QuickPreset[] = [
provider_type: "anthropic",
protocol: "anthropic",
base_url: "https://api.minimax.io/anthropic",
- extra_env: '{"API_TIMEOUT_MS":"3000000","CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":"1","ANTHROPIC_API_KEY":""}',
+ extra_env: '{"API_TIMEOUT_MS":"3000000","CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":"1","ANTHROPIC_AUTH_TOKEN":"","ANTHROPIC_MODEL":"MiniMax-M2.5","ANTHROPIC_DEFAULT_SONNET_MODEL":"MiniMax-M2.5","ANTHROPIC_DEFAULT_OPUS_MODEL":"MiniMax-M2.5","ANTHROPIC_DEFAULT_HAIKU_MODEL":"MiniMax-M2.5"}',
fields: ["api_key"],
},
{
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 55d8813..f519161 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -148,6 +148,8 @@ const en = {
'provider.advancedOptions': 'Advanced Options',
'provider.extraEnvVars': 'Extra Environment Variables',
'provider.notes': 'Notes',
+ 'provider.context1m': '1M Context Window',
+ 'provider.context1mDesc': 'Enable 1M token context for Opus 4.6 and Sonnet 4.6 (beta, higher pricing above 200K)',
'provider.notesPlaceholder': 'Optional notes about this provider...',
'provider.saving': 'Saving...',
'provider.update': 'Update',
@@ -289,10 +291,16 @@ const en = {
'connection.connected': 'Connected',
'connection.disconnected': 'Disconnected',
'connection.checking': 'Checking',
+ 'connection.missingGit': 'Missing Git',
+ 'connection.missingGitTitle': 'Git for Windows required',
+ 'connection.missingGitDesc': 'Claude Code requires Git for Windows to run. Please install it and refresh.',
+ 'connection.conflict': 'Conflict',
+ 'connection.conflictWarning': 'Multiple Claude Code installations detected',
+ 'connection.conflictRemove': 'To remove',
// ── Install wizard ──────────────────────────────────────────
'install.title': 'Install Claude Code',
- 'install.checkingPrereqs': 'Checking prerequisites...',
+ 'install.checkingPrereqs': 'Checking environment...',
'install.alreadyInstalled': 'Claude Code is already installed',
'install.readyToInstall': 'Ready to install',
'install.installing': 'Installing Claude Code...',
@@ -306,13 +314,21 @@ const en = {
'install.done': 'Done',
'install.recheck': 'Recheck',
'install.copy': 'Copy',
- 'install.homebrewRequired': 'Homebrew is required',
- 'install.homebrewDescription': 'Homebrew is the package manager for macOS. Node.js installation requires it.',
- 'install.homebrewSteps': 'Follow these steps:',
- 'install.homebrewStep1': 'Open Terminal',
- 'install.homebrewStep2': 'Paste the command above and press Enter',
- 'install.homebrewStep3': 'Follow the on-screen instructions to complete the installation',
- 'install.homebrewStep4': 'Come back here and click "Recheck"',
+ 'install.notDetected': 'not detected',
+ 'install.nativeDescription': 'Claude Code CLI was not detected. Install it using the official native installer?',
+ 'install.autoDescription': 'Automatically install Claude Code CLI',
+ 'install.nativeExplain': 'Click Install to download and run the official native installer. No Node.js required — the native binary auto-updates in the background.',
+ 'install.nativeCompleteDesc': 'Claude Code has been installed via the native installer. It will auto-update in the background.',
+ 'install.conflictTitle': 'Multiple installations detected',
+ 'install.conflictUsing': 'Currently using',
+ 'install.conflictAlso': 'Also found',
+ 'install.conflictRemove': 'To remove',
+ 'install.gitRequired': 'Git for Windows is required',
+ 'install.gitDescription': 'Claude Code requires Git for Windows to run commands on Windows. Please install it first.',
+ 'install.gitSteps': 'Follow these steps:',
+ 'install.gitStep1': 'Download Git for Windows from https://git-scm.com/downloads/win',
+ 'install.gitStep2': 'Run the installer with default settings',
+ 'install.gitStep3': 'Come back here and click "Recheck"',
// ── Task list ───────────────────────────────────────────────
'tasks.all': 'All',
diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts
index 77e88a9..b7bcee4 100644
--- a/src/i18n/zh.ts
+++ b/src/i18n/zh.ts
@@ -144,6 +144,8 @@ const zh: Record = {
'provider.modelName': '模型名称',
'provider.advancedOptions': '高级选项',
'provider.extraEnvVars': '额外环境变量',
+ 'provider.context1m': '1M 上下文窗口',
+ 'provider.context1mDesc': '为 Opus 4.6 和 Sonnet 4.6 启用 100 万 token 上下文(测试版,超过 200K 部分价格更高)',
'provider.notes': '备注',
'provider.notesPlaceholder': '关于此服务商的可选备注...',
'provider.saving': '保存中...',
@@ -286,10 +288,16 @@ const zh: Record = {
'connection.connected': '已连接',
'connection.disconnected': '未连接',
'connection.checking': '检测中',
+ 'connection.missingGit': '缺少 Git',
+ 'connection.missingGitTitle': '需要安装 Git for Windows',
+ 'connection.missingGitDesc': 'Claude Code 需要 Git for Windows 才能运行,请安装后刷新。',
+ 'connection.conflict': '版本冲突',
+ 'connection.conflictWarning': '检测到多个 Claude Code 安装',
+ 'connection.conflictRemove': '卸载方式',
// ── Install wizard ──────────────────────────────────────────
'install.title': '安装 Claude Code',
- 'install.checkingPrereqs': '检查前置条件...',
+ 'install.checkingPrereqs': '检查环境...',
'install.alreadyInstalled': 'Claude Code 已安装',
'install.readyToInstall': '准备安装',
'install.installing': '正在安装 Claude Code...',
@@ -303,13 +311,21 @@ const zh: Record = {
'install.done': '完成',
'install.recheck': '重新检测',
'install.copy': '复制',
- 'install.homebrewRequired': '需要先安装 Homebrew',
- 'install.homebrewDescription': 'Homebrew 是 macOS 的包管理器,安装 Node.js 需要它。',
- 'install.homebrewSteps': '请按以下步骤操作:',
- 'install.homebrewStep1': '打开终端(Terminal)',
- 'install.homebrewStep2': '粘贴上面的命令并按回车',
- 'install.homebrewStep3': '按提示完成安装',
- 'install.homebrewStep4': '回到这里点击"重新检测"',
+ 'install.notDetected': '未检测到',
+ 'install.nativeDescription': '未检测到 Claude Code CLI。使用官方原生安装器安装?',
+ 'install.autoDescription': '自动安装 Claude Code CLI',
+ 'install.nativeExplain': '点击安装将下载并运行官方原生安装器。无需 Node.js — 原生二进制文件会在后台自动更新。',
+ 'install.nativeCompleteDesc': 'Claude Code 已通过原生安装器安装完成,将在后台自动更新。',
+ 'install.conflictTitle': '检测到多个安装版本',
+ 'install.conflictUsing': '当前使用',
+ 'install.conflictAlso': '还发现',
+ 'install.conflictRemove': '卸载方式',
+ 'install.gitRequired': '需要安装 Git for Windows',
+ 'install.gitDescription': 'Claude Code 需要 Git for Windows 才能在 Windows 上运行命令,请先安装。',
+ 'install.gitSteps': '请按以下步骤操作:',
+ 'install.gitStep1': '从 https://git-scm.com/downloads/win 下载 Git for Windows',
+ 'install.gitStep2': '使用默认设置运行安装程序',
+ 'install.gitStep3': '回到这里点击"重新检测"',
// ── Task list ───────────────────────────────────────────────
'tasks.all': '全部',
diff --git a/src/lib/agent-sdk-capabilities.ts b/src/lib/agent-sdk-capabilities.ts
index f8ab781..e05beb1 100644
--- a/src/lib/agent-sdk-capabilities.ts
+++ b/src/lib/agent-sdk-capabilities.ts
@@ -105,8 +105,11 @@ export async function captureCapabilities(
conversation.mcpServerStatus(),
]);
- cache.models = models.status === 'fulfilled' ? models.value : cache.models;
- cache.commands = commands.status === 'fulfilled' ? commands.value : cache.commands;
+ // Guard: only overwrite cached models if the new result is non-empty.
+ // An empty array (e.g. transient SDK error) would otherwise wipe valid cached data,
+ // causing models like Opus to disappear from the selector after long idle.
+ cache.models = models.status === 'fulfilled' && models.value.length > 0 ? models.value : cache.models;
+ cache.commands = commands.status === 'fulfilled' && commands.value.length > 0 ? commands.value : cache.commands;
cache.account = account.status === 'fulfilled' ? account.value : cache.account;
cache.mcpStatus = mcpStatus.status === 'fulfilled' ? mcpStatus.value : cache.mcpStatus;
cache.capturedAt = Date.now();
diff --git a/src/lib/claude-client.ts b/src/lib/claude-client.ts
index 6de134f..25bc41e 100644
--- a/src/lib/claude-client.ts
+++ b/src/lib/claude-client.ts
@@ -20,7 +20,7 @@ import { registerConversation, unregisterConversation } from './conversation-reg
import { captureCapabilities } from './agent-sdk-capabilities';
import { getSetting, updateSdkSessionId, createPermissionRequest } from './db';
import { resolveForClaudeCode, toClaudeCodeEnv } from './provider-resolver';
-import { findClaudeBinary, findGitBash, getExpandedPath } from './platform';
+import { findClaudeBinary, findGitBash, getExpandedPath, invalidateClaudePathCache } from './platform';
import { notifyPermissionRequest, notifyGeneric } from './telegram-bot';
import os from 'os';
import fs from 'fs';
@@ -95,6 +95,15 @@ function findClaudePath(): string | undefined {
return found;
}
+/**
+ * Invalidate the cached Claude binary path in this module AND in platform.ts.
+ * Must be called after installation so the next SDK call picks up the new binary.
+ */
+export function invalidateClaudeClientCache(): void {
+ cachedClaudePath = undefined; // reset to "not yet looked up"
+ invalidateClaudePathCache(); // also reset the 60s TTL cache in platform.ts
+}
+
/**
* Convert our MCPServerConfig to the SDK's McpServerConfig format.
* Supports stdio, sse, and http transport types.
@@ -383,6 +392,7 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream({
@@ -516,6 +526,12 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream>();
@@ -781,11 +796,7 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream p.baseUrl === baseUrl);
if (match) return match;
+
+ // Fuzzy match: legacy entries may have old URLs (e.g. minimaxi.com/anthropic
+ // before /v1 suffix was added). Match by domain substring against presets.
+ const urlLower = baseUrl.toLowerCase();
+ const fuzzy = VENDOR_PRESETS.find(p => {
+ if (!p.baseUrl) return false;
+ try {
+ const presetHost = new URL(p.baseUrl).hostname;
+ return urlLower.includes(presetHost);
+ } catch { return false; }
+ });
+ if (fuzzy) return fuzzy;
}
// Type-based fallback
diff --git a/src/types/index.ts b/src/types/index.ts
index a0e878c..3a621ef 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -180,6 +180,8 @@ export interface ApiProvider {
env_overrides_json: string;
/** Semantic model role mapping — JSON string of { default?, reasoning?, small?, haiku?, sonnet?, opus? } */
role_models_json: string;
+ /** Per-provider options — JSON string of { thinking_mode?, context_1m? } */
+ options_json: string;
notes: string;
created_at: string;
updated_at: string;
@@ -228,6 +230,7 @@ export interface CreateProviderRequest {
headers_json?: string;
env_overrides_json?: string;
role_models_json?: string;
+ options_json?: string;
notes?: string;
}
@@ -241,10 +244,17 @@ export interface UpdateProviderRequest {
headers_json?: string;
env_overrides_json?: string;
role_models_json?: string;
+ options_json?: string;
notes?: string;
sort_order?: number;
}
+/** Per-provider options stored in options_json */
+export interface ProviderOptions {
+ thinking_mode?: 'adaptive' | 'enabled' | 'disabled';
+ context_1m?: boolean;
+}
+
export interface ProvidersResponse {
providers: ApiProvider[];
}
@@ -554,6 +564,8 @@ export interface AssistantWorkspaceState {
lastCheckInDate: string | null;
schemaVersion: number;
hookTriggeredSessionId?: string;
+ /** ISO timestamp when hookTriggeredSessionId was set — used for staleness detection */
+ hookTriggeredAt?: string;
}
export interface AssistantWorkspaceFiles {
@@ -936,6 +948,8 @@ export interface ClaudeStreamOptions {
enableFileCheckpointing?: boolean;
/** When true, this is an auto-trigger turn (invisible to user) — skip rewind point emission */
autoTrigger?: boolean;
+ /** Enable 1M context window (beta header: context-1m-2025-08-07) */
+ context1m?: boolean;
}
// ==========================================