feat: per-provider options (thinking mode, 1M context) and SDK betas integration
- Add ProviderOptionsSection component for per-provider thinking_mode and context_1m - Store options in options_json column (DB providers) or settings table (env provider) - Add /api/providers/options GET/PUT route - Fix SDK integration: use `betas` (not `betaHeaders`) for context-1m-2025-08-07 - ChatView and /chat page always reset thinkingMode on provider change (isolation fix) - Auto-correct model when switching providers if model not in new provider's list - Add i18n keys for provider options in both en.ts and zh.ts - Update test fixtures with options_json field - Remove unused lastAssistantText variable in claude-client.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e694d75f10
commit
cc3cae6868
14 files changed
+282
-84
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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<strin
|
||||
agent,
|
||||
enableFileCheckpointing,
|
||||
autoTrigger,
|
||||
context1m,
|
||||
} = options;
|
||||
|
||||
return new ReadableStream<string>({
|
||||
@@ -516,6 +526,12 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
|
||||
if (enableFileCheckpointing) {
|
||||
queryOptions.enableFileCheckpointing = true;
|
||||
}
|
||||
if (context1m) {
|
||||
queryOptions.betas = [
|
||||
...(queryOptions.betas || []),
|
||||
'context-1m-2025-08-07',
|
||||
];
|
||||
}
|
||||
|
||||
// Resume session if we have an SDK session ID from a previous conversation turn.
|
||||
// Pre-check: verify working_directory exists before attempting resume.
|
||||
@@ -767,7 +783,6 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
|
||||
console.warn('[claude-client] Capability capture failed:', err);
|
||||
});
|
||||
|
||||
let lastAssistantText = '';
|
||||
let tokenUsage: TokenUsage | null = null;
|
||||
// Track pending TodoWrite tool_use_ids so we can sync after successful execution
|
||||
const pendingTodoWrites = new Map<string, Array<{ content: string; status: string; activeForm?: string }>>();
|
||||
@@ -781,11 +796,7 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
|
||||
case 'assistant': {
|
||||
const assistantMsg = message as SDKAssistantMessage;
|
||||
// Text deltas are handled by stream_event for real-time streaming.
|
||||
// Only track lastAssistantText here and process tool_use blocks.
|
||||
const text = extractTextFromMessage(assistantMsg);
|
||||
if (text) {
|
||||
lastAssistantText = text;
|
||||
}
|
||||
// Here we only process tool_use blocks.
|
||||
|
||||
// Check for tool use blocks
|
||||
for (const block of assistantMsg.message.content) {
|
||||
|
||||
@@ -250,12 +250,16 @@ export const VENDOR_PRESETS: VendorPreset[] = [
|
||||
description: 'MiniMax Code Plan — China region',
|
||||
descriptionZh: 'MiniMax 编程套餐 — 中国区',
|
||||
protocol: 'anthropic',
|
||||
authStyle: 'api_key',
|
||||
baseUrl: 'https://api.minimaxi.com/anthropic',
|
||||
authStyle: 'auth_token',
|
||||
baseUrl: 'https://api.minimaxi.com/anthropic/v1',
|
||||
defaultEnvOverrides: {
|
||||
API_TIMEOUT_MS: '3000000',
|
||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
|
||||
ANTHROPIC_API_KEY: '',
|
||||
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',
|
||||
},
|
||||
defaultModels: [
|
||||
{ modelId: 'sonnet', displayName: 'MiniMax-M2.5', role: 'default' },
|
||||
@@ -272,12 +276,16 @@ export const VENDOR_PRESETS: VendorPreset[] = [
|
||||
description: 'MiniMax Code Plan — Global region',
|
||||
descriptionZh: 'MiniMax 编程套餐 — 国际区',
|
||||
protocol: 'anthropic',
|
||||
authStyle: 'api_key',
|
||||
authStyle: 'auth_token',
|
||||
baseUrl: 'https://api.minimax.io/anthropic',
|
||||
defaultEnvOverrides: {
|
||||
API_TIMEOUT_MS: '3000000',
|
||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
|
||||
ANTHROPIC_API_KEY: '',
|
||||
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',
|
||||
},
|
||||
defaultModels: [
|
||||
{ modelId: 'sonnet', displayName: 'MiniMax-M2.5', role: 'default' },
|
||||
@@ -495,6 +503,18 @@ export function findPresetForLegacy(baseUrl: string, providerType: string): Vend
|
||||
if (baseUrl) {
|
||||
const match = VENDOR_PRESETS.find(p => 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
|
||||
|
||||
Reference in new issue
Block a user