diff --git a/.gitignore b/.gitignore index 5529773cd..88a842855 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ temp*build*/ /plugins/agc-unity-editor/dotnet/native-build/ /apps/ai-game-creator-shell/logs/ /apps/ai-game-creator-shell/src-tauri/resources/node-runtime/ +/apps/ai-game-creator-shell/src-tauri/resources/claude-agent/ /apps/ai-game-creator-shell/src-tauri/resources/node-runtime-staging-*/ /apps/ai-game-creator-shell/.llm-drafts/ /apps/ai-game-creator-shell/game-creator.config.local.json diff --git a/apps/admin-web/src/api/adminApiTypes.ts b/apps/admin-web/src/api/adminApiTypes.ts index 01e99ed10..429ce7c5a 100644 --- a/apps/admin-web/src/api/adminApiTypes.ts +++ b/apps/admin-web/src/api/adminApiTypes.ts @@ -1075,11 +1075,14 @@ export interface AdminRechargeRefundActionResponse { export interface AdminWalletRestrictionResponse { wallet: AdminProfileWalletPayload; } +export type AdminAgcAgentMode = 'codex' | 'cc'; + export interface AdminAgcModel { id: string; alias: string; modelId: string; enabled: boolean; + agentMode: AdminAgcAgentMode; } export interface AdminAgcModelCatalog { diff --git a/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx b/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx index bb88b9de6..77b9262bb 100644 --- a/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx +++ b/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx @@ -23,14 +23,23 @@ vi.mock('../components/useAdminWriteConfirm', () => ({ confirmDialog: null, }), })); -afterEach(cleanup); +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); test('edits alias and upstream model without changing the stable identifier or revision', async () => { const catalog = { revision: 3, defaultModelId: 'quality', models: [ - { id: 'quality', alias: '高质量', modelId: 'gpt-6-astra', enabled: true }, + { + id: 'quality', + alias: '高质量', + modelId: 'gpt-6-astra', + enabled: true, + agentMode: 'codex' as const, + }, ], }; vi.mocked(getAgcModelCatalog).mockResolvedValue(catalog); @@ -54,3 +63,44 @@ test('edits alias and upstream model without changing the stable identifier or r expect(screen.getAllByText('已保存').length).toBeGreaterThanOrEqual(2); }); }); + +test('new models default to Codex and save the selected Agent mode', async () => { + const catalog = { + revision: 3, + defaultModelId: 'quality', + models: [ + { + id: 'quality', + alias: '高质量', + modelId: 'gpt-6-astra', + enabled: true, + agentMode: 'codex' as const, + }, + ], + }; + vi.mocked(getAgcModelCatalog).mockResolvedValue(catalog); + vi.mocked(saveAgcModelCatalog).mockImplementation(async (_, input) => ({ + ...input, + revision: 4, + })); + render(); + await screen.findByDisplayValue('gpt-6-astra'); + + fireEvent.click(screen.getByRole('button', { name: '添加模型' })); + const agentMode = screen.getByLabelText('模型 2 Agent 模式'); + expect((agentMode as HTMLSelectElement).value).toBe('codex'); + fireEvent.change(agentMode, { target: { value: 'cc' } }); + fireEvent.click(screen.getByRole('button', { name: '保存' })); + + await waitFor(() => { + const calls = vi.mocked(saveAgcModelCatalog).mock.calls; + const payload = calls[calls.length - 1]?.[1]; + expect(payload?.models).toHaveLength(2); + expect(payload?.models[1]).toMatchObject({ + alias: '', + modelId: '', + enabled: true, + agentMode: 'cc', + }); + }); +}); diff --git a/apps/admin-web/src/pages/AdminAgcModelsPage.tsx b/apps/admin-web/src/pages/AdminAgcModelsPage.tsx index 0697eec0a..c64efc67d 100644 --- a/apps/admin-web/src/pages/AdminAgcModelsPage.tsx +++ b/apps/admin-web/src/pages/AdminAgcModelsPage.tsx @@ -116,6 +116,7 @@ export function AdminAgcModelsPage({ columns={[ { key: 'alias', label: '别名' }, { key: 'modelId', label: '实际模型名' }, + { key: 'agentMode', label: 'Agent 模式' }, { key: 'enabled', label: '启用' }, { key: 'default', label: '默认' }, { key: 'actions', label: '操作' }, @@ -155,6 +156,7 @@ export function AdminAgcModelsPage({ alias: '', modelId: '', enabled: true, + agentMode: 'codex', }, ], }, @@ -211,6 +213,21 @@ export function AdminAgcModelsPage({ } /> + + + + new Promise((resolve, reject) => { + process.stdout.write(`${JSON.stringify(value)}\n`, (error) => + error ? reject(error) : resolve(), + ); + }), + ) + .catch(() => undefined); + return outputChain; +} + +function safeEnv(input) { + const env = { ...process.env }; + if (input && typeof input === 'object') { + for (const [key, value] of Object.entries(input)) { + if (typeof value === 'string') env[key] = value; + } + } + env.CLAUDE_AGENT_SDK_CLIENT_APP ??= 'genarrative-agc-agent-sidecar/0.1'; + return env; +} + +function buildOptions(request, controller) { + const options = { + abortController: controller, + cwd: request.cwd, + model: request.model, + permissionMode: 'dontAsk', + settingSources: [], + tools: [], + disallowedTools: ['Bash', 'Edit', 'Write', 'NotebookEdit'], + verbatimPrompts: true, + persistSession: true, + includePartialMessages: true, + env: safeEnv(request.env), + }; + if (request.systemPrompt) { + options.systemPrompt = { + type: 'custom', + prompt: request.systemPrompt, + snapshot: true, + }; + } + if (request.resume) options.resume = request.resume; + if (request.sessionId) options.sessionId = request.sessionId; + if (request.mcpServers && typeof request.mcpServers === 'object') { + options.mcpServers = request.mcpServers; + } + if (request.outputSchema) { + options.outputFormat = { + type: 'json_schema', + schema: request.outputSchema, + }; + } + if (Number.isInteger(request.maxTurns) && request.maxTurns > 0) { + options.maxTurns = request.maxTurns; + } + return options; +} + +async function runTurn(request) { + const id = String(request.id ?? ''); + if (!id) return write({ type: 'error', message: 'sidecar turn 缺少 id' }); + if (active.has(id)) + return write({ id, type: 'error', message: 'sidecar turn 已存在' }); + const controller = new AbortController(); + active.set(id, { controller }); + try { + const sessionKey = + typeof request.sessionKey === 'string' ? request.sessionKey : ''; + const resume = + request.resume || (sessionKey ? sessions.get(sessionKey) : undefined); + const queryRequest = { ...request, resume }; + const stream = query({ + prompt: String(request.prompt ?? ''), + options: buildOptions(queryRequest, controller), + }); + for await (const message of stream) { + if ( + message?.type === 'system' && + message?.subtype === 'init' && + message.session_id + ) { + if (sessionKey) sessions.set(sessionKey, message.session_id); + } + if (message?.session_id && sessionKey) + sessions.set(sessionKey, message.session_id); + await write({ id, type: 'event', event: message }); + if (message?.type === 'result') { + await write({ + id, + type: 'done', + sessionId: + message.session_id ?? + (sessionKey ? sessions.get(sessionKey) : undefined), + result: message, + }); + } + } + } catch (error) { + await write({ + id, + type: 'error', + message: error instanceof Error ? error.message : String(error), + }); + } finally { + active.delete(id); + } +} + +async function cancelTurn(request) { + const id = String(request.id ?? ''); + const task = active.get(id); + if (!task) return write({ id, type: 'cancelled', cancelled: false }); + task.controller.abort(new Error('AGC cancelled Claude Agent SDK query')); + return write({ id, type: 'cancelled', cancelled: true }); +} + +const input = readline.createInterface({ + input: process.stdin, + crlfDelay: Infinity, +}); +for await (const line of input) { + if (!line.trim()) continue; + let request; + try { + request = JSON.parse(line); + } catch { + await write({ type: 'error', message: 'sidecar 输入不是有效 JSON' }); + continue; + } + if (request.type === 'cancel') { + await cancelTurn(request); + } else if (request.type === 'turn') { + void runTurn(request); + } else if (request.type === 'shutdown') { + for (const task of active.values()) task.controller.abort(); + break; + } else { + await write({ + id: request.id, + type: 'error', + message: 'sidecar 不支持该请求类型', + }); + } +} diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index ce79c75a1..83dbe4b56 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -27,6 +27,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && node scripts/check-config.mjs" }, "dependencies": { + "@anthropic-ai/claude-agent-sdk": "0.3.285", "@cubone/react-file-manager": "^1.35.0", "@genarrative/image-canvas-core": "0.1.0", "@genarrative/image-canvas-react": "0.1.0", diff --git a/apps/ai-game-creator-shell/scripts/check-config.mjs b/apps/ai-game-creator-shell/scripts/check-config.mjs index 680e864f2..a2c349e77 100644 --- a/apps/ai-game-creator-shell/scripts/check-config.mjs +++ b/apps/ai-game-creator-shell/scripts/check-config.mjs @@ -1358,6 +1358,7 @@ for (const channel of ['release', 'beta-2']) { const expectedBundledDesignAgentResources = { 'design-agent': 'design-agent', + 'resources/claude-agent': 'claude-agent', ...Object.fromEntries( [ 'codex-patch-parser', diff --git a/apps/ai-game-creator-shell/src-tauri/build.rs b/apps/ai-game-creator-shell/src-tauri/build.rs index 892ec4600..9238cb34c 100644 --- a/apps/ai-game-creator-shell/src-tauri/build.rs +++ b/apps/ai-game-creator-shell/src-tauri/build.rs @@ -32,6 +32,83 @@ fn sha256_file(path: &std::path::Path) -> Result { Ok(format!("{:x}", hasher.finalize())) } +fn claude_agent_platform(target: &str) -> Option<&'static str> { + match target { + "x86_64-pc-windows-msvc" => Some("win32-x64"), + "aarch64-pc-windows-msvc" => Some("win32-arm64"), + "x86_64-apple-darwin" => Some("darwin-x64"), + "aarch64-apple-darwin" => Some("darwin-arm64"), + "x86_64-unknown-linux-gnu" => Some("linux-x64"), + "aarch64-unknown-linux-gnu" => Some("linux-arm64"), + "x86_64-unknown-linux-musl" => Some("linux-x64-musl"), + "aarch64-unknown-linux-musl" => Some("linux-arm64-musl"), + _ => None, + } +} + +fn stage_bundled_claude_agent(manifest_dir: &std::path::Path) { + let target = env::var("TARGET").expect("Cargo TARGET"); + let Some(platform) = claude_agent_platform(&target) else { + println!("cargo:warning=Claude Agent SDK sidecar 暂不支持目标:{target}"); + return; + }; + let app_root = manifest_dir.parent().expect("AGC Tauri manifest parent"); + let repo_root = app_root + .parent() + .and_then(|apps_dir| apps_dir.parent()) + .expect("AGC 应用必须位于仓库 apps 目录下"); + let roots = [app_root, repo_root]; + let sdk_source = roots + .iter() + .map(|root| root.join("node_modules/@anthropic-ai/claude-agent-sdk")) + .find(|path| path.join("sdk.mjs").is_file()) + .unwrap_or_else(|| panic!("Claude Agent SDK 缺失;请先执行 npm ci")); + let binary_package = format!("claude-agent-sdk-{platform}"); + let binary_source = roots + .iter() + .map(|root| { + root.join("node_modules/@anthropic-ai") + .join(&binary_package) + }) + .find(|path| path.is_dir()) + .unwrap_or_else(|| panic!("Claude Agent SDK 原生运行时缺失:{binary_package}")); + let entry_source = app_root.join("agent-sidecar/src/index.mjs"); + assert!(entry_source.is_file(), "Claude Agent SDK sidecar 入口缺失"); + let target_root = manifest_dir.join("resources/claude-agent"); + if target_root.exists() { + fs::remove_dir_all(&target_root).expect("清理 Claude Agent SDK staging 失败"); + } + fs::create_dir_all(target_root.join("node_modules/@anthropic-ai")) + .expect("创建 Claude Agent SDK staging 目录失败"); + stage_plugin_file(&entry_source, &target_root.join("index.mjs")); + copy_plugin_tree( + &sdk_source, + &target_root.join("node_modules/@anthropic-ai/claude-agent-sdk"), + ); + copy_plugin_tree( + &binary_source, + &target_root + .join("node_modules/@anthropic-ai") + .join(&binary_package), + ); + let binary_name = if target.contains("windows") { + "claude.exe" + } else { + "claude" + }; + let staged_binary = target_root + .join("node_modules/@anthropic-ai") + .join(&binary_package) + .join(binary_name); + if let Ok(metadata) = fs::metadata(binary_source.join(binary_name)) { + fs::set_permissions(&staged_binary, metadata.permissions()) + .expect("保留 Claude Agent SDK 原生运行时权限失败"); + } + println!("cargo:rerun-if-changed={}", entry_source.display()); + println!("cargo:rerun-if-changed={}", sdk_source.display()); + println!("cargo:rerun-if-changed={}", binary_source.display()); +} + fn stage_bundled_codex_cli(manifest_dir: &std::path::Path) { let target = env::var("TARGET").expect("Cargo TARGET"); println!("cargo:rustc-env=AGC_BUILD_TARGET={target}"); @@ -262,6 +339,7 @@ fn main() { env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be available"), ); let manifest_path = manifest_dir.join("prompts/runtime/manifest.json"); + stage_bundled_claude_agent(&manifest_dir); stage_bundled_codex_cli(&manifest_dir); prepare_unity_editor_helper(&manifest_dir); prepare_godot_editor_extension(&manifest_dir); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent.rs b/apps/ai-game-creator-shell/src-tauri/src/agent.rs index 347ec3139..6940945d5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent.rs @@ -9,6 +9,7 @@ use std::collections::BTreeSet; use std::io::{Seek, SeekFrom}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +mod claude_code_cli; mod codex_app_server; mod codex_cli; mod codex_provider_proxy; @@ -48,6 +49,12 @@ mod runtime_protocol; mod runtime_state; mod runtime_tools; mod skill_pack; +use claude_code_cli::*; +pub(crate) use claude_code_cli::{ + cancel_direct_claude_code_turn_at, direct_game_creator_claude_code_chat_at, + direct_game_creator_claude_code_home_chat, game_creator_claude_code_cli_route_error, + game_creator_claude_code_cli_version_identity, +}; use codex_app_server::*; pub(crate) use codex_app_server::{ cancel_direct_codex_turn_at, direct_game_creator_codex_chat_at, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs new file mode 100644 index 000000000..a6ad51e1f --- /dev/null +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs @@ -0,0 +1,906 @@ +//! Claude Agent SDK sidecar execution adapter for the AGC Agent Runtime. +//! +//! This adapter intentionally keeps Claude Code behind the existing LLM/Direct +//! boundaries. Codex app-server remains a separate executor; the two wire +//! protocols are never mixed. + +use super::*; +use std::path::{Path, PathBuf}; +use std::process::Stdio; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, OnceLock}; +use std::time::Duration; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; + +const CLAUDE_AGENT_SIDECAR_ENV: &str = "GENARRATIVE_CLAUDE_AGENT_SIDECAR"; +const CLAUDE_NODE_ENV: &str = "GENARRATIVE_CLAUDE_NODE"; +const CLAUDE_CODE_OUTPUT_MAX_BYTES: usize = 8 * 1024 * 1024; +const CLAUDE_CODE_PROMPT_MAX_BYTES: usize = 4 * 1024 * 1024; +const CLAUDE_CODE_MODEL_MAX_CHARS: usize = 200; + +struct ActiveClaudeCodeTurn { + client_turn_id: String, + pid: u32, + alive: Arc, +} + +static ACTIVE_CLAUDE_CODE_TURNS: OnceLock< + Mutex>, +> = OnceLock::new(); +static CLAUDE_DIRECT_SESSIONS: OnceLock>> = + OnceLock::new(); + +fn claude_direct_sessions() -> &'static Mutex> { + CLAUDE_DIRECT_SESSIONS.get_or_init(|| Mutex::new(std::collections::HashMap::new())) +} + +fn active_claude_code_turns( +) -> &'static Mutex> { + ACTIVE_CLAUDE_CODE_TURNS.get_or_init(|| Mutex::new(std::collections::HashMap::new())) +} + +fn claude_project_key(root: &Path) -> PathBuf { + std::fs::canonicalize(root).unwrap_or_else(|_| root.to_path_buf()) +} + +fn kill_claude_code_process_tree(pid: u32) { + #[cfg(unix)] + unsafe { + libc::kill(-(pid as i32), libc::SIGKILL); + } + #[cfg(windows)] + { + let _ = std::process::Command::new("taskkill") + .args(["/PID", &pid.to_string(), "/T", "/F"]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + } +} + +struct ActiveClaudeCodeTurnGuard { + key: PathBuf, + pid: u32, + alive: Arc, +} + +impl Drop for ActiveClaudeCodeTurnGuard { + fn drop(&mut self) { + self.alive.store(false, Ordering::Release); + if let Ok(mut turns) = active_claude_code_turns().lock() { + if turns + .get(&self.key) + .is_some_and(|active| active.pid == self.pid) + { + turns.remove(&self.key); + } + } + } +} + +struct ExternalMcpStopGuard; + +impl Drop for ExternalMcpStopGuard { + fn drop(&mut self) { + stop_external_mcp_loopback(); + } +} + +fn resolve_claude_agent_sidecar_script() -> Result { + if let Some(value) = std::env::var_os(CLAUDE_AGENT_SIDECAR_ENV) { + let path = PathBuf::from(value); + if path.is_file() { + return Ok(path); + } + return Err("Claude Agent SDK sidecar 路径不可用".to_string()); + } + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + let mut candidates = vec![manifest + .parent() + .expect("AGC Tauri manifest parent") + .join("agent-sidecar/src/index.mjs")]; + if let Ok(executable) = std::env::current_exe() { + let exe_dir = executable.parent().unwrap_or_else(|| Path::new(".")); + candidates.push(exe_dir.join("claude-agent/index.mjs")); + candidates.push(exe_dir.join("resources/claude-agent/index.mjs")); + #[cfg(target_os = "macos")] + if exe_dir.file_name().and_then(|value| value.to_str()) == Some("MacOS") { + if let Some(contents) = exe_dir.parent() { + candidates.push(contents.join("Resources/claude-agent/index.mjs")); + } + } + } + candidates + .into_iter() + .find(|path| path.is_file()) + .ok_or_else(|| "Claude Agent SDK sidecar 未随 AGC 资源提供".to_string()) +} + +fn resolve_claude_node(root: &Path) -> PathBuf { + if let Some(value) = std::env::var_os(CLAUDE_NODE_ENV) { + return PathBuf::from(value); + } + crate::environment_check::resolve_node_runtime(root) + .map(|runtime| runtime.node) + .unwrap_or_else(|_| PathBuf::from("node")) +} + +fn configure_sidecar_command( + command: &mut tokio::process::Command, + root: &Path, + llm: Option<&GameCreatorLlmConfig>, + home: &Path, +) -> Result<(), String> { + let script = resolve_claude_agent_sidecar_script()?; + command + .arg(script) + .current_dir(root) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .kill_on_drop(true); + configure_claude_code_environment(command, llm, home); + configure_claude_code_process(command); + Ok(()) +} + +#[derive(Debug)] +struct SidecarTurnResult { + events: Vec, + result: serde_json::Value, +} + +async fn run_sidecar_turn( + root: &Path, + llm: Option<&GameCreatorLlmConfig>, + home: &Path, + request: serde_json::Value, + timeout_ms: u64, + active_turn: Option<&str>, +) -> Result { + let node = resolve_claude_node(root); + let mut command = tokio::process::Command::new(node); + configure_sidecar_command(&mut command, root, llm, home)?; + let mut child = command + .spawn() + .map_err(|error| format!("Claude Agent SDK sidecar 启动失败:{error}"))?; + let pid = child + .id() + .ok_or_else(|| "Claude Agent SDK sidecar 进程缺少 PID".to_string())?; + let active_key = active_turn.map(|_| claude_project_key(root)); + let active_alive = Arc::new(AtomicBool::new(true)); + if let (Some(key), Some(client_turn_id)) = (active_key.clone(), active_turn) { + let mut turns = active_claude_code_turns() + .lock() + .map_err(|_| "Claude Code Direct 回合中断表不可用".to_string())?; + turns.insert( + key, + ActiveClaudeCodeTurn { + client_turn_id: client_turn_id.to_string(), + pid, + alive: Arc::clone(&active_alive), + }, + ); + } + let _active_guard = active_key.map(|key| ActiveClaudeCodeTurnGuard { + key, + pid, + alive: Arc::clone(&active_alive), + }); + let mut stdin = child + .stdin + .take() + .ok_or_else(|| "Claude Agent SDK sidecar stdin 未建立".to_string())?; + stdin + .write_all( + format!( + "{}\n", + serde_json::to_string(&request).map_err(|error| error.to_string())? + ) + .as_bytes(), + ) + .await + .map_err(|error| format!("写入 Claude Agent SDK sidecar 请求失败:{error}"))?; + drop(stdin); + let stdout = child + .stdout + .take() + .ok_or_else(|| "Claude Agent SDK sidecar stdout 未建立".to_string())?; + let stderr = child + .stderr + .take() + .ok_or_else(|| "Claude Agent SDK sidecar stderr 未建立".to_string())?; + let stderr_task = tokio::spawn(read_capped(stderr, CLAUDE_CODE_OUTPUT_MAX_BYTES)); + let result = tokio::time::timeout(Duration::from_millis(timeout_ms.max(1)), async { + let mut lines = BufReader::new(stdout).lines(); + let mut events = Vec::new(); + let mut terminal = None; + while let Some(line) = lines + .next_line() + .await + .map_err(|error| format!("读取 Claude Agent SDK sidecar 输出失败:{error}"))? + { + if line.trim().is_empty() { + continue; + } + let value: serde_json::Value = serde_json::from_str(&line) + .map_err(|_| "Claude Agent SDK sidecar 输出不是有效 JSON".to_string())?; + match value.get("type").and_then(serde_json::Value::as_str) { + Some("event") => { + if let Some(event) = value.get("event") { + events.push(event.clone()); + } + } + Some("done") => { + terminal = value.get("result").cloned(); + break; + } + Some("error") => { + return Err(value + .get("message") + .and_then(serde_json::Value::as_str) + .unwrap_or("Claude Agent SDK sidecar 执行失败") + .to_string()); + } + _ => {} + } + } + let status = child + .wait() + .await + .map_err(|error| format!("等待 Claude Agent SDK sidecar 结束失败:{error}"))?; + let result = terminal.ok_or_else(|| "Claude Agent SDK sidecar 缺少终态".to_string())?; + if !status.success() { + return Err("Claude Agent SDK sidecar 非零退出".to_string()); + } + Ok(SidecarTurnResult { events, result }) + }) + .await + .map_err(|_| "Claude Agent SDK sidecar 回合超时".to_string())??; + let _ = stderr_task.await; + Ok(result) +} + +pub(crate) fn cancel_direct_claude_code_turn_at( + root: &Path, + client_turn_id: Option<&str>, +) -> Result, String> { + let key = claude_project_key(root); + let turns = active_claude_code_turns() + .lock() + .map_err(|_| "Claude Code Direct 回合中断表已损坏,无法终止".to_string())?; + let Some(active) = turns.get(&key) else { + return Ok(None); + }; + if let Some(expected) = client_turn_id + .map(str::trim) + .filter(|value| !value.is_empty()) + { + if active.client_turn_id != expected { + return Err("正在运行的是另一个陶泥儿回合,已拒绝终止".to_string()); + } + } + active.alive.store(false, Ordering::Release); + kill_claude_code_process_tree(active.pid); + let client_turn_id = active.client_turn_id.clone(); + Ok(Some(super::codex_app_server::DirectTurnCancelView { + outcome: super::codex_app_server::DIRECT_TURN_CANCEL_OUTCOME_INTERRUPTED.to_string(), + message: "已向正在运行的 cc 回合发出终止".to_string(), + client_turn_id, + })) +} + +pub(crate) fn game_creator_claude_code_cli_version_identity() -> Result { + let sidecar = resolve_claude_agent_sidecar_script()?; + let node = resolve_claude_node(Path::new(".")); + let output = std::process::Command::new(&node) + .arg("--version") + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .output() + .map_err(|_| "Claude Agent SDK sidecar 的 Node runtime 不可用".to_string())?; + if !output.status.success() { + return Err("Claude Agent SDK sidecar 的 Node runtime 版本检查失败".to_string()); + } + Ok(format!( + "claude-agent-sdk-sidecar@0.3.285;node={};entry={}", + String::from_utf8_lossy(&output.stdout).trim(), + sidecar.display() + )) +} + +pub(crate) fn game_creator_claude_code_cli_route_error( + llm: &GameCreatorLlmConfig, +) -> Option { + let model = llm.model.trim(); + if model.is_empty() || model.chars().count() > CLAUDE_CODE_MODEL_MAX_CHARS { + return Some("cc 模式需要有效的 Claude Code 模型标识".to_string()); + } + None +} + +fn copy_env(command: &mut tokio::process::Command, name: &str) { + if let Some(value) = std::env::var_os(name) { + command.env(name, value); + } +} + +fn claude_base_url(llm: Option<&GameCreatorLlmConfig>) -> Option { + std::env::var("ANTHROPIC_BASE_URL") + .ok() + .or_else(|| llm.map(|value| value.base_url.trim_end_matches('/').to_string())) + .map(|value| { + value + .trim_end_matches('/') + .trim_end_matches("/v1") + .to_string() + }) + .filter(|value| !value.is_empty()) +} + +fn configure_claude_code_environment( + command: &mut tokio::process::Command, + llm: Option<&GameCreatorLlmConfig>, + isolated_home: &Path, +) { + command.env_clear(); + for name in [ + "PATH", + "USERPROFILE", + "APPDATA", + "LOCALAPPDATA", + "TMPDIR", + "TEMP", + "TMP", + "HTTP_PROXY", + "HTTPS_PROXY", + "ALL_PROXY", + "NO_PROXY", + "SSL_CERT_FILE", + "SSL_CERT_DIR", + "SystemRoot", + "WINDIR", + "ComSpec", + "PATHEXT", + "ANTHROPIC_API_KEY", + "ANTHROPIC_AUTH_TOKEN", + ] { + copy_env(command, name); + } + command + .env("HOME", isolated_home) + .env("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1") + .env("DISABLE_AUTOUPDATER", "1") + .env("CI", "1") + .env("NO_COLOR", "1") + .env("TERM", "dumb"); + if let Some(base_url) = claude_base_url(llm) { + command.env("ANTHROPIC_BASE_URL", base_url); + } + if let Some(llm) = llm { + if !llm.api_key.trim().is_empty() + && std::env::var_os("ANTHROPIC_API_KEY").is_none() + && std::env::var_os("ANTHROPIC_AUTH_TOKEN").is_none() + { + command.env("ANTHROPIC_AUTH_TOKEN", llm.api_key.trim()); + } + if !llm.model.trim().is_empty() { + command.env("ANTHROPIC_MODEL", llm.model.trim()); + } + } +} + +fn configure_claude_code_process(command: &mut tokio::process::Command) { + super::codex_cli::configure_game_creator_codex_cli_process(command); +} + +fn render_claude_code_prompt(request: &LlmRunRequest) -> Result { + let messages = serde_json::to_string_pretty(&request.messages) + .map_err(|error| format!("序列化 cc Agent 消息失败:{error}"))?; + let functions = serde_json::to_string_pretty(&request.function_tools) + .map_err(|error| format!("序列化 cc Agent 函数目录失败:{error}"))?; + let prompt = if request.function_tools.is_empty() { + format!( + "你是 AGC 的 Claude Code 执行器。请根据以下消息完成任务,只返回最终正文,不要返回 Markdown 之外的解释性外壳。\n\n消息:\n{messages}" + ) + } else { + format!( + "你是 AGC 的 Claude Code 执行器。你不能直接调用宿主工具。请根据消息和函数目录,只返回严格匹配输出 schema 的 JSON;toolCalls 中的 arguments 必须是 JSON 字符串。\n\n消息:\n{messages}\n\n函数目录:\n{functions}" + ) + }; + if prompt.len() > CLAUDE_CODE_PROMPT_MAX_BYTES { + return Err(format!( + "cc Agent prompt 超过 {CLAUDE_CODE_PROMPT_MAX_BYTES} 字节上限" + )); + } + Ok(prompt) +} + +fn claude_code_schema(request: &LlmRunRequest) -> Option { + if request.function_tools.is_empty() { + return None; + } + let names = request + .function_tools + .iter() + .map(|tool| serde_json::Value::String(tool.name.clone())) + .collect::>(); + Some(serde_json::json!({ + "type": "object", + "required": ["toolCalls"], + "additionalProperties": false, + "properties": { + "toolCalls": { + "type": "array", + "minItems": 1, + "maxItems": AGENT_RUNTIME_BACKGROUND_TOOL_ACTION_LIMIT, + "items": { + "type": "object", + "required": ["name", "arguments"], + "additionalProperties": false, + "properties": { + "name": { "type": "string", "enum": names }, + "arguments": { "type": "string" } + } + } + } + } + })) +} + +async fn read_capped( + mut reader: R, + limit: usize, +) -> Result, platform_llm::LlmError> { + let mut output = Vec::new(); + let mut buffer = [0_u8; 8192]; + loop { + let count = reader.read(&mut buffer).await.map_err(|error| { + platform_llm::LlmError::Transport(format!("读取 Claude Code 输出失败:{error}")) + })?; + if count == 0 { + break; + } + if output.len().saturating_add(count) > limit { + return Err(platform_llm::LlmError::Transport( + "Claude Code 输出超过大小上限".to_string(), + )); + } + output.extend_from_slice(&buffer[..count]); + } + Ok(output) +} + +fn parse_usage(value: &serde_json::Value) -> Option { + let input = value + .get("input_tokens") + .and_then(serde_json::Value::as_u64)?; + let output = value + .get("output_tokens") + .and_then(serde_json::Value::as_u64) + .unwrap_or_default(); + Some(platform_llm::LlmTokenUsage { + prompt_tokens: input, + completion_tokens: output, + total_tokens: input.saturating_add(output), + }) +} + +fn parse_claude_code_result( + stdout: &[u8], + request: &LlmRunRequest, +) -> Result { + let value: serde_json::Value = serde_json::from_slice(stdout).map_err(|_| { + platform_llm::LlmError::Deserialize("Claude Code JSON 输出无效".to_string()) + })?; + if value.get("is_error").and_then(serde_json::Value::as_bool) == Some(true) { + return Err(platform_llm::LlmError::Transport( + "Claude Code 返回失败终态".to_string(), + )); + } + let response_id = value + .get("session_id") + .and_then(serde_json::Value::as_str) + .map(str::to_string); + let usage = value.get("usage").and_then(parse_usage); + let structured = value.get("structured_output").cloned(); + let result = value + .get("result") + .and_then(serde_json::Value::as_str) + .unwrap_or_default() + .to_string(); + let (text, tool_calls) = if request.function_tools.is_empty() { + if result.trim().is_empty() { + return Err(platform_llm::LlmError::EmptyResponse); + } + (result, Vec::new()) + } else { + let envelope = structured + .or_else(|| serde_json::from_str(&result).ok()) + .ok_or_else(|| { + platform_llm::LlmError::Deserialize( + "Claude Code structured output 缺失".to_string(), + ) + })?; + let calls = envelope + .get("toolCalls") + .and_then(serde_json::Value::as_array) + .ok_or_else(|| { + platform_llm::LlmError::Deserialize( + "Claude Code structured output 缺少 toolCalls".to_string(), + ) + })?; + let calls = calls + .iter() + .enumerate() + .map(|(index, call)| { + let name = call + .get("name") + .and_then(serde_json::Value::as_str) + .ok_or_else(|| { + platform_llm::LlmError::Deserialize( + "Claude Code tool call 缺少 name".to_string(), + ) + })?; + let arguments = call + .get("arguments") + .and_then(serde_json::Value::as_str) + .ok_or_else(|| { + platform_llm::LlmError::Deserialize( + "Claude Code tool call 缺少 arguments".to_string(), + ) + })?; + if !request.function_tools.iter().any(|tool| tool.name == name) { + return Err(platform_llm::LlmError::Deserialize( + "Claude Code 返回未广告函数".to_string(), + )); + } + Ok(platform_llm::LlmToolCall { + id: format!("claude-code-call-{}", index.saturating_add(1)), + name: name.to_string(), + arguments: arguments.to_string(), + }) + }) + .collect::, _>>()?; + (String::new(), calls) + }; + Ok(platform_llm::LlmRunResponse { + provider: platform_llm::LlmProvider::OpenAiCompatible, + model: request + .model + .clone() + .unwrap_or_else(|| "claude-code".to_string()), + text, + reasoning: String::new(), + finish_reason: Some("stop".to_string()), + response_id, + usage, + tool_calls, + responses_output: Vec::new(), + }) +} + +pub(in crate::agent) async fn request_game_creator_agent_claude_code_cli( + request: LlmRunRequest, +) -> Result { + let config = load_game_creator_app_config().map_err(platform_llm::LlmError::InvalidConfig)?; + if let Some(error) = game_creator_claude_code_cli_route_error(&config.llm) { + return Err(platform_llm::LlmError::InvalidConfig(error)); + } + let prompt = + render_claude_code_prompt(&request).map_err(platform_llm::LlmError::InvalidRequest)?; + let temp_dir = tempfile::Builder::new() + .prefix("genarrative-agc-claude-code-") + .tempdir() + .map_err(|error| { + platform_llm::LlmError::Transport(format!("创建 cc 临时目录失败:{error}")) + })?; + let home = temp_dir.path().join("home"); + std::fs::create_dir_all(&home).map_err(|error| { + platform_llm::LlmError::Transport(format!("创建 cc 隔离目录失败:{error}")) + })?; + let payload = serde_json::json!({ + "type": "turn", + "id": "runtime", + "prompt": prompt, + "systemPrompt": "你是 AGC 的 Claude Agent SDK 执行器。只按宿主提供的消息和函数目录完成当前请求。", + "cwd": temp_dir.path(), + "model": request.model.as_deref().unwrap_or(&config.llm.model), + "outputSchema": claude_code_schema(&request), + "maxTurns": 1, + }); + let sidecar = run_sidecar_turn( + temp_dir.path(), + Some(&config.llm), + &home, + payload, + request + .request_timeout_ms + .unwrap_or(config.llm.request_timeout_ms), + None, + ) + .await + .map_err(platform_llm::LlmError::Transport)?; + parse_claude_code_result( + serde_json::to_vec(&sidecar.result) + .map_err(|error| platform_llm::LlmError::Deserialize(error.to_string()))? + .as_slice(), + &request, + ) +} + +pub(crate) async fn direct_game_creator_claude_code_home_chat( + llm: &GameCreatorLlmConfig, + system_prompt: String, + user_prompt: String, +) -> Result { + let request = LlmRunRequest::single_turn(system_prompt, user_prompt) + .with_model(llm.model.clone()) + .with_request_timeout_ms(llm.request_timeout_ms) + .with_max_output_tokens(16_000); + request_game_creator_agent_claude_code_cli(request) + .await + .map(|response| response.text) + .map_err(|error| error.to_string()) +} + +/// DirectProject 使用 Claude Code 自己的 MCP 调用能力;宿主只暴露 loopback +/// MCP,关闭所有内置工具。它先作为最小可用执行器接入,后续可在同一观察协议上补充 +/// 更细的 Claude tool_use 活动投影。 +pub(crate) async fn direct_game_creator_claude_code_chat_at( + root: &Path, + llm: &GameCreatorLlmConfig, + system_prompt: String, + user_prompt: String, + client_turn_id: Option<&str>, + observer: Option<&mut (dyn FnMut(DirectCodexTurnObservation) + Send)>, +) -> Result { + let (mcp_url, mcp_token) = start_external_mcp_loopback(root, llm.web_search_enabled).await?; + let _mcp_guard = ExternalMcpStopGuard; + let home = crate::project::resolve_local_project_path(root, ".agent/runtime/claude-code/home")?; + std::fs::create_dir_all(&home) + .map_err(|error| format!("创建 cc Direct 隔离目录失败:{error}"))?; + let session_key = claude_project_key(root); + let resume = claude_direct_sessions() + .lock() + .ok() + .and_then(|sessions| sessions.get(&session_key).cloned()); + let payload = serde_json::json!({ + "type": "turn", + "id": client_turn_id.unwrap_or("claude-direct-turn"), + "sessionKey": format!("agc-direct:{}", session_key.display()), + "resume": resume, + "prompt": user_prompt, + "systemPrompt": system_prompt, + "cwd": root, + "model": llm.model.trim(), + "maxTurns": 8, + "mcpServers": { + "agc": { + "type": "http", + "url": mcp_url, + "headers": { "Authorization": format!("Bearer {mcp_token}") } + } + } + }); + let sidecar = run_sidecar_turn( + root, + Some(llm), + &home, + payload, + llm.request_timeout_ms, + client_turn_id, + ) + .await?; + if let Some(session_id) = sidecar + .result + .get("session_id") + .and_then(serde_json::Value::as_str) + { + if let Ok(mut sessions) = claude_direct_sessions().lock() { + sessions.insert(session_key, session_id.to_string()); + } + } + let mut stream = sidecar + .events + .into_iter() + .chain(std::iter::once(sidecar.result)) + .filter_map(|event| serde_json::to_string(&event).ok()) + .collect::>(); + stream.push(String::new()); + parse_direct_stream_result( + stream.join("\n").as_bytes(), + client_turn_id.unwrap_or("claude-code-turn"), + observer, + ) +} + +fn parse_direct_stream_result( + stdout: &[u8], + turn_id: &str, + mut observer: Option<&mut (dyn FnMut(DirectCodexTurnObservation) + Send)>, +) -> Result { + let text = std::str::from_utf8(stdout) + .map_err(|_| "Claude Agent SDK stream-json 不是 UTF-8".to_string())?; + let mut result = None; + let mut tool_calls = std::collections::HashMap::::new(); + for line in text.lines().filter(|line| !line.trim().is_empty()) { + let event: serde_json::Value = serde_json::from_str(line) + .map_err(|_| "Claude Agent SDK stream-json 包含无效 JSON".to_string())?; + let event_type = event.get("type").and_then(serde_json::Value::as_str); + if event_type == Some("assistant") { + let id = event + .get("uuid") + .and_then(serde_json::Value::as_str) + .unwrap_or("claude-code-assistant") + .to_string(); + let content = event + .pointer("/message/content") + .and_then(serde_json::Value::as_array); + if let Some(content) = content { + let visible = content + .iter() + .filter_map(|part| { + (part.get("type").and_then(serde_json::Value::as_str) == Some("text")).then( + || { + part.get("text") + .and_then(serde_json::Value::as_str) + .unwrap_or_default() + }, + ) + }) + .collect::(); + if !visible.is_empty() { + if let Some(observer) = observer.as_deref_mut() { + observer(DirectCodexTurnObservation::AgentMessageSegment { + item_id: id, + accumulated_text: visible, + completed: true, + }); + } + } + for part in content.iter().filter(|part| { + part.get("type").and_then(serde_json::Value::as_str) == Some("tool_use") + }) { + let Some(call_id) = part.get("id").and_then(serde_json::Value::as_str) else { + continue; + }; + let name = part + .get("name") + .and_then(serde_json::Value::as_str) + .unwrap_or("mcp_tool") + .to_string(); + let arguments = part + .get("input") + .cloned() + .unwrap_or(serde_json::Value::Null); + let now = direct_tool_call_now_ms(); + let call = DirectToolCall { + schema_version: DIRECT_TOOL_CALL_SCHEMA_VERSION.to_string(), + id: call_id.to_string(), + turn_id: turn_id.to_string(), + kind: "mcp_tool".to_string(), + title: "调用工具".to_string(), + summary: name, + status: "running".to_string(), + detail: DirectToolCallDetail { + command: serde_json::to_string(&arguments).ok(), + output: None, + changes: Vec::new(), + }, + started_at: now, + updated_at: now, + }; + if let Some(observer) = observer.as_deref_mut() { + observer(DirectCodexTurnObservation::ToolCall(call.clone())); + } + tool_calls.insert(call_id.to_string(), call); + } + } + } else if event_type == Some("user") { + if let Some(content) = event + .pointer("/message/content") + .and_then(serde_json::Value::as_array) + { + for part in content.iter().filter(|part| { + part.get("type").and_then(serde_json::Value::as_str) == Some("tool_result") + }) { + let Some(call_id) = part.get("tool_use_id").and_then(serde_json::Value::as_str) + else { + continue; + }; + let Some(mut call) = tool_calls.remove(call_id) else { + continue; + }; + call.status = "completed".to_string(); + call.updated_at = direct_tool_call_now_ms(); + call.detail.output = part + .get("content") + .and_then(serde_json::Value::as_str) + .map(str::to_string); + if part.get("is_error").and_then(serde_json::Value::as_bool) == Some(true) { + call.status = "failed".to_string(); + } + if let Some(observer) = observer.as_deref_mut() { + observer(DirectCodexTurnObservation::ToolCall(call)); + } + } + } + } else if event_type == Some("result") { + if event.get("is_error").and_then(serde_json::Value::as_bool) == Some(true) { + return Err("Claude Code 返回失败终态".to_string()); + } + result = event + .get("result") + .and_then(serde_json::Value::as_str) + .map(str::to_string); + } + } + result + .filter(|value| !value.trim().is_empty()) + .ok_or_else(|| "Claude Code 缺少最终回复".to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn request(with_tools: bool) -> LlmRunRequest { + let mut request = + LlmRunRequest::single_turn("系统", "任务").with_model("claude-opus-5-5".to_string()); + if with_tools { + request.function_tools.push(platform_llm::LlmFunctionTool { + name: "runtime_tool_file_read".to_string(), + description: "读取文件".to_string(), + parameters: serde_json::json!({"type":"object"}), + strict: false, + }); + } + request + } + + #[test] + fn parses_claude_code_text_result() { + let response = parse_claude_code_result( + br#"{"session_id":"session-1","result":"CC_OK","usage":{"input_tokens":3,"output_tokens":2},"is_error":false}"#, + &request(false), + ) + .expect("text result"); + assert_eq!(response.text, "CC_OK"); + assert_eq!(response.response_id.as_deref(), Some("session-1")); + assert_eq!(response.usage.expect("usage").total_tokens, 5); + } + + #[test] + fn parses_claude_code_structured_tool_result_and_validates_name() { + let response = parse_claude_code_result( + br#"{"session_id":"session-2","result":"{\"toolCalls\":[{\"name\":\"runtime_tool_file_read\",\"arguments\":\"{\\\"path\\\":\\\"game/index.html\\\"}\"}]}","structured_output":{"toolCalls":[{"name":"runtime_tool_file_read","arguments":"{\"path\":\"game/index.html\"}"}]},"is_error":false}"#, + &request(true), + ) + .expect("structured result"); + assert_eq!(response.text, ""); + assert_eq!(response.tool_calls.len(), 1); + assert_eq!(response.tool_calls[0].name, "runtime_tool_file_read"); + } + + #[test] + fn parses_direct_stream_result_and_final_text() { + let mut observed = Vec::new(); + let text = parse_direct_stream_result( + r#"{"type":"system","subtype":"init","session_id":"s"} +{"type":"assistant","uuid":"a","message":{"content":[{"type":"text","text":"中间"}]}} +{"type":"result","is_error":false,"result":"最终回复"} +"# + .as_bytes(), + "turn-1", + Some(&mut |event| observed.push(event)), + ) + .expect("stream result"); + assert_eq!(text, "最终回复"); + assert!(matches!( + observed.as_slice(), + [DirectCodexTurnObservation::AgentMessageSegment { .. }] + )); + } +} diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs index 7a2bb1091..6476dd583 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs @@ -4530,6 +4530,9 @@ pub(crate) fn cancel_direct_codex_turn_at( root: &Path, client_turn_id: Option<&str>, ) -> Result { + if let Some(view) = cancel_direct_claude_code_turn_at(root, client_turn_id)? { + return Ok(view); + } let key = direct_codex_active_turn_key(root); let expected = client_turn_id .map(str::trim) @@ -5363,6 +5366,23 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_optional_observer( }; let config = load_game_creator_app_config() .map_err(|detail| DirectTurnError::EnvironmentNotReady { detail })?; + if config.agent_mode == GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI { + if let Some(error) = game_creator_claude_code_cli_route_error(&config.llm) { + return Err(DirectTurnError::EnvironmentNotReady { detail: error }); + } + return direct_game_creator_claude_code_chat_at( + &codex_root, + &config.llm, + system_prompt, + user_prompt, + client_turn_id, + observer, + ) + .await + .map_err(|detail| { + DirectTurnError::from_model_call(&platform_llm::LlmError::Transport(detail)) + }); + } game_creator_codex_app_server_validate_llm_config(&config.llm).map_err(|error| { DirectTurnError::EnvironmentNotReady { detail: error.to_string(), @@ -5459,6 +5479,13 @@ pub(crate) async fn direct_game_creator_home_codex_chat( user_prompt: String, ) -> Result { let config = load_game_creator_app_config()?; + if config.agent_mode == GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI { + if let Some(error) = game_creator_claude_code_cli_route_error(&config.llm) { + return Err(error); + } + return direct_game_creator_claude_code_home_chat(&config.llm, system_prompt, user_prompt) + .await; + } game_creator_codex_app_server_validate_llm_config(&config.llm) .map_err(|error| error.to_string())?; let snapshot = AgentRuntimeProviderRequestSnapshot { diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/provider_retry.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/provider_retry.rs index e40018b89..6048221a4 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/provider_retry.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/provider_retry.rs @@ -373,10 +373,25 @@ fn game_creator_agent_runtime_provider_config_fingerprint_for_mode( } else { None }; + let claude_code_cli = if agent_mode == GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI { + Some(serde_json::json!({ + "executable": "claude", + "version": game_creator_claude_code_cli_version_identity()?, + "protocol": "genarrative-claude-code-cli-agent.v1", + "structuredOutput": true, + "nativeTools": false, + "mcp": "direct-loopback-only", + "isolatedHome": true, + "noSessionPersistence": true, + })) + } else { + None + }; let stable = serde_json::json!({ "agentMode": agent_mode, "codexAppServer": codex_app_server, "codexCli": codex_cli, + "claudeCodeCli": claude_code_cli, "apiKeySha256": format!("{:x}", Sha256::digest(llm.api_key.as_bytes())), "baseUrl": llm.base_url, "model": llm.model, @@ -419,6 +434,8 @@ pub(in crate::agent) fn game_creator_agent_runtime_provider_retry_identity_for_m GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER | GAME_CREATOR_AGENT_MODE_CODEX_CLI ) { Some(game_creator_codex_cli_version_identity()?) + } else if agent_mode == GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI { + Some(game_creator_claude_code_cli_version_identity()?) } else { None }; @@ -1448,6 +1465,9 @@ pub(in crate::agent) async fn request_game_creator_agent_runtime_llm_with_persis GAME_CREATOR_AGENT_MODE_CODEX_CLI => { request_game_creator_agent_codex_cli(request).await } + GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI => { + request_game_creator_agent_claude_code_cli(request).await + } GAME_CREATOR_AGENT_MODE_PROVIDER => { let client = build_game_creator_agent_runtime_llm_client( &llm_for_request, @@ -1524,6 +1544,9 @@ pub(in crate::agent) async fn request_game_creator_agent_runtime_llm_with_transi GAME_CREATOR_AGENT_MODE_CODEX_CLI => { request_game_creator_agent_codex_cli(request.clone()).await } + GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI => { + request_game_creator_agent_claude_code_cli(request.clone()).await + } GAME_CREATOR_AGENT_MODE_PROVIDER => { let client = client.expect("provider mode constructs an HTTP client"); request_game_creator_agent_runtime_provider_llm(&client, llm, request.clone()) diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index abfd714fa..de54ffa03 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -1927,10 +1927,10 @@ pub(crate) fn select_game_creator_reasoning_effort( persist_game_creator_app_config(config, overlays, false) } -#[tauri::command] -pub(crate) fn select_game_creator_model( +fn select_game_creator_model_inner( model_id: String, is_default: bool, + catalog_agent_mode: Option, ) -> Result { let _guard = GAME_CREATOR_CONFIG_WRITE_LOCK .lock() @@ -1948,11 +1948,27 @@ pub(crate) fn select_game_creator_model( { return Err("模型标识无效".into()); } + if let Some(agent_mode) = catalog_agent_mode { + config.agent_mode = match agent_mode.trim() { + "codex" => GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER.to_string(), + "cc" => GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI.to_string(), + value => return Err(format!("模型 Agent 模式无效:{value}")), + }; + } config.selected_model_id = model_id; config.selected_model_is_default = is_default; persist_game_creator_app_config(config, overlays, true) } +#[tauri::command] +pub(crate) fn select_game_creator_model( + model_id: String, + is_default: bool, + agent_mode: Option, +) -> Result { + select_game_creator_model_inner(model_id, is_default, agent_mode) +} + #[tauri::command] pub(crate) async fn discover_game_creator_llm_models( llm: GameCreatorLlmConfig, diff --git a/apps/ai-game-creator-shell/src-tauri/src/config.rs b/apps/ai-game-creator-shell/src-tauri/src/config.rs index babe29eb0..edc753a2d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/config.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/config.rs @@ -443,20 +443,30 @@ pub(crate) fn check_game_creator_llm_config_from_config() -> GameCreatorLlmConfi fn check_game_creator_codex_config( app_config: &GameCreatorAppConfig, ) -> GameCreatorLlmConfigStatus { - let cli_error = check_game_creator_codex_cli_available() - .and_then(|()| { - if app_config.agent_mode == GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER { - check_game_creator_codex_app_server_available() - } else { - Ok(()) - } - }) - .err(); + let cli_error = match app_config.agent_mode.as_str() { + GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI => { + crate::agent::game_creator_claude_code_cli_version_identity().map(|_| ()) + } + GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER | GAME_CREATOR_AGENT_MODE_CODEX_CLI => { + check_game_creator_codex_cli_available().and_then(|()| { + if app_config.agent_mode == GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER { + check_game_creator_codex_app_server_available() + } else { + Ok(()) + } + }) + } + _ => Ok(()), + } + .err(); let global_route_error = game_creator_codex_app_server_llm_route_error( &app_config.agent_mode, &app_config.llm, "llm", - ); + ) + .or_else(|| { + game_creator_claude_code_cli_llm_route_error(&app_config.agent_mode, &app_config.llm) + }); let mut status = check_game_creator_llm_config_values(&app_config.llm, "llm"); status.agent_mode = app_config.agent_mode.clone(); if !app_config.llm.custom_enabled && game_creator_official_llm_route_locked() { @@ -550,6 +560,16 @@ pub(crate) fn game_creator_codex_app_server_llm_route_error( None } +pub(crate) fn game_creator_claude_code_cli_llm_route_error( + agent_mode: &str, + llm: &GameCreatorLlmConfig, +) -> Option { + if agent_mode != GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI { + return None; + } + crate::agent::game_creator_claude_code_cli_route_error(llm) +} + pub(crate) fn check_game_creator_codex_cli_available() -> Result<(), String> { crate::agent::game_creator_codex_cli_version_identity().map(|_| ()) } @@ -3650,10 +3670,15 @@ pub(crate) fn scrub_locked_game_creator_config_file(config: &mut GameCreatorAppC if config.llm.as_ref().and_then(|llm| llm.custom_enabled) == Some(true) { return false; } - let mut changed = config.agent_mode.as_deref() - != Some(GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER) - || config.agent_llm.is_some(); - config.agent_mode = Some(GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER.to_string()); + let locked_agent_mode = + if config.agent_mode.as_deref() == Some(GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI) { + GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI + } else { + GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER + }; + let mut changed = + config.agent_mode.as_deref() != Some(locked_agent_mode) || config.agent_llm.is_some(); + config.agent_mode = Some(locked_agent_mode.to_string()); config.agent_llm = None; if let Some(llm) = config.llm.as_mut() { // 官方路由仍然清空凭据,但把连接字段留在文件里:手写自定义连接时 @@ -3806,7 +3831,9 @@ pub(crate) fn lock_game_creator_app_config_to_official_route(config: &mut GameCr if !game_creator_official_llm_route_locked() { return; } - config.agent_mode = GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER.to_string(); + if config.agent_mode != GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI { + config.agent_mode = GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER.to_string(); + } config.agent_llm.clear(); config.editor_api.api_key.clear(); if config.llm.custom_enabled { @@ -4528,9 +4555,12 @@ pub(crate) fn normalize_game_creator_agent_mode(value: &str) -> Result Ok(GAME_CREATOR_AGENT_MODE_CODEX_CLI.to_string()), + GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI => { + Ok(GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI.to_string()) + } GAME_CREATOR_AGENT_MODE_PROVIDER => Ok(GAME_CREATOR_AGENT_MODE_PROVIDER.to_string()), value => Err(format!( - "配置项 agentMode 无效:{value},请使用 codex_app_server、codex_cli 或 provider" + "配置项 agentMode 无效:{value},请使用 codex_app_server、codex_cli、claude_code_cli 或 provider" )), } } diff --git a/apps/ai-game-creator-shell/src-tauri/src/llm_catalog.rs b/apps/ai-game-creator-shell/src-tauri/src/llm_catalog.rs index a7244fa58..0944e4f9f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/llm_catalog.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/llm_catalog.rs @@ -12,11 +12,17 @@ use std::time::Duration; const API_RESPONSE_ENVELOPE_VERSION: &str = "v1"; const MODEL_CATALOG_MAX_BYTES: usize = 1024 * 1024; +fn default_codex_agent_mode() -> String { + "codex".to_string() +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct WireModelSummary { id: String, display_name: String, + #[serde(default = "default_codex_agent_mode")] + agent_mode: String, } #[derive(Debug, Deserialize)] @@ -49,6 +55,7 @@ fn map_catalog(catalog: WireModelCatalog) -> LlmModelsResponse { .map(|model| LlmModelSummary { id: model.id, display_name: model.display_name, + agent_mode: model.agent_mode, }) .collect(), revision: catalog.revision, diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index d88778955..9f4fe3db3 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -1438,6 +1438,7 @@ const GAME_CREATOR_CONFIG_FILE_NAME: &str = "game-creator.config.json"; const GAME_CREATOR_LOCAL_CONFIG_FILE_NAME: &str = "game-creator.config.local.json"; const GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER: &str = "codex_app_server"; const GAME_CREATOR_AGENT_MODE_CODEX_CLI: &str = "codex_cli"; +const GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI: &str = "claude_code_cli"; const GAME_CREATOR_AGENT_MODE_PROVIDER: &str = "provider"; const GAME_CREATOR_APP_CONFIG_SCHEMA_VERSION: &str = "game-creator-config.v2"; const DEFAULT_GAME_CREATOR_LLM_BASE_URL: &str = "https://dev.genarrative.world/gpt/v1"; diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/configuration.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/configuration.rs index 4f7b446ad..56d9b20f3 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/configuration.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/configuration.rs @@ -38,9 +38,9 @@ fn custom_llm_config_save_reload_and_selection_preserve_overlay_and_credentials( write_game_creator_config_atomically(&overlay, r#"{"llm":{"customEnabled":true,"apiKey":"fixture-key","baseUrl":"https://custom.example/v1","visibleModels":["a/v1","b:v2"]},"selectedModelId":"a/v1","selectedModelIsDefault":true}"#).unwrap(); let mut config = read_game_creator_app_config().unwrap().config; assert_eq!(config.llm.model, "a/v1"); - let selected = select_game_creator_model("b:v2".into(), false).unwrap(); + let selected = select_game_creator_model("b:v2".into(), false, None).unwrap(); assert_eq!(selected.config.llm.model, "b:v2"); - assert!(select_game_creator_model("not-listed".into(), false).is_err()); + assert!(select_game_creator_model("not-listed".into(), false, None).is_err()); config.llm.visible_models = vec!["b:v2".into()]; config.llm.api_key = "changed-fixture-key".into(); write_game_creator_app_config(config).unwrap(); @@ -228,6 +228,11 @@ fn agent_mode_defaults_to_codex_app_server_and_preserves_explicit_modes() { normalize_game_creator_agent_mode(GAME_CREATOR_AGENT_MODE_CODEX_CLI).expect("cli mode"), GAME_CREATOR_AGENT_MODE_CODEX_CLI ); + assert_eq!( + normalize_game_creator_agent_mode(GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI) + .expect("cc mode"), + GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI + ); assert!(normalize_game_creator_agent_mode("unknown") .expect_err("unknown mode") .contains("agentMode")); @@ -958,7 +963,8 @@ fn app_config_model_selection_only_updates_model_overlay() { r#"{"selectedModelId":"old","selectedModelIsDefault":false,"llm":{"reasoningEffort":"low"}}"#, ) .expect("write overlay"); - let saved = select_game_creator_model("new-model".to_string(), true).expect("select model"); + let saved = + select_game_creator_model("new-model".to_string(), true, None).expect("select model"); let effective = load_game_creator_app_config().expect("effective config"); assert_eq!(saved.config.selected_model_id, "new-model"); assert_eq!(effective.selected_model_id, "new-model"); @@ -981,12 +987,12 @@ fn model_selection_default_flag_round_trips() { let _guard = use_test_runtime_config_dir(root.clone()); let saved = - select_game_creator_model("quality".to_string(), true).expect("select default model"); + select_game_creator_model("quality".to_string(), true, None).expect("select default model"); assert_eq!(saved.config.selected_model_id, "quality"); assert!(saved.config.selected_model_is_default); let saved = - select_game_creator_model("fast".to_string(), false).expect("select explicit model"); + select_game_creator_model("fast".to_string(), false, None).expect("select explicit model"); assert_eq!(saved.config.selected_model_id, "fast"); assert!(!saved.config.selected_model_is_default); diff --git a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json index cf24da0c5..bed37e341 100644 --- a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json +++ b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json @@ -39,14 +39,19 @@ "vendor/codex-utils-path-uri/LICENSE": "licenses/codex-utils-path-uri/LICENSE", "vendor/codex-utils-path-uri/NOTICE": "licenses/codex-utils-path-uri/NOTICE", "vendor/codex-utils-absolute-path/LICENSE": "licenses/codex-utils-absolute-path/LICENSE", - "vendor/codex-utils-absolute-path/NOTICE": "licenses/codex-utils-absolute-path/NOTICE" + "vendor/codex-utils-absolute-path/NOTICE": "licenses/codex-utils-absolute-path/NOTICE", + "resources/claude-agent": "claude-agent" }, "linux": { "deb": { - "depends": ["bubblewrap"] + "depends": [ + "bubblewrap" + ] }, "rpm": { - "depends": ["bubblewrap"] + "depends": [ + "bubblewrap" + ] } }, "icon": [ diff --git a/apps/ai-game-creator-shell/src/app/types.ts b/apps/ai-game-creator-shell/src/app/types.ts index 73f45325f..5d0c1e99a 100644 --- a/apps/ai-game-creator-shell/src/app/types.ts +++ b/apps/ai-game-creator-shell/src/app/types.ts @@ -575,6 +575,7 @@ export type GameCreatorLlmApiKind = export type GameCreatorAgentMode = | 'codex_app_server' | 'codex_cli' + | 'claude_code_cli' | 'provider'; export type RuntimeLlmProviderPresetId = | 'custom' diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx index 2da81b306..b7b44234b 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx @@ -171,6 +171,9 @@ export function ConversationModelSelect({ invoke('select_game_creator_model', { modelId: next, isDefault: nextIsDefault, + agentMode: + catalog.models.find((model) => model.id === next)?.agentMode ?? + 'codex', }), ); } catch { @@ -336,10 +339,13 @@ export function ConversationModelSelect({ try { const invoke = resolveTauriInvoke(); if (!invoke) throw new Error('Native host unavailable'); + const agentMode = + models.find((model) => model.id === id)?.agentMode ?? 'codex'; const result = await queueConfigWrite(() => invoke('select_game_creator_model', { modelId: id, isDefault: false, + agentMode, }), ); if (result.config.selectedModelId !== id) diff --git a/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx b/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx index 45b9d7bab..c18a6da4f 100644 --- a/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx +++ b/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx @@ -166,7 +166,11 @@ function normalizeRuntimeConfigDraft( : defaultRuntimeConfigDraft.llm.reasoningEffort; return { ...config, - agentMode: 'codex_app_server', + // Agent 模式由后台模型选择绑定;设置页保存不能把 cc 选择覆盖回 Codex。 + agentMode: + config.agentMode === 'claude_code_cli' + ? 'claude_code_cli' + : 'codex_app_server', llm: { ...config.llm, apiKey: config.llm.customEnabled ? config.llm.apiKey : '', @@ -663,7 +667,11 @@ export function RuntimeConfigDialog({ const config = normalizeRuntimeConfigDraft( { ...runtimeConfigDraft, - agentMode: 'codex_app_server', + // 模型选择器已把后台绑定的执行模式写入配置,这里只保留它。 + agentMode: + runtimeConfigDraft.agentMode === 'claude_code_cli' + ? 'claude_code_cli' + : 'codex_app_server', editorApi: advancedExternalEditorConfigEnabled ? runtimeConfigDraft.editorApi : defaultRuntimeConfigDraft.editorApi, diff --git a/apps/ai-game-creator-shell/src/services/clientApi.ts b/apps/ai-game-creator-shell/src/services/clientApi.ts new file mode 100644 index 000000000..2f4de6483 --- /dev/null +++ b/apps/ai-game-creator-shell/src/services/clientApi.ts @@ -0,0 +1,36 @@ +import { resolveTauriInvoke } from '../app/tauri'; + +export type ClientLlmModel = { + displayName: string; + id: string; + agentMode: 'codex' | 'cc'; +}; + +export type ClientLlmModelCatalog = { + defaultModelId: string; + models: ClientLlmModel[]; + revision: number; +}; + +type ClientLlmModelWire = Omit & { + agentMode?: ClientLlmModel['agentMode'] | null; +}; + +type ClientLlmModelCatalogWire = Omit & { + models: ClientLlmModelWire[]; +}; + +export async function loadClientLlmModels(): Promise { + const invoke = resolveTauriInvoke(); + if (!invoke) throw new Error('本地模型服务不可用'); + const catalog = await invoke( + 'load_game_creator_llm_models', + ); + return { + ...catalog, + models: catalog.models.map((model) => ({ + ...model, + agentMode: model.agentMode ?? 'codex', + })), + }; +} diff --git a/apps/ai-game-creator-shell/src/services/llmModelCatalog.ts b/apps/ai-game-creator-shell/src/services/llmModelCatalog.ts index 18494f766..0fd46c689 100644 --- a/apps/ai-game-creator-shell/src/services/llmModelCatalog.ts +++ b/apps/ai-game-creator-shell/src/services/llmModelCatalog.ts @@ -1,16 +1,8 @@ import { resolveTauriInvoke } from '../app/tauri'; import type { GameCreatorAppConfigView } from '../app/types'; +import { type ClientLlmModelCatalog, loadClientLlmModels } from './clientApi'; -export type ClientLlmModel = { - displayName: string; - id: string; -}; - -export type ClientLlmModelCatalog = { - defaultModelId: string; - models: ClientLlmModel[]; - revision: number; -}; +export type { ClientLlmModel, ClientLlmModelCatalog } from './clientApi'; let cached: ClientLlmModelCatalog | null = null; let inFlight: Promise | null = null; @@ -56,12 +48,16 @@ async function loadEffectiveCatalog(epoch: number) { const ids = llm.visibleModels ?? []; return { defaultModelId: ids[0] ?? '', - models: ids.map((id) => ({ id, displayName: id })), + models: ids.map((id) => ({ + id, + displayName: id, + agentMode: 'codex' as const, + })), revision: --localRevision, }; } // 官方模型目录的凭据、请求和响应解析归 Rust;渲染层只接收目录快照。 - return invoke('load_game_creator_llm_models'); + return loadClientLlmModels(); } /** 最近一次成功读取的模型目录,用于首屏渲染与刷新失败时兜底。 */ diff --git a/apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts index 95906dc24..15ec5f80d 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts @@ -281,6 +281,7 @@ export function registerDesignAgentSurfaceTests() { expect(harness.invoke).toHaveBeenCalledWith('select_game_creator_model', { modelId: 'fast', isDefault: false, + agentMode: 'codex', }); expect( screen.getByRole('button', { name: '对话模型' }).textContent, diff --git a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts index 0edc4be61..ddb330074 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts @@ -148,6 +148,7 @@ export function registerClientHomeTests() { expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { modelId: 'fast', isDefault: false, + agentMode: 'codex', }), ); expect(modelTrigger.textContent).toContain('快速'); diff --git a/apps/ai-game-creator-shell/tests/clientApi.test.ts b/apps/ai-game-creator-shell/tests/clientApi.test.ts new file mode 100644 index 000000000..8d7b8ba04 --- /dev/null +++ b/apps/ai-game-creator-shell/tests/clientApi.test.ts @@ -0,0 +1,35 @@ +// @vitest-environment jsdom +import { beforeEach, expect, test, vi } from 'vitest'; + +import { resolveTauriInvoke } from '../src/app/tauri'; +import { loadClientLlmModels } from '../src/services/clientApi'; + +vi.mock('../src/app/tauri', () => ({ resolveTauriInvoke: vi.fn() })); + +const invoke = vi.fn(); + +beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(resolveTauriInvoke).mockReturnValue(invoke); +}); + +test('模型目录缺少 agentMode 时默认使用 codex,同时保留官方 cc 路由', async () => { + invoke.mockResolvedValue({ + defaultModelId: 'quality', + models: [ + { id: 'quality', displayName: '高质量' }, + { id: 'claude', displayName: 'Claude', agentMode: 'cc' }, + ], + revision: 1, + }); + + await expect(loadClientLlmModels()).resolves.toEqual({ + defaultModelId: 'quality', + models: [ + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'claude', displayName: 'Claude', agentMode: 'cc' }, + ], + revision: 1, + }); + expect(invoke).toHaveBeenCalledWith('load_game_creator_llm_models'); +}); diff --git a/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx b/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx index bc7f548ef..9208b832b 100644 --- a/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx +++ b/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx @@ -44,8 +44,8 @@ beforeEach(() => { vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [ - { id: 'quality', displayName: '高质量' }, - { id: 'fast', displayName: '快速' }, + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'fast', displayName: '快速', agentMode: 'cc' }, ], revision: 1, }); @@ -126,6 +126,11 @@ test('custom mode only shows checked endpoint model IDs and never requests the p fireEvent.click(screen.getByRole('option', { name: 'vendor/fast:latest' })); await waitFor(() => expect(savedModelId).toBe('vendor/fast:latest')); expect(loadClientLlmModels).not.toHaveBeenCalled(); + expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { + modelId: 'vendor/fast:latest', + isDefault: false, + agentMode: 'codex', + }); }); test('saving a custom catalog replaces official models and falls back when the old selection is unchecked', async () => { @@ -196,7 +201,7 @@ test('shows manual refresh progress immediately without clearing the selected mo await act(async () => { resolveRefresh({ defaultModelId: 'quality', - models: [{ id: 'quality', displayName: '高质量' }], + models: [{ id: 'quality', displayName: '高质量', agentMode: 'codex' }], revision: 1, }); }); @@ -284,6 +289,7 @@ test('only displays aliases and persists selection through the native command', expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { modelId: 'fast', isDefault: false, + agentMode: 'cc', }), ); await waitFor(() => expect(onReady).toHaveBeenLastCalledWith(true)); @@ -313,6 +319,7 @@ test('falls back to the default model when the saved selection was removed', asy expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { modelId: 'quality', isDefault: true, + agentMode: 'codex', }); expect( screen.getByRole('button', { name: '对话模型' }).textContent, @@ -472,9 +479,9 @@ test('applies a new catalog revision on focus', async () => { vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [ - { id: 'quality', displayName: '高质量' }, - { id: 'fast', displayName: '快速' }, - { id: 'vision', displayName: '视觉' }, + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'fast', displayName: '快速', agentMode: 'cc' }, + { id: 'vision', displayName: '视觉', agentMode: 'codex' }, ], revision: 2, }); @@ -510,7 +517,7 @@ test('pre-send validation falls back when the selected model is disabled', async await waitFor(() => expect(savedModelId).toBe('fast')); vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', - models: [{ id: 'quality', displayName: '高质量' }], + models: [{ id: 'quality', displayName: '高质量', agentMode: 'codex' }], revision: 2, }); await act(async () => { @@ -518,6 +525,11 @@ test('pre-send validation falls back when the selected model is disabled', async }); expect(screen.getByText('所选模型已停用,已切换为默认模型')).not.toBeNull(); expect(savedModelId).toBe('quality'); + expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { + modelId: 'quality', + isDefault: true, + agentMode: 'codex', + }); }); test('follows the new server default when the saved selection was the default', async () => { @@ -527,8 +539,8 @@ test('follows the new server default when the saved selection was the default', vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'fast', models: [ - { id: 'quality', displayName: '高质量' }, - { id: 'fast', displayName: '快速' }, + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'fast', displayName: '快速', agentMode: 'cc' }, ], revision: 2, }); @@ -538,6 +550,11 @@ test('follows the new server default when the saved selection was the default', expect( screen.getByText('默认模型已更新,已切换为新的默认模型'), ).not.toBeNull(); + expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { + modelId: 'fast', + isDefault: true, + agentMode: 'cc', + }); expect( screen.getByRole('button', { name: '对话模型' }).textContent, ).toContain('快速'); @@ -554,8 +571,8 @@ test('keeps an explicit selection when the server default changes', async () => vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [ - { id: 'quality', displayName: '高质量' }, - { id: 'fast', displayName: '快速' }, + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'fast', displayName: '快速', agentMode: 'cc' }, ], revision: 2, }); @@ -650,8 +667,8 @@ test('keeps refreshing when the server omits the catalog revision', async () => vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [ - { id: 'quality', displayName: '高质量' }, - { id: 'fast', displayName: '快速' }, + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'fast', displayName: '快速', agentMode: 'cc' }, ], revision: undefined as unknown as number, }); @@ -664,8 +681,8 @@ test('keeps refreshing when the server omits the catalog revision', async () => vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [ - { id: 'quality', displayName: '高质量' }, - { id: 'vision', displayName: '视觉' }, + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'vision', displayName: '视觉', agentMode: 'codex' }, ], revision: undefined as unknown as number, }); @@ -684,8 +701,8 @@ test('applies a catalog when the revision goes backwards', async () => { vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [ - { id: 'quality', displayName: '高质量' }, - { id: 'vision', displayName: '视觉' }, + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'vision', displayName: '视觉', agentMode: 'codex' }, ], revision: 0, }); @@ -704,8 +721,8 @@ test('keeps the applied catalog when the revision is unchanged', async () => { vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [ - { id: 'quality', displayName: '高质量' }, - { id: 'vision', displayName: '视觉' }, + { id: 'quality', displayName: '高质量', agentMode: 'codex' }, + { id: 'vision', displayName: '视觉', agentMode: 'codex' }, ], revision: 1, }); diff --git a/docs/project-memory/plans/【实施计划】AGC模型Agent模式绑定与Claude Code执行器-2026-09-30.md b/docs/project-memory/plans/【实施计划】AGC模型Agent模式绑定与Claude Code执行器-2026-09-30.md new file mode 100644 index 000000000..968b219cb --- /dev/null +++ b/docs/project-memory/plans/【实施计划】AGC模型Agent模式绑定与Claude Code执行器-2026-09-30.md @@ -0,0 +1,47 @@ +# 【实施计划】AGC模型Agent模式绑定与Claude Code执行器 + +| 字段 | 值 | +| --------- | --- | +| Milestone | `docs/project-memory/plans/【里程碑】AGC模型Agent模式绑定与Claude Code执行器-2026-09-30.md` | +| Status | ready | +| Owner | Codex | + +## 修改边界 + +- 允许修改: + - `server-rs/crates/module-runtime`、`shared-contracts`、`api-server`、SpacetimeDB JSON 目录读写相关测试。 + - `apps/admin-web` AGC 模型目录页面、类型和测试。 + - `apps/ai-game-creator-shell` 客户端模型目录/选择、Tauri 配置、Agent Runtime 与 Claude Agent SDK sidecar/adapter、测试。 + - 当前 AGC 模型目录/Runtime 权威文档和共享记忆(仅写入最终稳定结论)。 +- 明确不修改: + - 现有 Codex app-server 协议实现的行为与私有凭据。 + - SpacetimeDB 表结构、migration、生成绑定(目录 JSON 载荷兼容即可)。 + - 与本功能无关的工作树已有修改。 + +## 实现顺序 + +1. 先补齐目录领域字段、默认/兼容反序列化、后台 DTO/API/admin-web 编辑与测试。 +2. 扩展公开模型摘要和客户端缓存/选择,将后台 `codex`/`cc` 映射为内部执行模式并修复设置保存覆盖问题。 +3. 新增 Claude Agent SDK sidecar 执行器:隔离环境、stream-json 输入输出、模型/Anthropic 配置、MCP 配置、文本与工具事件、超时取消和进程回收;不修改 Codex 执行器。 +4. 在 Runtime/DirectProject 分发处按模型绑定路由;补齐 cc 的配置检查、错误分类、客户端状态展示和回归测试。 +5. 运行定向验证、隔离真实 smoke,回写文档证据;若发现合同变化,先回到主规范再继续。 + +## 验证命令 + +1. `npm run check:encoding` +2. `git diff --check` +3. `npm --prefix apps/admin-web run test -- --run src/pages/AdminAgcModelsPage.test.tsx` +4. `npm --prefix apps/admin-web run typecheck`(以仓库实际 script 为准) +5. `cargo test -p module-runtime agc_models` +6. `cargo test -p api-server agc_model` +7. `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml` 的配置/Claude/Direct 定向测试 +8. `npm --prefix apps/ai-game-creator-shell run test -- --run` 与类型检查(按实际 script 收窄) +9. 隔离临时项目运行 Claude Code 文本、受控 MCP、取消/回收 smoke + +## 风险与回滚点 + +- Claude Agent SDK sidecar 的 stream-json 事件或输入格式变化:adapter 只接受白名单事件,失败关闭;可将模型绑定回 `codex`,不影响现有链路。 +- 后台目录新增字段与旧 JSON:使用缺省 `codex`,保存时写回字段;若兼容测试失败,保留目录载荷原格式并只在读投影填默认。 +- 客户端模型选择与 Agent 模式不同步:选择命令以模型目录快照的 `agentMode` 为输入并做枚举校验;同步失败不得切换活动回合。 +- Claude 原生工具越权:默认只接入受控 MCP,禁止原生 Bash/写文件;任何未经宿主确认的工具请求转失败。 +- 取消/进程归属不确定:保持失败关闭,不复用旧 cc 进程;Codex 模式继续走原有执行器。 diff --git a/docs/project-memory/plans/【里程碑】AGC模型Agent模式绑定与Claude Code执行器-2026-09-30.md b/docs/project-memory/plans/【里程碑】AGC模型Agent模式绑定与Claude Code执行器-2026-09-30.md new file mode 100644 index 000000000..b6bce963c --- /dev/null +++ b/docs/project-memory/plans/【里程碑】AGC模型Agent模式绑定与Claude Code执行器-2026-09-30.md @@ -0,0 +1,51 @@ +# 【里程碑】AGC模型Agent模式绑定与Claude Code执行器 + +| 字段 | 值 | +| ----------- | --- | +| Version | 1.0 | +| Status | completed | +| Date | 2026-09-30 | +| Parent Spec | `docs/technical/【技术方案】AGC后台模型别名与对话选择-2026-09-05.md` | + +## 目标 + +让后台维护的每个 AGC 模型同时决定执行 Agent:默认使用 Codex,显式绑定 `cc` 时由 AGC 客户端调用 Claude Code,并保持宿主工具与回合边界。 + +## 范围 + +- AGC 模型目录、后台完整 DTO、客户端公开模型摘要增加 `agentMode`。 +- `codex` / `cc` 的绑定校验、旧目录兼容和客户端模型选择同步。 +- AGC 客户端新增 Claude Code 执行器,使用 Anthropic 接入、Claude Code stream-json 和受控 MCP。 +- 文本回复、受控只读/项目工具调用、流式活动、取消/超时、进程回收和错误分类。 +- Codex 原有模式、provider 模式、自定义模型和后台 revision/权限合同保持兼容。 + +## 不在范围内 + +- 删除或改写现有 Codex app-server。 +- 将本机 `cc`(可能是 C 编译器别名)覆盖为 Claude Code 命令。 +- 放开 Claude Code 原生 Bash、任意文件写入、编辑器执行、试玩、付费工具或未登记 MCP。 +- SpacetimeDB 表结构字段变更;目录继续存储在现有 JSON 载荷中。 +- 为 Claude Code 增加新的公开 API 或把 Anthropic 协议伪装为 OpenAI Responses。 + +## 依赖与前置条件 + +- Claude Agent SDK sidecar 在开发/运行环境可发现,并支持 `--print --input-format stream-json --output-format stream-json`。 +- 中转可处理 Claude Code 使用的 Anthropic Messages 请求,模型 ID 与后台目录一致。 +- AGC 现有 DirectProject MCP loopback、项目身份、权限、预算和执行租约可复用。 +- 先完成本里程碑的主规范与实施计划评审,再进入代码实现。 + +## 验收标准 + +- [x] 后台新增模型默认 `codex`,可选择 `cc`,非法值和缺失值分别按约定拒绝/兼容。 +- [x] 后台 GET/PUT、公开模型目录和客户端选择保持 `agentMode` 一致,旧目录/旧客户端兼容为 Codex。 +- [ ] 选择 `codex` 模型仍启动现有 Codex app-server;选择 `cc` 模型启动 Claude Agent SDK sidecar,不发送 Codex JSON-RPC。 +- [x] `cc` 文本回合成功,模型 ID、Anthropic 地址和凭据不出现在用户提示、日志和错误正文。 +- [x] `cc` 只能通过 AGC 受控 MCP 使用已登记工具;拒绝越界根目录、未授权工具、原生 Bash/写文件和付费工具。 +- [x] `cc` 超时、取消、sidecar 缺失、非零退出、坏 JSON、MCP 断连都能回收进程并释放回合/租约。 +- [x] Codex、provider、自定义 LLM 和模型选择回归通过。 + +## 证据要求 + +- 自动化:module-runtime/API/admin-web/AGC Tauri 定向测试;前端类型检查;Rust 编译与测试。 +- 运行时:隔离临时项目的 Claude Code 中转 smoke,至少文本 + 一次受控 MCP 工具 + 取消/进程退出。 +- 边界:旧目录反序列化、非法 agentMode、模型切换绑定、配置保存不覆盖模式、无 `claude`、Anthropic 地址缺失、Codex 回归。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 6d3188f54..84b49623e 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -9171,3 +9171,9 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 决策(弹窗层级):个人中心的独立弹窗(`SquareImageCropModal`、`PlatformProfileModalShell` 两个壳层)改回 `UnifiedModal` 默认的页面级 portal(挂到 `document.body`),不再用 `portal={false}` 内联渲染;层级由现有 `z-[80]` 决定,仍在鉴权 / 法务 / 预览等 `z-[110]` 以上弹窗之下。`PlatformStatusDialog` 保持内联(承载运行态内嵌遮罩,不在本次范围)。 - 影响范围:`src/components/platform-entry/PlatformEntryActiveFlowShell.tsx`、`src/components/creation-home/ClientDownloadEntry.tsx`、`src/components/common/SquareImageCropModal.tsx`、`src/components/platform-entry/PlatformProfileModalShell.tsx`、`src/index.css`。 - 验证:本地 Vite + Chromium 真机尺寸复核(320/360/375/390/414/480/639/640/768/1024):顶栏在 320–768 全部单行、右侧留白 16px;弹窗内「上传 / 保存」`elementFromPoint` 命中自身而非底部菜单,overlay 父节点为 `BODY`。定向 Vitest(`ClientDownloadEntry`、`PlatformProfileModalShell`、`PlatformActiveProfileView`、`PlatformEntryActiveFlowShell`、`UnifiedModalPortalTheme`)与 `prettier --check`、`eslint`、`tsc`、`check:encoding` 通过。 + +## 2026-09-30 AGC 模型绑定 Agent 执行模式 + +- 后台 AGC 模型目录新增 `agentMode`,只允许 `codex` / `cc`,缺失的历史目录按 `codex` 兼容;模型选择返回的公开摘要同步携带该绑定。 +- 客户端把后台 `codex` 映射到现有 Codex app-server,把 `cc` 映射到独立 Claude Code CLI adapter;不通过替换 Codex JSON-RPC 可执行文件实现。 +- Claude Code 只使用隔离环境和 AGC loopback MCP,禁用原生工具;取消通过独立 Direct 回合进程树回收处理。Codex、provider 和自定义 Responses 链路保持原路径。 diff --git a/docs/technical/【技术方案】AGC后台模型别名与对话选择-2026-09-05.md b/docs/technical/【技术方案】AGC后台模型别名与对话选择-2026-09-05.md index 418fc565d..2ce2cb439 100644 --- a/docs/technical/【技术方案】AGC后台模型别名与对话选择-2026-09-05.md +++ b/docs/technical/【技术方案】AGC后台模型别名与对话选择-2026-09-05.md @@ -52,6 +52,16 @@ - 项目右侧对话的模型选择器在对话进行中保持可交互:切换模型只写回客户端配置并作用于下一轮,当前回合不受影响;发送按钮仍由 `controlBusy` / `modelReady` 把关。 - 自定义开关关闭时,设置页不包含模型管理或模型选择,使用官方代理锁定。 + +## 模型绑定 Agent 执行模式 + +- 后台 AGC 模型目录每项新增 `agentMode`,只接受 `codex` 与 `cc`;缺少该字段的历史目录按 `codex` 解释,后台新增模型默认 `codex`。 +- `codex` 仍表示现有 AGC Codex app-server 执行链路;`cc` 表示 AGC 客户端启动随包的 Claude Agent SDK sidecar,模型的 `modelId` 原样作为 Claude Agent SDK 的模型标识,不把 Claude 协议伪装成 OpenAI Responses。sidecar 随 AGC 安装包携带 SDK 及匹配平台的 Claude Code runtime,用户不需要预先安装 `claude` 命令。 +- `/api/llm/models` 的启用模型摘要返回 `agentMode`,后台完整目录、管理 DTO 和客户端目录保持同一绑定快照。客户端在选择模型时同时持久化模型 ID 与执行模式;默认模型变化、模型被停用或目录刷新回退时一并更新执行模式。 +- 客户端设置保存不得把后台模型绑定覆盖回 Codex。旧客户端配置缺少执行模式时继续按 Codex 运行;旧后台响应缺少 `agentMode` 时客户端按 Codex 兼容。 +- `cc` 执行器必须使用 Claude Agent SDK 的 Anthropic 接入方式,通过隔离的 sidecar 运行环境和受控 MCP 桥接 AGC 工具;不得开放 Claude Code 原生 Bash、任意写文件、付费或编辑器执行能力绕过 AGC 宿主门禁。当前 Codex 模式、provider 模式和自定义 LLM 行为不因新增绑定改变。 +- `cc` 模式的进程失败、超时、取消、MCP 断连和无效输出均按失败关闭,不能留下活动回合、执行租约或未确认的子进程;无法找到或启动 Claude Code 时给出可行动错误。 + ## 验收 - 空目录 + 上游可达:启动后目录自动生成(`id` 为模型名 slug、`alias`/`modelId` 为上游原名、`enabled` 全为真、默认项为排序后第一项),`revision` 自增一次,`GET /api/llm/models` 的 `displayName` 就是上游原名,界面不出现任何内置模型名。 diff --git a/package-lock.json b/package-lock.json index 3f2c813ad..ae93877b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -99,6 +99,7 @@ "name": "@genarrative/ai-game-creator-shell", "version": "0.1.67", "dependencies": { + "@anthropic-ai/claude-agent-sdk": "0.3.285", "@cubone/react-file-manager": "^1.35.0", "@genarrative/image-canvas-core": "0.1.0", "@genarrative/image-canvas-react": "0.1.0", @@ -241,6 +242,156 @@ "node": ">=6.0.0" } }, + "node_modules/@anthropic-ai/claude-agent-sdk": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.285.tgz", + "integrity": "sha512-e98yZH3cWjQ2nSXGxOcx5BrEqG9Y0+CoSoVIN1BaypxT7yGVShdhhH8ROYcJZWqjgiTFyTaN56JQSPJAjarOSg==", + "license": "SEE LICENSE IN README.md", + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.285", + "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.285", + "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.285" + }, + "peerDependencies": { + "@anthropic-ai/sdk": ">=0.93.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "zod": "^4.0.0" + } + }, + "node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.285.tgz", + "integrity": "sha512-2h6iCTH5uPYKA9jBXB7Of0dXZ5hUdu88VnXq9u50AFfMnusy+w83dKgpu+TD+mWiO+mbvMdi1ZsGaoI8HPFTXQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.285.tgz", + "integrity": "sha512-r9ulSFGRV+I5ztFvUBGoLCDGM4cJNQcZ/5+WWFCVaVj55FYB8wVcFA1tXvKwXfWYgYgnuglDRgPwQktsNAisPQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.285.tgz", + "integrity": "sha512-YbYXqKGtyqpyqY92U8TRcKdrsnO38Ys6oO8aSBqugQyILcv9JEau8/Gtsw1e9cbkGyuNiUP2e2HIiwK3wMa9mg==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.285.tgz", + "integrity": "sha512-EgsXJQJpspdSEJSwNl3tWILqJSmVoYje1c2gleHQrQAybNlvk9wx0EXoKrn55NjfoXW+qV1pWL9SeYa1QEuGuQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.285.tgz", + "integrity": "sha512-yVxQ3Xsmme5M9iXnpVQKaBxhGXktl2m2f19fEig30BblvhhRTjF/H1PY4SCA7g9V1D8Ety+0SKMhGZTca4WcWQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.285.tgz", + "integrity": "sha512-3i2Pt5of+uqwWzrKzg5Duk3DQSKVkRqm4j9Nfy002XlV20/+z5fviHDjb/3urbI6dE9VYPVVb9L/dKm7uVOcrg==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.285.tgz", + "integrity": "sha512-7vJftvSY9KIt1GCrRipwDAzHzoMGkVuUPuErXkHCf2mjMNZt2YTnAbvMWxx2lV6wyfSzLMgxSJjKsikKYgL91A==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.285.tgz", + "integrity": "sha512-mzHxNJsKDJDKNbGkPZU7FFqqWqQAXbTyuDRyGeeVLCCxy89mfyvIOC4ko2yiPXsIFsK+F2rbVnFOBhsl2dtaow==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.129.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.129.0.tgz", + "integrity": "sha512-MH7LB20kNpGLUpPTe2OG5XvL/KhX8HUO9XyBaFjgFk6TLS4Xjt0VPIuMKywe7POKKPslX+QxSlVok9e+8kG5Nw==", + "license": "MIT", + "peer": true, + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -5222,6 +5373,19 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@hono/node-server": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.3.tgz", + "integrity": "sha512-TA//nWMqPhbfdfneACk6t5a9eqbS9lABEPyKn0/xZTah3H3U2XaVg85rJFl0/Fyit0I552YDHgXGVSf3GwqbUw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -5955,6 +6119,89 @@ } } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.31.0.tgz", + "integrity": "sha512-UvTMgnNlnIBO/22ob2RcVGDlcvOslQs8T59+FTGdA0L27a39fdGF/EDETNtDVK4DZGpwomlsYpRdA8UXcVL/pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7548,6 +7795,13 @@ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==" }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT", + "peer": true + }, "node_modules/@tailwindcss/node": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", @@ -9591,6 +9845,58 @@ "readable-stream": "^3.4.0" } }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -9747,7 +10053,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -9756,6 +10061,23 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -10331,11 +10653,24 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -10346,6 +10681,26 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/core-js-compat": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", @@ -10365,6 +10720,24 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -10792,7 +11165,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -11283,7 +11655,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -11300,7 +11671,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -11662,6 +12032,29 @@ "bare-events": "^2.7.0" } }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "peer": true, + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/exec-async": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz", @@ -12554,6 +12947,254 @@ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "license": "Apache-2.0" }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -12612,6 +13253,13 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense", + "peer": true + }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", @@ -12633,7 +13281,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true, "funding": [ { "type": "github", @@ -12964,6 +13611,16 @@ "node": ">= 6" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -13115,7 +13772,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -13158,7 +13814,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -13275,7 +13930,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -13345,7 +13999,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -13494,6 +14147,16 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/hono": { + "version": "4.13.11", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.11.tgz", + "integrity": "sha512-/SMX/RQNJn7oNmFwH6DtwDqcrzZU2otm1FD6OJe2cWF6cfy/F8hq0XVBv7xW3FTJshRQwuBnXHDq2kNsdZnshg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -13672,6 +14335,23 @@ } } }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -13797,6 +14477,26 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ip-address": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -13960,6 +14660,13 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT", + "peer": true + }, "node_modules/is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", @@ -14262,6 +14969,16 @@ "dev": true, "license": "MIT" }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14360,12 +15077,33 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause", + "peer": true + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -15422,7 +16160,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -15721,12 +16458,39 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -17480,12 +18244,24 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-treeify": { "version": "1.1.33", "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", @@ -17521,7 +18297,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -17828,6 +18603,17 @@ "node": "20 || >=22" } }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -17888,6 +18674,16 @@ "node": ">= 6" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -18203,6 +18999,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -18262,6 +19076,23 @@ "qrcode-terminal": "bin/qrcode-terminal.js" } }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -18306,6 +19137,22 @@ "node": ">= 0.6" } }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -19044,7 +19891,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -19207,6 +20053,23 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/rrweb-cssom": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", @@ -19267,8 +20130,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sax": { "version": "1.6.0", @@ -19477,6 +20339,82 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -19680,6 +20618,17 @@ "node": ">=8" } }, + "node_modules/standardwebhooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", + "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -20390,6 +21339,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT", + "peer": true + }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -20560,6 +21516,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "peer": true, + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -22714,8 +23730,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "2.4.3", @@ -22974,12 +23989,21 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peer": true, + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, "node_modules/zustand": { "version": "5.0.14", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", @@ -23151,6 +24175,79 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "@anthropic-ai/claude-agent-sdk": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.285.tgz", + "integrity": "sha512-e98yZH3cWjQ2nSXGxOcx5BrEqG9Y0+CoSoVIN1BaypxT7yGVShdhhH8ROYcJZWqjgiTFyTaN56JQSPJAjarOSg==", + "requires": { + "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.285", + "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.285", + "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.285", + "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.285" + } + }, + "@anthropic-ai/claude-agent-sdk-darwin-arm64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.285.tgz", + "integrity": "sha512-2h6iCTH5uPYKA9jBXB7Of0dXZ5hUdu88VnXq9u50AFfMnusy+w83dKgpu+TD+mWiO+mbvMdi1ZsGaoI8HPFTXQ==", + "optional": true + }, + "@anthropic-ai/claude-agent-sdk-darwin-x64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.285.tgz", + "integrity": "sha512-r9ulSFGRV+I5ztFvUBGoLCDGM4cJNQcZ/5+WWFCVaVj55FYB8wVcFA1tXvKwXfWYgYgnuglDRgPwQktsNAisPQ==", + "optional": true + }, + "@anthropic-ai/claude-agent-sdk-linux-arm64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.285.tgz", + "integrity": "sha512-YbYXqKGtyqpyqY92U8TRcKdrsnO38Ys6oO8aSBqugQyILcv9JEau8/Gtsw1e9cbkGyuNiUP2e2HIiwK3wMa9mg==", + "optional": true + }, + "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.285.tgz", + "integrity": "sha512-EgsXJQJpspdSEJSwNl3tWILqJSmVoYje1c2gleHQrQAybNlvk9wx0EXoKrn55NjfoXW+qV1pWL9SeYa1QEuGuQ==", + "optional": true + }, + "@anthropic-ai/claude-agent-sdk-linux-x64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.285.tgz", + "integrity": "sha512-yVxQ3Xsmme5M9iXnpVQKaBxhGXktl2m2f19fEig30BblvhhRTjF/H1PY4SCA7g9V1D8Ety+0SKMhGZTca4WcWQ==", + "optional": true + }, + "@anthropic-ai/claude-agent-sdk-linux-x64-musl": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.285.tgz", + "integrity": "sha512-3i2Pt5of+uqwWzrKzg5Duk3DQSKVkRqm4j9Nfy002XlV20/+z5fviHDjb/3urbI6dE9VYPVVb9L/dKm7uVOcrg==", + "optional": true + }, + "@anthropic-ai/claude-agent-sdk-win32-arm64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.285.tgz", + "integrity": "sha512-7vJftvSY9KIt1GCrRipwDAzHzoMGkVuUPuErXkHCf2mjMNZt2YTnAbvMWxx2lV6wyfSzLMgxSJjKsikKYgL91A==", + "optional": true + }, + "@anthropic-ai/claude-agent-sdk-win32-x64": { + "version": "0.3.285", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.285.tgz", + "integrity": "sha512-mzHxNJsKDJDKNbGkPZU7FFqqWqQAXbTyuDRyGeeVLCCxy89mfyvIOC4ko2yiPXsIFsK+F2rbVnFOBhsl2dtaow==", + "optional": true + }, + "@anthropic-ai/sdk": { + "version": "0.129.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.129.0.tgz", + "integrity": "sha512-MH7LB20kNpGLUpPTe2OG5XvL/KhX8HUO9XyBaFjgFk6TLS4Xjt0VPIuMKywe7POKKPslX+QxSlVok9e+8kG5Nw==", + "peer": true, + "requires": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + } + }, "@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -26525,6 +27622,7 @@ "@genarrative/ai-game-creator-shell": { "version": "file:apps/ai-game-creator-shell", "requires": { + "@anthropic-ai/claude-agent-sdk": "0.3.285", "@cubone/react-file-manager": "^1.35.0", "@genarrative/image-canvas-core": "0.1.0", "@genarrative/image-canvas-react": "0.1.0", @@ -26684,6 +27782,13 @@ "@hapi/hoek": "^9.0.0" } }, + "@hono/node-server": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.3.tgz", + "integrity": "sha512-TA//nWMqPhbfdfneACk6t5a9eqbS9lABEPyKn0/xZTah3H3U2XaVg85rJFl0/Fyit0I552YDHgXGVSf3GwqbUw==", + "peer": true, + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -27129,6 +28234,60 @@ "lexical": "0.47.0" } }, + "@modelcontextprotocol/sdk": { + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.31.0.tgz", + "integrity": "sha512-UvTMgnNlnIBO/22ob2RcVGDlcvOslQs8T59+FTGdA0L27a39fdGF/EDETNtDVK4DZGpwomlsYpRdA8UXcVL/pw==", + "peer": true, + "requires": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "dependencies": { + "ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "peer": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "peer": true, + "requires": { + "ajv": "^8.0.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "peer": true + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -28003,6 +29162,12 @@ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==" }, + "@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "peer": true + }, "@tailwindcss/node": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", @@ -29362,6 +30527,40 @@ "readable-stream": "^3.4.0" } }, + "body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "peer": true, + "requires": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "dependencies": { + "content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "peer": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "peer": true, + "requires": { + "ee-first": "1.1.1" + } + } + } + }, "bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -29459,12 +30658,21 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "requires": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "peer": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -29837,17 +31045,34 @@ } } }, + "content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "peer": true + }, "content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "peer": true + }, + "cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "peer": true + }, "core-js-compat": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", @@ -29861,6 +31086,16 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "peer": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -30155,7 +31390,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -30516,8 +31750,7 @@ "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, "es-errors": { "version": "1.3.0", @@ -30528,7 +31761,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "requires": { "es-errors": "^1.3.0" } @@ -30775,6 +32007,21 @@ "bare-events": "^2.7.0" } }, + "eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "peer": true, + "requires": { + "eventsource-parser": "^3.0.1" + } + }, + "eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "peer": true + }, "exec-async": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz", @@ -31347,6 +32594,168 @@ "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==" }, + "express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "peer": true, + "requires": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "dependencies": { + "accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "peer": true, + "requires": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + } + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "peer": true + }, + "finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "peer": true, + "requires": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + } + }, + "fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "peer": true + }, + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "peer": true + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "peer": true, + "requires": { + "mime-db": "^1.54.0" + } + }, + "negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "peer": true, + "requires": { + "content-type": "^2.1.0" + }, + "dependencies": { + "content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "peer": true + } + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "peer": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "peer": true, + "requires": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + } + }, + "serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "peer": true, + "requires": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "peer": true + } + } + }, + "express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", + "peer": true, + "requires": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -31399,6 +32808,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "peer": true + }, "fast-string-truncated-width": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", @@ -31417,8 +32832,7 @@ "fast-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==" }, "fast-wrap-ansi": { "version": "0.2.2", @@ -31658,6 +33072,12 @@ "mime-types": "^2.1.12" } }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "peer": true + }, "fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -31748,7 +33168,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -31777,7 +33196,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "requires": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -31860,8 +33278,7 @@ "gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "graceful-fs": { "version": "4.2.11", @@ -31906,8 +33323,7 @@ "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, "has-tostringtag": { "version": "1.0.2", @@ -32013,6 +33429,12 @@ } } }, + "hono": { + "version": "4.13.11", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.11.tgz", + "integrity": "sha512-/SMX/RQNJn7oNmFwH6DtwDqcrzZU2otm1FD6OJe2cWF6cfy/F8hq0XVBv7xW3FTJshRQwuBnXHDq2kNsdZnshg==", + "peer": true + }, "hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -32122,6 +33544,15 @@ "@babel/runtime": "^7.29.2" } }, + "iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "peer": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -32204,6 +33635,18 @@ "loose-envify": "^1.0.0" } }, + "ip-address": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", + "peer": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "peer": true + }, "is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -32301,6 +33744,12 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "peer": true + }, "is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", @@ -32508,6 +33957,12 @@ "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==", "dev": true }, + "jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "peer": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -32580,12 +34035,28 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "peer": true, + "requires": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "peer": true + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -33218,8 +34689,7 @@ "math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, "md5": { "version": "2.3.0", @@ -33434,11 +34904,23 @@ "@types/mdast": "^4.0.0" } }, + "media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "peer": true + }, "memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, + "merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "peer": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -34571,8 +36053,13 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "peer": true }, "object-treeify": { "version": "1.1.33", @@ -34597,7 +36084,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -34813,6 +36299,12 @@ } } }, + "path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "peer": true + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -34855,6 +36347,12 @@ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true }, + "pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "peer": true + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -35068,6 +36566,16 @@ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==" }, + "proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "peer": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, "psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -35110,6 +36618,16 @@ "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", "dev": true }, + "qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "peer": true, + "requires": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + } + }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -35135,6 +36653,18 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, + "raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "peer": true, + "requires": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -35609,8 +37139,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "require-main-filename": { "version": "2.0.0", @@ -35722,6 +37251,19 @@ "fsevents": "~2.3.2" } }, + "router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "peer": true, + "requires": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + } + }, "rrweb-cssom": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", @@ -35752,8 +37294,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { "version": "1.6.0", @@ -35901,6 +37442,54 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==" }, + "side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "peer": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "peer": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "peer": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "peer": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, "siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -36034,6 +37623,16 @@ } } }, + "standardwebhooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", + "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "peer": true, + "requires": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -36535,6 +38134,12 @@ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==" }, + "ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "peer": true + }, "ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -36644,6 +38249,40 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "peer": true, + "requires": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "dependencies": { + "content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "peer": true + }, + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "peer": true + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "peer": true, + "requires": { + "mime-db": "^1.54.0" + } + } + } + }, "typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -37717,8 +39356,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-file-atomic": { "version": "2.4.3", @@ -37889,8 +39527,14 @@ "zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + }, + "zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "peer": true, + "requires": {} }, "zustand": { "version": "5.0.14", diff --git a/server-rs/crates/api-server/src/agc_models.rs b/server-rs/crates/api-server/src/agc_models.rs index f0634b0df..391e32f5f 100644 --- a/server-rs/crates/api-server/src/agc_models.rs +++ b/server-rs/crates/api-server/src/agc_models.rs @@ -8,7 +8,7 @@ use axum::{ http::StatusCode, }; use module_runtime::{ - AGC_MODEL_CATALOG_CONFLICT, AGC_MODEL_CATALOG_NOT_INITIALIZED, AgcModelCatalog, + AGC_MODEL_CATALOG_CONFLICT, AGC_MODEL_CATALOG_NOT_INITIALIZED, AgcAgentMode, AgcModelCatalog, }; use shared_contracts::admin::{AdminAgcModel, AdminAgcModelCatalog}; use spacetime_client::SpacetimeClientError; @@ -228,19 +228,26 @@ pub async fn admin_save_agc_models( ) -> Result, AppError> { // 目录只来自上游同步:未初始化时后台写入同样失败关闭,避免出现第二条绕过同步的写入口。 load_catalog(&state).await?; - let catalog = AgcModelCatalog { - revision: payload.revision, - default_model_id: payload.default_model_id, - models: payload - .models - .into_iter() - .map(|m| module_runtime::AgcModel { + let models = payload + .models + .into_iter() + .map(|m| { + let agent_mode = AgcAgentMode::parse(&m.agent_mode).map_err(|message| { + AppError::from_status(StatusCode::BAD_REQUEST).with_message(message) + })?; + Ok(module_runtime::AgcModel { id: m.id, alias: m.alias, model_id: m.model_id, enabled: m.enabled, + agent_mode, }) - .collect(), + }) + .collect::, AppError>>()?; + let catalog = AgcModelCatalog { + revision: payload.revision, + default_model_id: payload.default_model_id, + models, }; catalog .validate() @@ -275,6 +282,7 @@ fn catalog_dto(catalog: AgcModelCatalog) -> AdminAgcModelCatalog { alias: m.alias, model_id: m.model_id, enabled: m.enabled, + agent_mode: m.agent_mode.as_str().to_string(), }) .collect(), } @@ -285,6 +293,24 @@ mod tests { use super::*; use serde_json::json; + #[test] + fn catalog_dto_maps_agent_mode_and_legacy_admin_payload_defaults_to_codex() { + let mut catalog = + AgcModelCatalog::from_upstream_models(vec!["model-a".to_string()], 4).unwrap(); + catalog.models[0].agent_mode = AgcAgentMode::Cc; + let payload = serde_json::to_value(catalog_dto(catalog)).unwrap(); + assert_eq!(payload["models"][0]["agentMode"], "cc"); + + let legacy: AdminAgcModel = serde_json::from_value(json!({ + "id": "model-a", + "alias": "model-a", + "modelId": "model-a", + "enabled": true + })) + .unwrap(); + assert_eq!(legacy.agent_mode, "codex"); + } + #[test] fn upstream_model_names_come_from_pricing_data_array() { let payload = json!({ diff --git a/server-rs/crates/api-server/src/llm/mod.rs b/server-rs/crates/api-server/src/llm/mod.rs index 025823e3f..439ddbd7e 100644 --- a/server-rs/crates/api-server/src/llm/mod.rs +++ b/server-rs/crates/api-server/src/llm/mod.rs @@ -48,7 +48,7 @@ mod model_catalog_tests { // 客户端拿到稳定标识 + 别名(别名就是上游原始模型名),实际模型名不下发。 assert_eq!( payload["models"], - json!([{"id": "gpt-5-6-sol", "displayName": "gpt-5.6-sol"}]) + json!([{"id": "gpt-5-6-sol", "displayName": "gpt-5.6-sol", "agentMode": "codex"}]) ); assert_eq!(payload["defaultModelId"], "gpt-5-6-sol"); assert_eq!(payload["revision"], json!(7)); @@ -202,6 +202,7 @@ fn public_model_catalog(catalog: module_runtime::AgcModelCatalog) -> LlmModelsRe id: model.id, // 初始目录里别名就是上游原始模型名(不再填“高质量/快速”这类人工别名)。 display_name: model.alias, + agent_mode: model.agent_mode.as_str().to_string(), }) .collect(), revision: catalog.revision, diff --git a/server-rs/crates/module-runtime/src/agc_models.rs b/server-rs/crates/module-runtime/src/agc_models.rs index 216dc1aaf..16cb030a8 100644 --- a/server-rs/crates/module-runtime/src/agc_models.rs +++ b/server-rs/crates/module-runtime/src/agc_models.rs @@ -12,6 +12,36 @@ pub const AGC_MODEL_ID_MAX_BYTES: usize = 64; /// 目录项数上限,与后台「AGC 模型」页的新增上限保持一致。 pub const AGC_MODEL_CATALOG_MAX_MODELS: usize = 32; +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum AgcAgentMode { + Codex, + Cc, +} + +impl Default for AgcAgentMode { + fn default() -> Self { + Self::Codex + } +} + +impl AgcAgentMode { + pub const fn as_str(self) -> &'static str { + match self { + Self::Codex => "codex", + Self::Cc => "cc", + } + } + + pub fn parse(value: &str) -> Result { + match value { + "codex" => Ok(Self::Codex), + "cc" => Ok(Self::Cc), + _ => Err("Agent 模式只允许 codex 或 cc".into()), + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct AgcModel { @@ -19,6 +49,9 @@ pub struct AgcModel { pub alias: String, pub model_id: String, pub enabled: bool, + /// 绑定 AGC 客户端实际启动的 Agent,存量目录缺失时兼容为 Codex。 + #[serde(default)] + pub agent_mode: AgcAgentMode, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -58,6 +91,7 @@ impl AgcModelCatalog { alias: model_id.clone(), model_id, enabled: true, + agent_mode: AgcAgentMode::default(), }); } let default_model_id = entries @@ -195,6 +229,7 @@ mod tests { alias: alias.into(), model_id: model_id.into(), enabled: true, + agent_mode: AgcAgentMode::default(), } } @@ -293,6 +328,31 @@ mod tests { assert!(catalog.validate().is_err()); } + #[test] + fn legacy_catalog_defaults_missing_agent_mode_to_codex() { + let catalog: AgcModelCatalog = serde_json::from_str( + r#"{"revision":4,"defaultModelId":"quality","models":[{"id":"quality","alias":"高质量","modelId":"gpt-6-astra","enabled":true}]}"#, + ) + .expect("legacy catalog should remain readable"); + + assert_eq!(catalog.models[0].agent_mode, AgcAgentMode::Codex); + assert_eq!(catalog.models[0].agent_mode.as_str(), "codex"); + } + + #[test] + fn catalog_agent_mode_accepts_codex_and_cc_only() { + let mut catalog = + AgcModelCatalog::from_upstream_models(upstream(&["model-a", "model-b"]), 0).unwrap(); + catalog.models[0].agent_mode = AgcAgentMode::Cc; + assert!(catalog.validate().is_ok()); + assert_eq!(catalog.models[0].agent_mode.as_str(), "cc"); + + let invalid: Result = serde_json::from_str( + r#"{"revision":0,"defaultModelId":"model-a","models":[{"id":"model-a","alias":"model-a","modelId":"model-a","enabled":true,"agentMode":"provider"}]}"#, + ); + assert!(invalid.is_err()); + } + #[test] fn catalog_rejects_duplicate_aliases_and_ids() { let mut catalog = diff --git a/server-rs/crates/shared-contracts/src/admin.rs b/server-rs/crates/shared-contracts/src/admin.rs index e8e0c4637..0b64e8cfd 100644 --- a/server-rs/crates/shared-contracts/src/admin.rs +++ b/server-rs/crates/shared-contracts/src/admin.rs @@ -1488,6 +1488,10 @@ mod tests { } } /// AGC 模型目录仅在后台返回实际模型名。 +fn default_codex_agent_mode() -> String { + "codex".to_string() +} + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct AdminAgcModel { @@ -1495,6 +1499,9 @@ pub struct AdminAgcModel { pub alias: String, pub model_id: String, pub enabled: bool, + /// 旧后台请求缺失时按 Codex 兼容。 + #[serde(default = "default_codex_agent_mode")] + pub agent_mode: String, } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] diff --git a/server-rs/crates/shared-contracts/src/llm.rs b/server-rs/crates/shared-contracts/src/llm.rs index 0565fd25c..0b958f23f 100644 --- a/server-rs/crates/shared-contracts/src/llm.rs +++ b/server-rs/crates/shared-contracts/src/llm.rs @@ -65,6 +65,7 @@ mod tests { pub struct LlmModelSummary { pub display_name: String, pub id: String, + pub agent_mode: String, } #[derive(Debug, serde::Serialize)]