From 21cdadfc1e70a98a89006fa2b4b53d65e2b2be98 Mon Sep 17 00:00:00 2001 From: dawn <90008@klbr.net> Date: Wed, 9 Sep 2026 16:00:14 +0300 Subject: [PATCH] feat(coding-agent): add remote python host support and ui indicators --- packages/ai/src/models.generated.ts | 1325 ++++++++--------- .../.changes/remote-python-host.md | 2 + packages/coding-agent/src/cli/args.ts | 19 + .../coding-agent/src/cli/command-registry.ts | 1 + .../src/cli/daemon-list-format.ts | 7 +- .../src/core/agent-session-config.ts | 6 +- .../src/core/agent-session-services.ts | 2 + .../coding-agent/src/core/agent-session.ts | 14 +- .../src/core/kernel/repl-manager.ts | 3 +- packages/coding-agent/src/core/prompts/rlm.ts | 10 + packages/coding-agent/src/core/sdk.ts | 1 + .../coding-agent/src/core/settings-manager.ts | 10 + .../coding-agent/src/core/system-prompt.ts | 3 + packages/coding-agent/src/main.ts | 13 +- .../src/modes/agent-connection/snapshot.ts | 1 + .../src/modes/agent-connection/types.ts | 2 + .../src/modes/agents-view/agents-view-mode.ts | 15 +- .../src/modes/daemon/daemon-session-list.ts | 2 + .../src/modes/interactive/interactive-mode.ts | 14 +- .../test/agent-session-config.test.ts | 19 + .../test/agent-session-recursion.test.ts | 55 +- .../test/agents-view-mode.test.ts | 535 +++++++ packages/coding-agent/test/args.test.ts | 65 +- .../test/daemon-list-format.test.ts | 103 ++ .../test/daemon-session-list.test.ts | 13 + .../test/interactive-mode-status.test.ts | 50 +- .../test/remote-python-host.test.ts | 79 + .../coding-agent/test/system-prompt.test.ts | 16 + 28 files changed, 1662 insertions(+), 723 deletions(-) create mode 100644 packages/coding-agent/.changes/remote-python-host.md create mode 100644 packages/coding-agent/test/daemon-list-format.test.ts create mode 100644 packages/coding-agent/test/remote-python-host.test.ts create mode 100644 packages/coding-agent/test/system-prompt.test.ts diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index ff0f33240..0403e65f3 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -886,6 +886,41 @@ export const MODELS = { contextWindow: 1050000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "global.openai.gpt-6-astra": { + id: "global.openai.gpt-6-astra", + name: "GPT-6 Astra (Global)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh","minimal":null,"max":"max"}, + input: ["text", "image"], + cost: { + input: 10, + output: 50, + cacheRead: 1, + cacheWrite: 12.5, + }, + contextWindow: 1050000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, + "global.xai.grok-4.6": { + id: "global.xai.grok-4.6", + name: "Grok 4.6 (Global)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + input: ["text", "image"], + cost: { + input: 2, + output: 6, + cacheRead: 0.5, + cacheWrite: 0, + }, + contextWindow: 500000, + maxTokens: 500000, + } satisfies Model<"bedrock-converse-stream">, "google.gemma-3-27b-it": { id: "google.gemma-3-27b-it", name: "Google Gemma 3 27B Instruct", @@ -1525,6 +1560,24 @@ export const MODELS = { contextWindow: 1050000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "openai.gpt-6-astra": { + id: "openai.gpt-6-astra", + name: "GPT-6 Astra", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh","minimal":null,"max":"max"}, + input: ["text", "image"], + cost: { + input: 11, + output: 55, + cacheRead: 1.1, + cacheWrite: 13.75, + }, + contextWindow: 1050000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "openai.gpt-oss-120b": { id: "openai.gpt-oss-120b", name: "gpt-oss-120b", @@ -2009,6 +2062,41 @@ export const MODELS = { contextWindow: 3500000, maxTokens: 16384, } satisfies Model<"bedrock-converse-stream">, + "us.openai.gpt-6-astra": { + id: "us.openai.gpt-6-astra", + name: "GPT-6 Astra (US)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh","minimal":null,"max":"max"}, + input: ["text", "image"], + cost: { + input: 11, + output: 55, + cacheRead: 1.1, + cacheWrite: 13.75, + }, + contextWindow: 1050000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, + "us.xai.grok-4.6": { + id: "us.xai.grok-4.6", + name: "Grok 4.6 (US)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + input: ["text", "image"], + cost: { + input: 2.2, + output: 6.6, + cacheRead: 0.55, + cacheWrite: 0, + }, + contextWindow: 500000, + maxTokens: 500000, + } satisfies Model<"bedrock-converse-stream">, "writer.palmyra-x4-v1:0": { id: "writer.palmyra-x4-v1:0", name: "Palmyra X4", @@ -2987,7 +3075,7 @@ export const MODELS = { provider: "azure-openai-responses", baseUrl: "", reasoning: true, - thinkingLevelMap: {"minimal":null,"xhigh":"xhigh","max":"max","off":null}, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], cost: { input: 10, @@ -3119,39 +3207,39 @@ export const MODELS = { } satisfies Model<"azure-openai-responses">, }, "cerebras": { - "gemma-4-31b": { - id: "gemma-4-31b", - name: "Gemma 4 31B IT", + "gpt-oss-120b": { + id: "gpt-oss-120b", + name: "GPT OSS 120B", api: "openai-completions", provider: "cerebras", baseUrl: "https://api.cerebras.ai/v1", reasoning: true, - input: ["text", "image"], + input: ["text"], cost: { - input: 0.99, - output: 1.49, + input: 0.35, + output: 0.75, cacheRead: 0, cacheWrite: 0, }, contextWindow: 131072, maxTokens: 40960, } satisfies Model<"openai-completions">, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "GPT OSS 120B", + "qwen-3.8-27b": { + id: "qwen-3.8-27b", + name: "Qwen3.8 27B", api: "openai-completions", provider: "cerebras", baseUrl: "https://api.cerebras.ai/v1", reasoning: true, - input: ["text"], + input: ["text", "image"], cost: { - input: 0.35, - output: 0.75, + input: 0.99, + output: 1.49, cacheRead: 0, cacheWrite: 0, }, - contextWindow: 131072, - maxTokens: 40960, + contextWindow: 65536, + maxTokens: 32768, } satisfies Model<"openai-completions">, }, "cloudflare-ai-gateway": { @@ -4172,8 +4260,8 @@ export const MODELS = { cacheRead: 0.26, cacheWrite: 0, }, - contextWindow: 1000000, - maxTokens: 131072, + contextWindow: 1048573, + maxTokens: 262144, } satisfies Model<"anthropic-messages">, "accounts/fireworks/models/glm-5p3-flash": { id: "accounts/fireworks/models/glm-5p3-flash", @@ -4189,7 +4277,7 @@ export const MODELS = { cacheRead: 0.03, cacheWrite: 0, }, - contextWindow: 1000000, + contextWindow: 1048573, maxTokens: 131072, } satisfies Model<"anthropic-messages">, "accounts/fireworks/models/gpt-oss-120b": { @@ -4414,6 +4502,23 @@ export const MODELS = { contextWindow: 1048575, maxTokens: 131072, } satisfies Model<"anthropic-messages">, + "accounts/fireworks/routers/glm-5p3-fast": { + id: "accounts/fireworks/routers/glm-5p3-fast", + name: "GLM 5.3 Fast", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 2.1, + output: 6.6, + cacheRead: 0.39, + cacheWrite: 0, + }, + contextWindow: 1048572, + maxTokens: 262144, + } satisfies Model<"anthropic-messages">, "accounts/fireworks/routers/kimi-k3-fast": { id: "accounts/fireworks/routers/kimi-k3-fast", name: "Kimi K3 Fast", @@ -4440,7 +4545,7 @@ export const MODELS = { api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"}, input: ["text", "image"], @@ -4459,7 +4564,7 @@ export const MODELS = { api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"}, input: ["text", "image"], @@ -4478,7 +4583,7 @@ export const MODELS = { api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsEagerToolInputStreaming":false}, reasoning: true, input: ["text", "image"], @@ -4497,7 +4602,7 @@ export const MODELS = { api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"xhigh":"xhigh","max":"max"}, input: ["text", "image"], @@ -4516,7 +4621,7 @@ export const MODELS = { api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"xhigh":"xhigh","max":"max"}, input: ["text", "image"], @@ -4535,7 +4640,7 @@ export const MODELS = { api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"xhigh":"xhigh","max":"max"}, input: ["text", "image"], @@ -4554,7 +4659,7 @@ export const MODELS = { api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"max":"max"}, input: ["text", "image"], @@ -4573,7 +4678,7 @@ export const MODELS = { api: "anthropic-messages", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"xhigh":"xhigh","max":"max"}, input: ["text", "image"], @@ -4592,7 +4697,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text", "image"], @@ -4611,7 +4716,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text", "image"], @@ -4630,7 +4735,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text", "image"], @@ -4649,7 +4754,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text", "image"], @@ -4668,7 +4773,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null}, input: ["text", "image"], @@ -4687,7 +4792,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -4706,7 +4811,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -4725,7 +4830,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -4744,7 +4849,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -4763,7 +4868,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -4782,7 +4887,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], @@ -4801,7 +4906,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], @@ -4820,7 +4925,7 @@ export const MODELS = { api: "openai-responses", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], @@ -4836,12 +4941,13 @@ export const MODELS = { "gpt-6-astra": { id: "gpt-6-astra", name: "GPT-6 Astra", - api: "openai-responses", + api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, - thinkingLevelMap: {"minimal":null,"xhigh":"xhigh","max":"max","off":null}, + thinkingLevelMap: {"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], cost: { input: 10, @@ -4851,14 +4957,14 @@ export const MODELS = { }, contextWindow: 1050000, maxTokens: 128000, - } satisfies Model<"openai-responses">, + } satisfies Model<"openai-completions">, "grok-4.5": { id: "grok-4.5", name: "Grok 4.5", api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text", "image"], @@ -4877,7 +4983,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text", "image"], @@ -4896,7 +5002,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text", "image"], @@ -4915,7 +5021,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":null,"xhigh":null,"max":"max"}, @@ -4935,7 +5041,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text"], @@ -4954,7 +5060,7 @@ export const MODELS = { api: "openai-completions", provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", - headers: {"User-Agent":"GitHubCopilotChat/0.48.1","Editor-Version":"vscode/1.136.1","Editor-Plugin-Version":"copilot-chat/0.48.1","Copilot-Integration-Id":"vscode-chat"}, + headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"}, compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false}, reasoning: true, input: ["text", "image"], @@ -7595,120 +7701,87 @@ export const MODELS = { } satisfies Model<"mistral-conversations">, }, "moonshotai": { - "kimi-k2-0711-preview": { - id: "kimi-k2-0711-preview", - name: "Kimi K2 0711", - api: "openai-completions", - provider: "moonshotai", - baseUrl: "https://api.moonshot.ai/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: false, - input: ["text"], - cost: { - input: 0.6, - output: 2.5, - cacheRead: 0.15, - cacheWrite: 0, - }, - contextWindow: 131072, - maxTokens: 16384, - } satisfies Model<"openai-completions">, - "kimi-k2-0905-preview": { - id: "kimi-k2-0905-preview", - name: "Kimi K2 0905", + "kimi-k2.6": { + id: "kimi-k2.6", + name: "Kimi K2.6", api: "openai-completions", provider: "moonshotai", baseUrl: "https://api.moonshot.ai/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: false, - input: ["text"], + reasoning: true, + input: ["text", "image"], cost: { - input: 0.6, - output: 2.5, - cacheRead: 0.15, + input: 0.95, + output: 4, + cacheRead: 0.16, cacheWrite: 0, }, contextWindow: 262144, maxTokens: 262144, } satisfies Model<"openai-completions">, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", + "kimi-k2.7-code": { + id: "kimi-k2.7-code", + name: "Kimi K2.7 Code", api: "openai-completions", provider: "moonshotai", baseUrl: "https://api.moonshot.ai/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, reasoning: true, - input: ["text"], + input: ["text", "image"], cost: { - input: 0.6, - output: 2.5, - cacheRead: 0.15, + input: 0.95, + output: 4, + cacheRead: 0.19, cacheWrite: 0, }, contextWindow: 262144, maxTokens: 262144, } satisfies Model<"openai-completions">, - "kimi-k2-thinking-turbo": { - id: "kimi-k2-thinking-turbo", - name: "Kimi K2 Thinking Turbo", + "kimi-k2.7-code-highspeed": { + id: "kimi-k2.7-code-highspeed", + name: "Kimi K2.7 Code HighSpeed", api: "openai-completions", provider: "moonshotai", baseUrl: "https://api.moonshot.ai/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, reasoning: true, - input: ["text"], + input: ["text", "image"], cost: { - input: 1.15, + input: 1.9, output: 8, - cacheRead: 0.15, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k2-turbo-preview": { - id: "kimi-k2-turbo-preview", - name: "Kimi K2 Turbo", - api: "openai-completions", - provider: "moonshotai", - baseUrl: "https://api.moonshot.ai/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: false, - input: ["text"], - cost: { - input: 2.4, - output: 10, - cacheRead: 0.6, + cacheRead: 0.38, cacheWrite: 0, }, contextWindow: 262144, maxTokens: 262144, } satisfies Model<"openai-completions">, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", + "kimi-k3": { + id: "kimi-k3", + name: "Kimi K3", api: "openai-completions", provider: "moonshotai", baseUrl: "https://api.moonshot.ai/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, reasoning: true, + thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":null,"xhigh":null,"max":"max"}, input: ["text", "image"], cost: { - input: 0.6, - output: 3, - cacheRead: 0.1, + input: 3, + output: 15, + cacheRead: 0.3, cacheWrite: 0, }, - contextWindow: 262144, - maxTokens: 262144, + contextWindow: 1048576, + maxTokens: 131072, } satisfies Model<"openai-completions">, + }, + "moonshotai-cn": { "kimi-k2.6": { id: "kimi-k2.6", name: "Kimi K2.6", api: "openai-completions", - provider: "moonshotai", - baseUrl: "https://api.moonshot.ai/v1", + provider: "moonshotai-cn", + baseUrl: "https://api.moonshot.cn/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, reasoning: true, input: ["text", "image"], @@ -7725,8 +7798,8 @@ export const MODELS = { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", api: "openai-completions", - provider: "moonshotai", - baseUrl: "https://api.moonshot.ai/v1", + provider: "moonshotai-cn", + baseUrl: "https://api.moonshot.cn/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, reasoning: true, input: ["text", "image"], @@ -7743,8 +7816,8 @@ export const MODELS = { id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code HighSpeed", api: "openai-completions", - provider: "moonshotai", - baseUrl: "https://api.moonshot.ai/v1", + provider: "moonshotai-cn", + baseUrl: "https://api.moonshot.cn/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, reasoning: true, input: ["text", "image"], @@ -7761,8 +7834,8 @@ export const MODELS = { id: "kimi-k3", name: "Kimi K3", api: "openai-completions", - provider: "moonshotai", - baseUrl: "https://api.moonshot.ai/v1", + provider: "moonshotai-cn", + baseUrl: "https://api.moonshot.cn/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, reasoning: true, thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":null,"xhigh":null,"max":"max"}, @@ -7777,215 +7850,32 @@ export const MODELS = { maxTokens: 131072, } satisfies Model<"openai-completions">, }, - "moonshotai-cn": { - "kimi-k2-0711-preview": { - id: "kimi-k2-0711-preview", - name: "Kimi K2 0711", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, + "openai": { + "gpt-4": { + id: "gpt-4", + name: "GPT-4", + api: "openai-responses", + provider: "openai", + baseUrl: "https://api.openai.com/v1", reasoning: false, input: ["text"], cost: { - input: 0.6, - output: 2.5, - cacheRead: 0.15, + input: 30, + output: 60, + cacheRead: 0, cacheWrite: 0, }, - contextWindow: 131072, - maxTokens: 16384, - } satisfies Model<"openai-completions">, - "kimi-k2-0905-preview": { - id: "kimi-k2-0905-preview", - name: "Kimi K2 0905", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, + contextWindow: 8192, + maxTokens: 8192, + } satisfies Model<"openai-responses">, + "gpt-4-turbo": { + id: "gpt-4-turbo", + name: "GPT-4 Turbo", + api: "openai-responses", + provider: "openai", + baseUrl: "https://api.openai.com/v1", reasoning: false, - input: ["text"], - cost: { - input: 0.6, - output: 2.5, - cacheRead: 0.15, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - input: ["text"], - cost: { - input: 0.6, - output: 2.5, - cacheRead: 0.15, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k2-thinking-turbo": { - id: "kimi-k2-thinking-turbo", - name: "Kimi K2 Thinking Turbo", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - input: ["text"], - cost: { - input: 1.15, - output: 8, - cacheRead: 0.15, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k2-turbo-preview": { - id: "kimi-k2-turbo-preview", - name: "Kimi K2 Turbo", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: false, - input: ["text"], - cost: { - input: 2.4, - output: 10, - cacheRead: 0.6, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - input: ["text", "image"], - cost: { - input: 0.6, - output: 3, - cacheRead: 0.1, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k2.6": { - id: "kimi-k2.6", - name: "Kimi K2.6", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - input: ["text", "image"], - cost: { - input: 0.95, - output: 4, - cacheRead: 0.16, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k2.7-code": { - id: "kimi-k2.7-code", - name: "Kimi K2.7 Code", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - input: ["text", "image"], - cost: { - input: 0.95, - output: 4, - cacheRead: 0.19, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k2.7-code-highspeed": { - id: "kimi-k2.7-code-highspeed", - name: "Kimi K2.7 Code HighSpeed", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - input: ["text", "image"], - cost: { - input: 1.9, - output: 8, - cacheRead: 0.38, - cacheWrite: 0, - }, - contextWindow: 262144, - maxTokens: 262144, - } satisfies Model<"openai-completions">, - "kimi-k3": { - id: "kimi-k3", - name: "Kimi K3", - api: "openai-completions", - provider: "moonshotai-cn", - baseUrl: "https://api.moonshot.cn/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":null,"xhigh":null,"max":"max"}, - input: ["text", "image"], - cost: { - input: 3, - output: 15, - cacheRead: 0.3, - cacheWrite: 0, - }, - contextWindow: 1048576, - maxTokens: 131072, - } satisfies Model<"openai-completions">, - }, - "openai": { - "gpt-4": { - id: "gpt-4", - name: "GPT-4", - api: "openai-responses", - provider: "openai", - baseUrl: "https://api.openai.com/v1", - reasoning: false, - input: ["text"], - cost: { - input: 30, - output: 60, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 8192, - maxTokens: 8192, - } satisfies Model<"openai-responses">, - "gpt-4-turbo": { - id: "gpt-4-turbo", - name: "GPT-4 Turbo", - api: "openai-responses", - provider: "openai", - baseUrl: "https://api.openai.com/v1", - reasoning: false, - input: ["text", "image"], + input: ["text", "image"], cost: { input: 10, output: 30, @@ -8570,7 +8460,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"minimal":null,"xhigh":"xhigh","max":"max","off":null}, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], cost: { input: 10, @@ -8941,7 +8831,7 @@ export const MODELS = { provider: "openai-codex", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, - thinkingLevelMap: {"minimal":null,"xhigh":"xhigh","max":"max","off":null}, + thinkingLevelMap: {"xhigh":"xhigh","minimal":null,"max":"max","off":null}, input: ["text", "image"], cost: { input: 10, @@ -9800,7 +9690,7 @@ export const MODELS = { provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", reasoning: true, - thinkingLevelMap: {"minimal":null,"xhigh":"xhigh","max":"max","off":null}, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], cost: { input: 10, @@ -11269,9 +11159,9 @@ export const MODELS = { reasoning: false, input: ["text"], cost: { - input: 0.25, - output: 1, - cacheRead: 0, + input: 0.29, + output: 1.1400000000000001, + cacheRead: 0.11, cacheWrite: 0, }, contextWindow: 163840, @@ -11288,13 +11178,13 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, input: ["text"], cost: { - input: 0.55, - output: 1.6500000000000001, - cacheRead: 0.55, + input: 0.25, + output: 0.95, + cacheRead: 0.13, cacheWrite: 0, }, contextWindow: 163840, - maxTokens: 144900, + maxTokens: 32768, } satisfies Model<"openai-completions">, "deepseek/deepseek-r1": { id: "deepseek/deepseek-r1", @@ -11402,9 +11292,9 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null}, input: ["text"], cost: { - input: 0.08106, - output: 0.16212, - cacheRead: 0.016212, + input: 0.088606, + output: 0.177212, + cacheRead: 0.017721200000000003, cacheWrite: 0, }, contextWindow: 1048576, @@ -11421,13 +11311,13 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null}, input: ["text"], cost: { - input: 0.049980000000000004, - output: 0.09996000000000001, - cacheRead: 0.009996000000000001, + input: 0.065, + output: 0.18, + cacheRead: 0.016, cacheWrite: 0, }, contextWindow: 1310720, - maxTokens: 131072, + maxTokens: 943718, } satisfies Model<"openai-completions">, "deepseek/deepseek-v4-flash-vision-exp": { id: "deepseek/deepseek-v4-flash-vision-exp", @@ -11440,13 +11330,13 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null}, input: ["text", "image"], cost: { - input: 0.44, - output: 1.32, - cacheRead: 0.014, + input: 0.22, + output: 0.66, + cacheRead: 0.007, cacheWrite: 0, }, contextWindow: 1048576, - maxTokens: 384000, + maxTokens: 943718, } satisfies Model<"openai-completions">, "deepseek/deepseek-v4-pro": { id: "deepseek/deepseek-v4-pro", @@ -11459,9 +11349,9 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null}, input: ["text"], cost: { - input: 0.7078319999999999, - output: 1.4156639999999998, - cacheRead: 0.058986000000000004, + input: 0.9552599999999999, + output: 1.9105199999999998, + cacheRead: 0.07960500000000001, cacheWrite: 0, }, contextWindow: 1048576, @@ -11478,13 +11368,13 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null}, input: ["text"], cost: { - input: 1.12068, - output: 3.36204, - cacheRead: 0.037356, + input: 0.57948, + output: 1.73844, + cacheRead: 0.057948, cacheWrite: 0, }, contextWindow: 1048576, - maxTokens: 384000, + maxTokens: 393216, } satisfies Model<"openai-completions">, "dots-studio/dots-3-note-preview:free": { id: "dots-studio/dots-3-note-preview:free", @@ -11919,9 +11809,9 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":null}, input: ["text"], cost: { - input: 0.09999999999999999, - output: 0.15, - cacheRead: 0.049999999999999996, + input: 0.06, + output: 0.25, + cacheRead: 0.015, cacheWrite: 0, }, contextWindow: 131072, @@ -11945,9 +11835,9 @@ export const MODELS = { contextWindow: 128000, maxTokens: 50000, } satisfies Model<"openai-completions">, - "inception/mercury-2.5-preview": { - id: "inception/mercury-2.5-preview", - name: "Inception: Mercury 2.5 Preview", + "inception/mercury-2.5": { + id: "inception/mercury-2.5", + name: "Inception: Mercury 2.5", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", @@ -12399,25 +12289,6 @@ export const MODELS = { contextWindow: 204800, maxTokens: 131072, } satisfies Model<"openai-completions">, - "minimax/minimax-m2.7:free": { - id: "minimax/minimax-m2.7:free", - name: "MiniMax: MiniMax M2.7 (free)", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - compat: {"supportsReasoningEffort":false}, - reasoning: true, - thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, - input: ["text"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 196608, - maxTokens: 176947, - } satisfies Model<"openai-completions">, "minimax/minimax-m3": { id: "minimax/minimax-m3", name: "MiniMax: MiniMax M3", @@ -12437,25 +12308,6 @@ export const MODELS = { contextWindow: 1048576, maxTokens: 512000, } satisfies Model<"openai-completions">, - "minimax/minimax-m3:free": { - id: "minimax/minimax-m3:free", - name: "MiniMax: MiniMax M3 (free)", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - compat: {"supportsReasoningEffort":false}, - reasoning: true, - thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 1048576, - maxTokens: 943718, - } satisfies Model<"openai-completions">, "mistralai/codestral-2508": { id: "mistralai/codestral-2508", name: "Mistral: Codestral 2508", @@ -12794,11 +12646,11 @@ export const MODELS = { cost: { input: 0.6, output: 2.5, - cacheRead: 0.15, + cacheRead: 0, cacheWrite: 0, }, contextWindow: 262144, - maxTokens: 100352, + maxTokens: 235929, } satisfies Model<"openai-completions">, "moonshotai/kimi-k2.5": { id: "moonshotai/kimi-k2.5", @@ -12849,9 +12701,9 @@ export const MODELS = { thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, input: ["text", "image"], cost: { - input: 0.66, - output: 3.4, - cacheRead: 0.18, + input: 0.71, + output: 3.5, + cacheRead: 0.15, cacheWrite: 0, }, contextWindow: 262144, @@ -12875,39 +12727,37 @@ export const MODELS = { contextWindow: 1048576, maxTokens: 1048576, } satisfies Model<"openai-completions">, - "nex-agi/nex-n2-mini": { - id: "nex-agi/nex-n2-mini", - name: "Nex AGI: Nex-N2-Mini", + "nex-agi/nex-n2.5-mini:free": { + id: "nex-agi/nex-n2.5-mini:free", + name: "Nex AGI: Nex-N2.5-Mini (free)", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", - compat: {"supportsReasoningEffort":false}, reasoning: true, - thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, - input: ["text", "image"], + thinkingLevelMap: {"minimal":null,"low":null,"medium":"medium","high":"high","xhigh":null,"max":null}, + input: ["text"], cost: { - input: 0.024999999999999998, - output: 0.09999999999999999, - cacheRead: 0.0025, + input: 0, + output: 0, + cacheRead: 0, cacheWrite: 0, }, contextWindow: 262144, maxTokens: 235929, } satisfies Model<"openai-completions">, - "nex-agi/nex-n2-pro": { - id: "nex-agi/nex-n2-pro", - name: "Nex AGI: Nex-N2-Pro", + "nex-agi/nex-n2.5-pro:free": { + id: "nex-agi/nex-n2.5-pro:free", + name: "Nex AGI: Nex-N2.5-Pro (free)", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", - compat: {"supportsReasoningEffort":false}, reasoning: true, - thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, + thinkingLevelMap: {"minimal":null,"low":null,"medium":"medium","high":"high","xhigh":null,"max":null}, input: ["text", "image"], cost: { - input: 0.25, - output: 1, - cacheRead: 0.024999999999999998, + input: 0, + output: 0, + cacheRead: 0, cacheWrite: 0, }, contextWindow: 262144, @@ -12966,7 +12816,7 @@ export const MODELS = { cacheRead: 0, cacheWrite: 0, }, - contextWindow: 1000000, + contextWindow: 262144, maxTokens: 16384, } satisfies Model<"openai-completions">, "nvidia/nemotron-3-super-120b-a12b:free": { @@ -14245,13 +14095,13 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, input: ["text"], cost: { - input: 0.12, - output: 0.24, + input: 0.22749999999999998, + output: 0.9099999999999999, cacheRead: 0, cacheWrite: 0, }, contextWindow: 131072, - maxTokens: 16384, + maxTokens: 8192, } satisfies Model<"openai-completions">, "qwen/qwen3-235b-a22b": { id: "qwen/qwen3-235b-a22b", @@ -14531,13 +14381,13 @@ export const MODELS = { reasoning: false, input: ["text"], cost: { - input: 0.09999999999999999, + input: 0.09, output: 1.1, - cacheRead: 0.07, + cacheRead: 0, cacheWrite: 0, }, contextWindow: 262144, - maxTokens: 235929, + maxTokens: 16384, } satisfies Model<"openai-completions">, "qwen/qwen3-next-80b-a3b-thinking": { id: "qwen/qwen3-next-80b-a3b-thinking", @@ -14556,7 +14406,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 262144, - maxTokens: 32768, + maxTokens: 235929, } satisfies Model<"openai-completions">, "qwen/qwen3-vl-235b-a22b-instruct": { id: "qwen/qwen3-vl-235b-a22b-instruct", @@ -15003,7 +14853,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 1048576, - maxTokens: 262144, + maxTokens: 131072, } satisfies Model<"openai-completions">, "qwen/qwen3.8-27b": { id: "qwen/qwen3.8-27b", @@ -15563,13 +15413,13 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, input: ["text"], cost: { - input: 0.55, - output: 2.2, - cacheRead: 0.11, + input: 0.43, + output: 1.75, + cacheRead: 0.08, cacheWrite: 0, }, contextWindow: 204800, - maxTokens: 131072, + maxTokens: 16384, } satisfies Model<"openai-completions">, "z-ai/glm-4.6v": { id: "z-ai/glm-4.6v", @@ -15620,13 +15470,13 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, input: ["text"], cost: { - input: 0.06, + input: 0.060500000000000005, output: 0.39999999999999997, - cacheRead: 0.01, + cacheRead: 0, cacheWrite: 0, }, contextWindow: 202752, - maxTokens: 16384, + maxTokens: 117964, } satisfies Model<"openai-completions">, "z-ai/glm-5": { id: "z-ai/glm-5", @@ -15703,24 +15553,6 @@ export const MODELS = { contextWindow: 1048576, maxTokens: 131072, } satisfies Model<"openai-completions">, - "z-ai/glm-5.2:free": { - id: "z-ai/glm-5.2:free", - name: "Z.ai: GLM 5.2 (free)", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - reasoning: true, - thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null}, - input: ["text"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 256000, - maxTokens: 230400, - } satisfies Model<"openai-completions">, "z-ai/glm-5.3": { id: "z-ai/glm-5.3", name: "Z.ai: GLM 5.3", @@ -15733,11 +15565,11 @@ export const MODELS = { cost: { input: 1.4, output: 4.4, - cacheRead: 0.14, + cacheRead: 0.26, cacheWrite: 0, }, contextWindow: 1310720, - maxTokens: 262144, + maxTokens: 943718, } satisfies Model<"openai-completions">, "z-ai/glm-5.3-flash": { id: "z-ai/glm-5.3-flash", @@ -15860,13 +15692,13 @@ export const MODELS = { thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null}, input: ["text"], cost: { - input: 0.045, - output: 0.09, - cacheRead: 0.009, + input: 0.049999999999999996, + output: 0.16, + cacheRead: 0.013000000000000001, cacheWrite: 0, }, contextWindow: 1310720, - maxTokens: 943718, + maxTokens: 393216, } satisfies Model<"openai-completions">, "~google/gemini-flash-latest": { id: "~google/gemini-flash-latest", @@ -16004,16 +15836,183 @@ export const MODELS = { thinkingLevelMap: {"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"}, input: ["text"], cost: { - input: 1.17, - output: 3.9600000000000004, - cacheRead: 0.234, + input: 1.113, + output: 3.4979999999999998, + cacheRead: 0.2067, cacheWrite: 0, }, contextWindow: 1310720, - maxTokens: 235929, + maxTokens: 128000, } satisfies Model<"openai-completions">, }, "prime-inference": { + "Qwen/Qwen3-235B-A22B-Instruct-2507": { + id: "Qwen/Qwen3-235B-A22B-Instruct-2507", + name: "Qwen3 235B A22B Instruct 2507", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, + reasoning: false, + input: ["text"], + cost: { + input: 0.25, + output: 1, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 16384, + } satisfies Model<"openai-completions">, + "Qwen/Qwen3-235B-A22B-Thinking-2507": { + id: "Qwen/Qwen3-235B-A22B-Thinking-2507", + name: "Qwen3 235B A22B Thinking 2507", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"openrouter"}, + reasoning: true, + thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, + input: ["text"], + cost: { + input: 0.3, + output: 3, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 131072, + maxTokens: 117964, + } satisfies Model<"openai-completions">, + "Qwen/Qwen3-30B-A3B-Thinking-2507": { + id: "Qwen/Qwen3-30B-A3B-Thinking-2507", + name: "Qwen3 30B A3B Thinking 2507", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"openrouter"}, + reasoning: true, + thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, + input: ["text"], + cost: { + input: 0.2, + output: 2.4, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 81920, + maxTokens: 32768, + } satisfies Model<"openai-completions">, + "Qwen/Qwen3-VL-30B-A3B-Thinking": { + id: "Qwen/Qwen3-VL-30B-A3B-Thinking", + name: "Qwen3 VL 30B A3B Thinking", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"openrouter"}, + reasoning: true, + thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, + input: ["text", "image"], + cost: { + input: 0.2, + output: 2.4, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 32768, + } satisfies Model<"openai-completions">, + "Qwen/Qwen3.5-0.8B": { + id: "Qwen/Qwen3.5-0.8B", + name: "QWEN3.5 0.8B", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, + reasoning: false, + input: ["text"], + cost: { + input: 0.04, + output: 0.08, + cacheRead: 0.036, + cacheWrite: 0.04, + }, + contextWindow: 128000, + maxTokens: 8192, + } satisfies Model<"openai-completions">, + "Qwen/Qwen3.5-122B-A10B": { + id: "Qwen/Qwen3.5-122B-A10B", + name: "QWEN3.5 122B A10B", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"openrouter"}, + reasoning: true, + thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, + input: ["text", "image"], + cost: { + input: 0.3, + output: 0.9, + cacheRead: 0.27, + cacheWrite: 0.3, + }, + contextWindow: 262144, + maxTokens: 81920, + } satisfies Model<"openai-completions">, + "Qwen/Qwen3.5-2B": { + id: "Qwen/Qwen3.5-2B", + name: "QWEN3.5 2B", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, + reasoning: false, + input: ["text"], + cost: { + input: 0.06, + output: 0.18, + cacheRead: 0.054, + cacheWrite: 0.06, + }, + contextWindow: 128000, + maxTokens: 8192, + } satisfies Model<"openai-completions">, + "Qwen/Qwen3.5-4B": { + id: "Qwen/Qwen3.5-4B", + name: "QWEN3.5 4B", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, + reasoning: false, + input: ["text"], + cost: { + input: 0.1, + output: 0.3, + cacheRead: 0.09, + cacheWrite: 0.1, + }, + contextWindow: 128000, + maxTokens: 8192, + } satisfies Model<"openai-completions">, + "Qwen/Qwen3.5-9B": { + id: "Qwen/Qwen3.5-9B", + name: "QWEN3.5 9B", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"openrouter"}, + reasoning: true, + thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null}, + input: ["text", "image"], + cost: { + input: 0.18, + output: 0.54, + cacheRead: 0.162, + cacheWrite: 0.18, + }, + contextWindow: 262144, + maxTokens: 235929, + } satisfies Model<"openai-completions">, "anthropic/claude-fable-5": { id: "anthropic/claude-fable-5", name: "Claude Fable 5", @@ -16206,7 +16205,7 @@ export const MODELS = { cacheRead: 0.30000000000000004, cacheWrite: 3.75, }, - contextWindow: 200000, + contextWindow: 1000000, maxTokens: 64000, } satisfies Model<"openai-completions">, "anthropic/claude-sonnet-4.5": { @@ -16225,7 +16224,7 @@ export const MODELS = { cacheRead: 0.30000000000000004, cacheWrite: 3.75, }, - contextWindow: 200000, + contextWindow: 1000000, maxTokens: 64000, featured: true, } satisfies Model<"openai-completions">, @@ -16243,71 +16242,35 @@ export const MODELS = { input: 3, output: 15, cacheRead: 0.30000000000000004, - cacheWrite: 3.75, - }, - contextWindow: 1000000, - maxTokens: 128000, - featured: true, - } satisfies Model<"openai-completions">, - "anthropic/claude-sonnet-5": { - id: "anthropic/claude-sonnet-5", - name: "Claude Sonnet 5", - api: "openai-completions", - provider: "prime-inference", - baseUrl: "https://api.pinference.ai/api/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"}, - input: ["text", "image"], - cost: { - input: 2, - output: 10, - cacheRead: 0.2, - cacheWrite: 2.5, - }, - contextWindow: 1000000, - maxTokens: 128000, - featured: true, - } satisfies Model<"openai-completions">, - "deepseek/deepseek-chat": { - id: "deepseek/deepseek-chat", - name: "Deepseek Chat", - api: "openai-completions", - provider: "prime-inference", - baseUrl: "https://api.pinference.ai/api/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: false, - input: ["text"], - cost: { - input: 0.4, - output: 1.3, - cacheRead: 0, - cacheWrite: 0, + cacheWrite: 3.75, }, - contextWindow: 163840, - maxTokens: 16384, + contextWindow: 1000000, + maxTokens: 128000, + featured: true, } satisfies Model<"openai-completions">, - "deepseek/deepseek-chat-v3-0324": { - id: "deepseek/deepseek-chat-v3-0324", - name: "Deepseek Chat V3 0324", + "anthropic/claude-sonnet-5": { + id: "anthropic/claude-sonnet-5", + name: "Claude Sonnet 5", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: false, - input: ["text"], + reasoning: true, + thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"}, + input: ["text", "image"], cost: { - input: 0.27, - output: 1.12, - cacheRead: 0, - cacheWrite: 0, + input: 2, + output: 10, + cacheRead: 0.2, + cacheWrite: 2.5, }, - contextWindow: 163840, - maxTokens: 147456, + contextWindow: 1000000, + maxTokens: 128000, + featured: true, } satisfies Model<"openai-completions">, "deepseek/deepseek-chat-v3.1": { id: "deepseek/deepseek-chat-v3.1", - name: "Deepseek Chat V3.1", + name: "DeepSeek V3.1", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16322,11 +16285,11 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 163840, - maxTokens: 144900, + maxTokens: 32768, } satisfies Model<"openai-completions">, "deepseek/deepseek-v3.1-terminus": { id: "deepseek/deepseek-v3.1-terminus", - name: "Deepseek V3.1 Terminus", + name: "DeepSeek V3.1 Terminus", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16345,7 +16308,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "deepseek/deepseek-v3.2": { id: "deepseek/deepseek-v3.2", - name: "Deepseek V3.2", + name: "DeepSeek V3.2", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16365,7 +16328,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "deepseek/deepseek-v3.2-exp": { id: "deepseek/deepseek-v3.2-exp", - name: "Deepseek V3.2 EXP", + name: "DeepSeek V3.2 Exp", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16384,7 +16347,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "deepseek/deepseek-v4-flash": { id: "deepseek/deepseek-v4-flash", - name: "Deepseek V4 Flash", + name: "DeepSeek V4 Flash 0423", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16404,7 +16367,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "deepseek/deepseek-v4-flash-0731": { id: "deepseek/deepseek-v4-flash-0731", - name: "Deepseek V4 Flash 0731", + name: "DeepSeek V4 Flash 0731", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16419,11 +16382,11 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 1310720, - maxTokens: 131072, + maxTokens: 943718, } satisfies Model<"openai-completions">, "deepseek/deepseek-v4-pro": { id: "deepseek/deepseek-v4-pro", - name: "Deepseek V4 PRO", + name: "DeepSeek V4 Pro 0423", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16481,7 +16444,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "google/gemini-2.5-pro": { id: "google/gemini-2.5-pro", - name: "Gemini 2.5 PRO", + name: "Gemini 2.5 Pro", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16519,7 +16482,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "google/gemini-3.1-pro-preview": { id: "google/gemini-3.1-pro-preview", - name: "Gemini 3.1 PRO Preview", + name: "Gemini 3.1 Pro Preview", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16614,7 +16577,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "google/gemma-3-27b-it": { id: "google/gemma-3-27b-it", - name: "Gemma 3 27B IT", + name: "Gemma 3 27B", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16663,8 +16626,8 @@ export const MODELS = { cacheRead: 0, cacheWrite: 0, }, - contextWindow: 80000, - maxTokens: 80000, + contextWindow: 131072, + maxTokens: 117964, } satisfies Model<"openai-completions">, "meta-llama/llama-3.3-70b-instruct": { id: "meta-llama/llama-3.3-70b-instruct", @@ -16704,7 +16667,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "minimax/minimax-m2.5": { id: "minimax/minimax-m2.5", - name: "Minimax M2.5", + name: "MiniMax M2.5", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16723,7 +16686,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "minimax/minimax-m2.7": { id: "minimax/minimax-m2.7", - name: "Minimax M2.7", + name: "MiniMax M2.7", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16742,7 +16705,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "minimax/minimax-m3": { id: "minimax/minimax-m3", - name: "Minimax M3", + name: "MiniMax M3", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16756,13 +16719,13 @@ export const MODELS = { cacheRead: 0, cacheWrite: 0, }, - contextWindow: 524288, + contextWindow: 1048576, maxTokens: 512000, featured: true, } satisfies Model<"openai-completions">, "mistralai/mistral-large-2512": { id: "mistralai/mistral-large-2512", - name: "Mistral Large 2512", + name: "Mistral Large 3 2512", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16798,7 +16761,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "mistralai/mistral-small-2603": { id: "mistralai/mistral-small-2603", - name: "Mistral Small 2603", + name: "Mistral Small 4", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16817,7 +16780,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "mistralai/mixtral-8x22b-instruct": { id: "mistralai/mixtral-8x22b-instruct", - name: "Mixtral 8X22B Instruct", + name: "Mixtral 8x22B Instruct", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16848,8 +16811,8 @@ export const MODELS = { cacheRead: 0, cacheWrite: 0, }, - contextWindow: 98304, - maxTokens: 98304, + contextWindow: 262144, + maxTokens: 100352, } satisfies Model<"openai-completions">, "moonshotai/kimi-k2.5": { id: "moonshotai/kimi-k2.5", @@ -16868,7 +16831,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 262144, - maxTokens: 65535, + maxTokens: 235929, } satisfies Model<"openai-completions">, "moonshotai/kimi-k2.6": { id: "moonshotai/kimi-k2.6", @@ -16887,7 +16850,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 262144, - maxTokens: 262144, + maxTokens: 235929, } satisfies Model<"openai-completions">, "moonshotai/kimi-k2.7-code": { id: "moonshotai/kimi-k2.7-code", @@ -16926,7 +16889,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 1048576, - maxTokens: 1048576, + maxTokens: 943718, featured: true, } satisfies Model<"openai-completions">, "nvidia/nemotron-3-nano-30b-a3b": { @@ -16951,7 +16914,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "nvidia/nemotron-3-super-120b-a12b": { id: "nvidia/nemotron-3-super-120b-a12b", - name: "Nemotron 3 Super 120B A12B", + name: "Nemotron 3 Super", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16966,12 +16929,12 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 262144, - maxTokens: 4096, + maxTokens: 16384, featured: true, } satisfies Model<"openai-completions">, "openai/gpt-4.1": { id: "openai/gpt-4.1", - name: "GPT 4.1", + name: "GPT-4.1", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -16989,7 +16952,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-4.1-mini": { id: "openai/gpt-4.1-mini", - name: "GPT 4.1 Mini", + name: "GPT-4.1 Mini", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17007,7 +16970,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-4.1-nano": { id: "openai/gpt-4.1-nano", - name: "GPT 4.1 Nano", + name: "GPT-4.1 Nano", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17025,7 +16988,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-4o": { id: "openai/gpt-4o", - name: "GPT 4O", + name: "GPT-4o", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17043,7 +17006,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-4o-mini": { id: "openai/gpt-4o-mini", - name: "GPT 4O Mini", + name: "GPT-4o-mini", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17061,7 +17024,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5": { id: "openai/gpt-5", - name: "GPT 5", + name: "GPT-5", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17080,7 +17043,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5-mini": { id: "openai/gpt-5-mini", - name: "GPT 5 Mini", + name: "GPT-5 Mini", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17099,7 +17062,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5-nano": { id: "openai/gpt-5-nano", - name: "GPT 5 Nano", + name: "GPT-5 Nano", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17118,7 +17081,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.1": { id: "openai/gpt-5.1", - name: "GPT 5.1", + name: "GPT-5.1", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17137,7 +17100,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.2": { id: "openai/gpt-5.2", - name: "GPT 5.2", + name: "GPT-5.2", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17156,7 +17119,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.2-pro": { id: "openai/gpt-5.2-pro", - name: "GPT 5.2 PRO", + name: "GPT-5.2 Pro", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17175,7 +17138,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.3-codex": { id: "openai/gpt-5.3-codex", - name: "GPT 5.3 Codex", + name: "GPT-5.3-Codex", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17195,7 +17158,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.4": { id: "openai/gpt-5.4", - name: "GPT 5.4", + name: "GPT-5.4", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17215,7 +17178,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.4-mini": { id: "openai/gpt-5.4-mini", - name: "GPT 5.4 Mini", + name: "GPT-5.4 Mini", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17235,7 +17198,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.4-nano": { id: "openai/gpt-5.4-nano", - name: "GPT 5.4 Nano", + name: "GPT-5.4 Nano", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17254,7 +17217,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.4-pro": { id: "openai/gpt-5.4-pro", - name: "GPT 5.4 PRO", + name: "GPT-5.4 Pro", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17274,7 +17237,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.5": { id: "openai/gpt-5.5", - name: "GPT 5.5", + name: "GPT-5.5", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17311,47 +17274,9 @@ export const MODELS = { contextWindow: 128000, maxTokens: 8192, } satisfies Model<"openai-completions">, - "openai/gpt-5.6-luna": { - id: "openai/gpt-5.6-luna", - name: "GPT 5.6 Luna", - api: "openai-completions", - provider: "prime-inference", - baseUrl: "https://api.pinference.ai/api/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"}, - input: ["text", "image"], - cost: { - input: 0.2, - output: 1.2, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 1050000, - maxTokens: 128000, - } satisfies Model<"openai-completions">, - "openai/gpt-5.6-luna-pro": { - id: "openai/gpt-5.6-luna-pro", - name: "GPT 5.6 Luna PRO", - api: "openai-completions", - provider: "prime-inference", - baseUrl: "https://api.pinference.ai/api/v1", - compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, - reasoning: true, - thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"}, - input: ["text", "image"], - cost: { - input: 1, - output: 6, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 1050000, - maxTokens: 128000, - } satisfies Model<"openai-completions">, "openai/gpt-5.6-sol": { id: "openai/gpt-5.6-sol", - name: "GPT 5.6 SOL", + name: "GPT-5.6 Sol", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17370,7 +17295,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.6-sol-pro": { id: "openai/gpt-5.6-sol-pro", - name: "GPT 5.6 SOL PRO", + name: "GPT-5.6 Sol Pro", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17389,7 +17314,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.6-terra": { id: "openai/gpt-5.6-terra", - name: "GPT 5.6 Terra", + name: "GPT-5.6 Terra", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17408,7 +17333,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-5.6-terra-pro": { id: "openai/gpt-5.6-terra-pro", - name: "GPT 5.6 Terra PRO", + name: "GPT-5.6 Terra Pro", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17427,7 +17352,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-6-astra": { id: "openai/gpt-6-astra", - name: "GPT 6 Astra", + name: "GPT-6 Astra", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17446,7 +17371,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-oss-120b": { id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", + name: "gpt-oss-120b", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17465,7 +17390,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "openai/gpt-oss-20b": { id: "openai/gpt-oss-20b", - name: "GPT OSS 20B", + name: "gpt-oss-20b", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17480,7 +17405,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 131072, - maxTokens: 131072, + maxTokens: 117964, } satisfies Model<"openai-completions">, "poolside/laguna-s-2.1": { id: "poolside/laguna-s-2.1", @@ -17522,7 +17447,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-235b-a22b-2507": { id: "qwen/qwen3-235b-a22b-2507", - name: "QWEN3 235B A22B 2507", + name: "Qwen3 235B A22B Instruct 2507", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17540,7 +17465,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-30b-a3b-instruct-2507": { id: "qwen/qwen3-30b-a3b-instruct-2507", - name: "QWEN3 30B A3B Instruct 2507", + name: "Qwen3 30B A3B Instruct 2507", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17559,7 +17484,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-8b": { id: "qwen/qwen3-8b", - name: "QWEN3 8B", + name: "Qwen3 8B", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17578,7 +17503,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-coder": { id: "qwen/qwen3-coder", - name: "QWEN3 Coder", + name: "Qwen3 Coder 480B A35B", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17596,7 +17521,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-coder-next": { id: "qwen/qwen3-coder-next", - name: "QWEN3 Coder Next", + name: "Qwen3 Coder Next", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17615,7 +17540,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-max": { id: "qwen/qwen3-max", - name: "QWEN3 MAX", + name: "Qwen3 Max", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17634,7 +17559,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-vl-235b-a22b-instruct": { id: "qwen/qwen3-vl-235b-a22b-instruct", - name: "QWEN3 VL 235B A22B Instruct", + name: "Qwen3 VL 235B A22B Instruct", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17652,7 +17577,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-vl-235b-a22b-thinking": { id: "qwen/qwen3-vl-235b-a22b-thinking", - name: "QWEN3 VL 235B A22B Thinking", + name: "Qwen3 VL 235B A22B Thinking", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17672,7 +17597,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-vl-30b-a3b-instruct": { id: "qwen/qwen3-vl-30b-a3b-instruct", - name: "QWEN3 VL 30B A3B Instruct", + name: "Qwen3 VL 30B A3B Instruct", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17690,7 +17615,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3-vl-8b-instruct": { id: "qwen/qwen3-vl-8b-instruct", - name: "QWEN3 VL 8B Instruct", + name: "Qwen3 VL 8B Instruct", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17708,7 +17633,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3.5-35b-a3b": { id: "qwen/qwen3.5-35b-a3b", - name: "QWEN3.5 35B A3B", + name: "Qwen3.5-35B-A3B", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17727,7 +17652,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3.5-397b-a17b": { id: "qwen/qwen3.5-397b-a17b", - name: "QWEN3.5 397B A17B", + name: "Qwen3.5 397B A17B", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17742,11 +17667,11 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 262144, - maxTokens: 65536, + maxTokens: 235929, } satisfies Model<"openai-completions">, "qwen/qwen3.6-27b": { id: "qwen/qwen3.6-27b", - name: "QWEN3.6 27B", + name: "Qwen3.6 27B", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17765,7 +17690,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3.6-35b-a3b": { id: "qwen/qwen3.6-35b-a3b", - name: "QWEN3.6 35B A3B", + name: "Qwen3.6 35B A3B", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17784,7 +17709,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "qwen/qwen3.7-flash": { id: "qwen/qwen3.7-flash", - name: "QWEN3.7 Flash", + name: "Qwen3.7 Flash", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17838,12 +17763,12 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 2000000, - maxTokens: 30000, + maxTokens: 1800000, featured: true, } satisfies Model<"openai-completions">, "x-ai/grok-4.20-multi-agent": { id: "x-ai/grok-4.20-multi-agent", - name: "Grok 4.20 Multi Agent", + name: "Grok 4.20 Multi-Agent", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17858,7 +17783,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 2000000, - maxTokens: 30000, + maxTokens: 1800000, featured: true, } satisfies Model<"openai-completions">, "x-ai/grok-4.6": { @@ -17882,7 +17807,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "xiaomi/mimo-v2.5": { id: "xiaomi/mimo-v2.5", - name: "Mimo V2.5", + name: "MiMo-V2.5", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17901,7 +17826,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "xiaomi/mimo-v2.5-pro": { id: "xiaomi/mimo-v2.5-pro", - name: "Mimo V2.5 PRO", + name: "MiMo-V2.5-Pro", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17939,7 +17864,7 @@ export const MODELS = { } satisfies Model<"openai-completions">, "z-ai/glm-4.5-air": { id: "z-ai/glm-4.5-air", - name: "GLM 4.5 AIR", + name: "GLM 4.5 Air", api: "openai-completions", provider: "prime-inference", baseUrl: "https://api.pinference.ai/api/v1", @@ -17973,7 +17898,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 204800, - maxTokens: 131072, + maxTokens: 16384, } satisfies Model<"openai-completions">, "z-ai/glm-4.7": { id: "z-ai/glm-4.7", @@ -18011,7 +17936,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 202752, - maxTokens: 16384, + maxTokens: 117964, } satisfies Model<"openai-completions">, "z-ai/glm-5": { id: "z-ai/glm-5", @@ -18030,7 +17955,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 204800, - maxTokens: 131072, + maxTokens: 128000, featured: true, } satisfies Model<"openai-completions">, "z-ai/glm-5.1": { @@ -18090,7 +18015,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 1310720, - maxTokens: 262144, + maxTokens: 943718, } satisfies Model<"openai-completions">, "z-ai/glm-5.3-flash": { id: "z-ai/glm-5.3-flash", @@ -18111,6 +18036,24 @@ export const MODELS = { contextWindow: 1310720, maxTokens: 131072, } satisfies Model<"openai-completions">, + "zai-org/GLM-4.7": { + id: "zai-org/GLM-4.7", + name: "GLM 4.7", + api: "openai-completions", + provider: "prime-inference", + baseUrl: "https://api.pinference.ai/api/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false}, + reasoning: true, + input: ["text"], + cost: { + input: 0.6, + output: 2.2, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 204800, + maxTokens: 131072, + } satisfies Model<"openai-completions">, }, "vercel-ai-gateway": { "alibaba/qwen-3-14b": { @@ -19228,6 +19171,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 384000, } satisfies Model<"anthropic-messages">, + "deepseek/deepseek-v4.1-flash-beta": { + id: "deepseek/deepseek-v4.1-flash-beta", + name: "DeepSeek V4.1 Flash Beta", + api: "anthropic-messages", + provider: "vercel-ai-gateway", + baseUrl: "https://ai-gateway.vercel.sh", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.22, + output: 0.66, + cacheRead: 0.007, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 384000, + } satisfies Model<"anthropic-messages">, "google/gemini-2.5-flash": { id: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash", @@ -19466,6 +19426,23 @@ export const MODELS = { contextWindow: 128000, maxTokens: 128000, } satisfies Model<"anthropic-messages">, + "inception/mercury-2.5": { + id: "inception/mercury-2.5", + name: "Mercury 2.5", + api: "anthropic-messages", + provider: "vercel-ai-gateway", + baseUrl: "https://ai-gateway.vercel.sh", + reasoning: true, + input: ["text"], + cost: { + input: 0.04, + output: 0.15, + cacheRead: 0.004, + cacheWrite: 0, + }, + contextWindow: 260000, + maxTokens: 65536, + } satisfies Model<"anthropic-messages">, "inception/mercury-coder-small": { id: "inception/mercury-coder-small", name: "Mercury Coder Small Beta", @@ -19942,23 +19919,6 @@ export const MODELS = { contextWindow: 204800, maxTokens: 131000, } satisfies Model<"anthropic-messages">, - "minimax/minimax-m2.7-free": { - id: "minimax/minimax-m2.7-free", - name: "MiniMax M2.7 (Free)", - api: "anthropic-messages", - provider: "vercel-ai-gateway", - baseUrl: "https://ai-gateway.vercel.sh", - reasoning: true, - input: ["text"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 196608, - maxTokens: 196608, - } satisfies Model<"anthropic-messages">, "minimax/minimax-m2.7-highspeed": { id: "minimax/minimax-m2.7-highspeed", name: "MiniMax M2.7 High Speed", @@ -19993,23 +19953,6 @@ export const MODELS = { contextWindow: 512000, maxTokens: 512000, } satisfies Model<"anthropic-messages">, - "minimax/minimax-m3-free": { - id: "minimax/minimax-m3-free", - name: "MiniMax M3 (Free)", - api: "anthropic-messages", - provider: "vercel-ai-gateway", - baseUrl: "https://ai-gateway.vercel.sh", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 1048576, - maxTokens: 1048576, - } satisfies Model<"anthropic-messages">, "mistral/codestral": { id: "mistral/codestral", name: "Mistral Codestral", @@ -21247,7 +21190,7 @@ export const MODELS = { provider: "vercel-ai-gateway", baseUrl: "https://ai-gateway.vercel.sh", reasoning: true, - thinkingLevelMap: {"minimal":null,"xhigh":"xhigh","max":"max"}, + thinkingLevelMap: {"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], cost: { input: 10, @@ -21265,7 +21208,7 @@ export const MODELS = { provider: "vercel-ai-gateway", baseUrl: "https://ai-gateway.vercel.sh", reasoning: true, - thinkingLevelMap: {"minimal":null,"xhigh":"xhigh","max":"max"}, + thinkingLevelMap: {"xhigh":"xhigh","minimal":null,"max":"max"}, input: ["text", "image"], cost: { input: 20, @@ -21871,23 +21814,6 @@ export const MODELS = { contextWindow: 1050000, maxTokens: 131000, } satisfies Model<"anthropic-messages">, - "xiaomi/mimo-v2.5-pro-ultraspeed": { - id: "xiaomi/mimo-v2.5-pro-ultraspeed", - name: "MiMo V2.5 Pro UltraSpeed", - api: "anthropic-messages", - provider: "vercel-ai-gateway", - baseUrl: "https://ai-gateway.vercel.sh", - reasoning: true, - input: ["text"], - cost: { - input: 1.305, - output: 2.61, - cacheRead: 0.0108, - cacheWrite: 0, - }, - contextWindow: 1048576, - maxTokens: 131072, - } satisfies Model<"anthropic-messages">, "zai/glm-4.5": { id: "zai/glm-4.5", name: "GLM 4.5", @@ -22101,9 +22027,9 @@ export const MODELS = { reasoning: true, input: ["text"], cost: { - input: 0.7, - output: 2.2, - cacheRead: 0.13, + input: 1.4, + output: 4.4, + cacheRead: 0.14, cacheWrite: 0, }, contextWindow: 1000000, @@ -22143,23 +22069,6 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 131000, } satisfies Model<"anthropic-messages">, - "zai/glm-5.3-promo-50": { - id: "zai/glm-5.3-promo-50", - name: "GLM 5.3 (50% off)", - api: "anthropic-messages", - provider: "vercel-ai-gateway", - baseUrl: "https://ai-gateway.vercel.sh", - reasoning: true, - input: ["text"], - cost: { - input: 0.7, - output: 2.2, - cacheRead: 0.13, - cacheWrite: 0, - }, - contextWindow: 1048576, - maxTokens: 1048576, - } satisfies Model<"anthropic-messages">, "zai/glm-5v-turbo": { id: "zai/glm-5v-turbo", name: "GLM 5V Turbo", diff --git a/packages/coding-agent/.changes/remote-python-host.md b/packages/coding-agent/.changes/remote-python-host.md new file mode 100644 index 000000000..2f8511415 --- /dev/null +++ b/packages/coding-agent/.changes/remote-python-host.md @@ -0,0 +1,2 @@ +- Added `--remote-python-host` (and `--remote-host`) to execute Python kernels and subagents on a remote SSH host on demand. +- Added remote Python host display to the startup banner, prompt tray, and agents view. diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 6fd4bb270..cd164a5c9 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -54,6 +54,7 @@ export interface Args { offline?: boolean; verbose?: boolean; remotePython?: string; + remotePythonHost?: string; messages: string[]; fileArgs: string[]; /** Unknown flags (potentially extension flags) - map of flag name to value */ @@ -321,6 +322,11 @@ export function parseArgs(args: string[]): Args { result.remotePython = args[++i]; process.env.PRIME_AGENT_REMOTE_PYTHON = result.remotePython; } + } else if (arg === "--remote-python-host" || arg === "--remote-host") { + if (hasRequiredOptionValue(args, i, arg, result)) { + result.remotePythonHost = args[++i]; + process.env.PRIME_AGENT_REMOTE_PYTHON_HOST = result.remotePythonHost; + } } else if (arg === "--list-models") { const hasSearch = i + 1 < args.length && !args[i + 1].startsWith("-") && !args[i + 1].startsWith("@"); if (!internalRuntimeCommand) { @@ -373,6 +379,19 @@ export function parseArgs(args: string[]): Args { }); } + if (!result.remotePythonHost) { + const envHost = process.env.PRIME_AGENT_REMOTE_PYTHON_HOST ?? process.env.PRIME_AGENT_REMOTE_HOST; + if (envHost?.trim()) { + result.remotePythonHost = envHost.trim(); + } + } + if (!result.remotePython) { + const envPython = process.env.PRIME_AGENT_REMOTE_PYTHON; + if (envPython?.trim()) { + result.remotePython = envPython.trim(); + } + } + return result; } diff --git a/packages/coding-agent/src/cli/command-registry.ts b/packages/coding-agent/src/cli/command-registry.ts index 93be70e6e..072d81ad8 100644 --- a/packages/coding-agent/src/cli/command-registry.ts +++ b/packages/coding-agent/src/cli/command-registry.ts @@ -204,6 +204,7 @@ const TOP_LEVEL_OPTION_GROUPS: ReadonlyArray<{ heading: string; options: readonl ["--verbose", "Force verbose startup"], ["--daemon-socket ", "Use a specific daemon socket"], ["--remote-python ", "Use a specific Python interpreter for remote kernels"], + ["--remote-python-host ", "Execute Python kernels on a remote SSH host"], ], }, { diff --git a/packages/coding-agent/src/cli/daemon-list-format.ts b/packages/coding-agent/src/cli/daemon-list-format.ts index 4d963b8ef..21ef73524 100644 --- a/packages/coding-agent/src/cli/daemon-list-format.ts +++ b/packages/coding-agent/src/cli/daemon-list-format.ts @@ -34,7 +34,7 @@ export function formatSessionListTable(sessions: readonly SessionSummary[], nowM id: formatSessionDisplayId(session.id), status: listStatusForSummary(session), age: formatSessionAge(session.modified, nowMs), - model: formatModelSelector(session.model), + model: formatSessionModel(session.model, session.remoteHost),>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) messages: String(session.messageCount), clients: String(session.attachedClients), })); @@ -99,8 +99,9 @@ function formatSessionAge(modified: string | undefined, nowMs: number): string { return `${Math.floor(ageWeeks / 52)}y`; } -function formatModelSelector(model: SessionSummary["model"]): string { - return model ? `${model.provider}/${model.id}` : ""; +function formatSessionModel(model: SessionSummary["model"], remoteHost?: string): string { + const modelLabel = model ? `${model.provider}/${model.id}` : ""; + return remoteHost ? `${modelLabel ? `${modelLabel}@` : "@"}${remoteHost}` : modelLabel;>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) } function formatTable>( diff --git a/packages/coding-agent/src/core/agent-session-config.ts b/packages/coding-agent/src/core/agent-session-config.ts index f855f4c07..9e8a791db 100644 --- a/packages/coding-agent/src/core/agent-session-config.ts +++ b/packages/coding-agent/src/core/agent-session-config.ts @@ -43,11 +43,12 @@ export interface AgentSessionRuntimeConfig { * thread_goal_state entry (idempotent restart/rehydration). */ initialGoal?: { objective: string; tokenBudget?: number }; + remoteHost?: string; } export type DurableAgentSessionRuntimeConfig = Pick< AgentSessionRuntimeConfig, - "cwd" | "agentDir" | "sessionDir" | "telemetryDisabled" + "cwd" | "agentDir" | "sessionDir" | "telemetryDisabled" | "remoteHost" >; /** Only non-secret host settings needed to locate and govern durable daemon state. */ @@ -57,6 +58,7 @@ export function durableAgentSessionRuntimeConfig(config: AgentSessionRuntimeConf ...(typeof config.agentDir === "string" ? { agentDir: config.agentDir } : {}), ...(typeof config.sessionDir === "string" ? { sessionDir: config.sessionDir } : {}), ...(config.telemetryDisabled === true ? { telemetryDisabled: true as const } : {}), + ...(typeof config.remoteHost === "string" ? { remoteHost: config.remoteHost } : {}), }; } @@ -99,6 +101,7 @@ export function mergeAgentSessionRuntimeConfig( executionMode: override.executionMode ?? base.executionMode, telemetryDisabled: base.telemetryDisabled || override.telemetryDisabled ? true : undefined, initialGoal: override.initialGoal ?? base.initialGoal, + remoteHost: override.remoteHost ?? base.remoteHost, }; } @@ -118,6 +121,7 @@ function cloneAgentSessionRuntimeConfig(config: AgentSessionRuntimeConfig): Agen executionMode: config.executionMode, telemetryDisabled: config.telemetryDisabled, initialGoal: config.initialGoal ? { ...config.initialGoal } : undefined, + remoteHost: config.remoteHost, }; } diff --git a/packages/coding-agent/src/core/agent-session-services.ts b/packages/coding-agent/src/core/agent-session-services.ts index 27574abae..59d5b1eb1 100644 --- a/packages/coding-agent/src/core/agent-session-services.ts +++ b/packages/coding-agent/src/core/agent-session-services.ts @@ -83,6 +83,7 @@ export interface AgentSessionCreationOptions { executionMode?: AgentExecutionMode; telemetryDisabled?: true; initialGoal?: { objective: string; tokenBudget?: number }; + remoteHost?: string; } export interface CreateAgentSessionFromServicesOptions extends AgentSessionCreationOptions { @@ -287,6 +288,7 @@ export async function createAgentSessionFromServices( autonomous: options.autonomous, serializedRefine: options.serializedRefine, initialGoal: options.initialGoal, + remoteHost: options.remoteHost, }); if (result.session.rlmDepth === 0 && !options.telemetryDisabled) { installAgentTelemetry(result.session, { diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index e28bddb5d..ebef6c584 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -401,6 +401,7 @@ export interface RlmChildAgentSnapshot { */ activityStaleMs?: number; error?: string; + remoteHost?: string; } export type CompactionReason = "manual" | "threshold" | "overflow" | "requested"; @@ -1046,6 +1047,7 @@ interface RlmChildRun { */ lastActivityMonotonicAt?: number; error?: string; + remoteHost?: string; abort: () => void; publication: AgentMessageDeferred; /** Resolves after terminal result publication and detached-run cleanup finish. */ @@ -5068,6 +5070,10 @@ export class AgentSession { return this._rlmMaxDepth; } + get remoteHost(): string | undefined { + return this._remoteHost; + } + get sessionName(): string | undefined { return this.sessionManager.getSessionName(); } @@ -5179,6 +5185,7 @@ export class AgentSession { rlmDepth: this._rlmDepth, rlmParentAgent: this._rlmParentAgent, genericMcpServers: this._mcpManager?.getEnabledPersistentGenericServers(), + remoteHost: this._remoteHost, }; return buildSystemPrompt(this._baseSystemPromptOptions); } @@ -11694,6 +11701,7 @@ export class AgentSession { lastActivityAt: run.lastActivityAt, activityStaleMs: rlmActivityStaleMs(run.status, run.activity, run.lastActivityAt, run.lastActivityMonotonicAt), error: run.error, + remoteHost: child?.remoteHost ?? run.remoteHost, }; } @@ -11728,6 +11736,7 @@ export class AgentSession { // finished) and current work surfaces through activity. activity: child.isSessionActive ? { kind: child.isStreaming ? "writing" : "waiting" } : undefined, repliedSinceTask: child._repliedToParentSinceTask, + remoteHost: child.remoteHost, }; } @@ -12161,6 +12170,8 @@ export class AgentSession { let runningToolCount = 0; let childSession: AgentSession | undefined; const startedMonotonicAt = performance.now(); + const targetHost = requestedHost === "local" ? undefined : (requestedHost ?? this._remoteHost); +>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) const run: RlmChildRun = { id: childNodeId, prompt, @@ -12179,6 +12190,7 @@ export class AgentSession { publication: createAgentMessageDeferred(), settlement: createAgentMessageDeferred(), deletionReservation: createAgentMessageDeferred(), + remoteHost: targetHost, }; const throwIfCancelled = () => { if (run.status === "cancelled") throw new Error(run.error ?? "RLM child cancelled"); @@ -12221,7 +12233,7 @@ export class AgentSession { model: modelSelection.model, thinkingLevel: requestedThinkingLevel, spawnedByRequestId, - remoteHost: requestedHost, + remoteHost: targetHost, }), onSessionPublished: publishChildSession, }; diff --git a/packages/coding-agent/src/core/kernel/repl-manager.ts b/packages/coding-agent/src/core/kernel/repl-manager.ts index 8a0d4da5a..3b99f3c81 100644 --- a/packages/coding-agent/src/core/kernel/repl-manager.ts +++ b/packages/coding-agent/src/core/kernel/repl-manager.ts @@ -333,7 +333,8 @@ export class ReplKernelManager { let child: ChildProcess; if (this.options.remote) { - const remoteCmd = `cd ${this.options.cwd ?? "~"} && ${python} -m rlm.repl`; + const remoteCwd = this.options.cwd ? `(cd "${this.options.cwd}" 2>/dev/null || cd ~)` : "cd ~"; + const remoteCmd = `${remoteCwd} && ${python} -m rlm.repl`; const sshArgs = buildSshArgs(this.options.remote, remoteCmd); child = spawn("ssh", sshArgs, { stdio: ["pipe", "pipe", "pipe"], diff --git a/packages/coding-agent/src/core/prompts/rlm.ts b/packages/coding-agent/src/core/prompts/rlm.ts index a8d939cb7..f2cbc8757 100644 --- a/packages/coding-agent/src/core/prompts/rlm.ts +++ b/packages/coding-agent/src/core/prompts/rlm.ts @@ -9,10 +9,13 @@ export interface RlmPromptOptions { depth?: number; parentAgent?: string; activeTools?: string[]; + remoteHost?: string; } const LONG_RUNNING_WORK_PROMPT = [ "For slow or independently completing work, use a nonblocking control loop: start the work, record its handle or output location, then end your turn. A `bash()` handle left running beyond its creating cell sends a completion follow-up; when it arrives, inspect the saved handle and continue. Reading a finished handle's result first cancels that follow-up.", + "Only an active unawaited `bash()` handle or child subagent will wake you up. Never end your turn assuming an external daemon, remote build, or container (`docker`, `podman`, `systemd`) will wake you without a running handle: start a sentinel `bash()` command that blocks until the terminal condition (such as `bash('podman wait ')` or a polling loop), then end your turn with that handle active.", +>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) "When delegation is available and useful, assign independent substantive tasks to separate workers. Start independent workers without waiting for each one sequentially, and let them run in parallel.", "Do not keep the turn open by polling with `time.sleep()` or shell `sleep`, and do not replace polling with a long blocking `await`. Await only the short operation needed to start work or inspect a result that is already available; otherwise end the turn.", ].join("\n"); @@ -102,6 +105,7 @@ export function buildRlmPrompt(options: RlmPromptOptions): string { SIMPLIFIED_TECHNICAL_ENGLISH_PROMPT, "", `Working directory: ${cwd}`, + ...(options.remoteHost ? [`Remote execution host: ${options.remoteHost}`] : []), `Conversation log: ${messagesPath}`, `Recursive agent depth: ${depth}`, `Pre-installed Python packages: ${DEFAULT_RLM_EXTRA_IMPORT_LABELS.join(", ")}.`, @@ -190,6 +194,12 @@ export function buildRlmPrompt(options: RlmPromptOptions): string { } if (hasIpython) { + if (options.remoteHost) { + parts.push( + "", + `The \`ipython\` tool and \`bash()\` execute on remote host ${options.remoteHost} over SSH. Python code, shell commands, and filesystem operations run in the remote environment on that host.`, + ); + } parts.push("", REPL_CONTROL_PROMPT); if (installedSkills.includes("refine")) { parts.push( diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index b72417f3c..b9e7e2eea 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -385,6 +385,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} autonomous: options.autonomous, serializedRefine: options.serializedRefine, initialGoal: options.initialGoal, + remoteHost: options.remoteHost, }); const extensionsResult = resourceLoader.getExtensions(); diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index 9e9733940..bee496d83 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -247,6 +247,8 @@ export interface Settings { markdown?: MarkdownSettings; warnings?: WarningSettings; sessionDir?: string; // Custom session storage directory (same format as --session-dir CLI flag) + remotePythonHost?: string; + remotePython?: string; } export interface AgentTracesSettings { @@ -774,6 +776,14 @@ export class SettingsManager { return sessionDir; } + getRemotePythonHost(): string | undefined { + return this.settings.remotePythonHost; + } + + getRemotePython(): string | undefined { + return this.settings.remotePython; + } + getDefaultProvider(): string | undefined { return this.settings.defaultProvider; } diff --git a/packages/coding-agent/src/core/system-prompt.ts b/packages/coding-agent/src/core/system-prompt.ts index 474a2c619..111e62f67 100644 --- a/packages/coding-agent/src/core/system-prompt.ts +++ b/packages/coding-agent/src/core/system-prompt.ts @@ -33,6 +33,8 @@ export interface BuildSystemPromptOptions { rlmParentAgent?: string; /** Enabled user-configured servers available through the generic kernel MCP API. */ genericMcpServers?: string[]; + /** Remote execution host over SSH when running on a remote host. */ + remoteHost?: string; } /** Build the system prompt with tools, guidelines, and context */ @@ -120,6 +122,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions): string { allowRecursion, depth: options.rlmDepth, parentAgent: options.rlmParentAgent, + remoteHost: options.remoteHost, }); // Appended AFTER the trained buildRlmPrompt prefix: delegation doctrine precedes the subagent specs delivered via the harness digest. diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 2848de728..35e750696 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -609,6 +609,9 @@ function buildSessionOptions( if (config.autonomous) { options.autonomous = mergeAutonomousConfig(undefined, config.autonomous); } + if (config.remoteHost) { + options.remoteHost = config.remoteHost; + } return { options, cliThinkingFromModel, diagnostics }; } @@ -691,6 +694,7 @@ function runtimeConfigFromArgs( // instead of its own appMode="daemon". serializedRefine: appMode !== "interactive" && appMode !== "daemon", initialGoal: parsed.goal ? { objective: parsed.goal, tokenBudget: parsed.goalTokenBudget } : undefined, + remoteHost: parsed.remotePythonHost, }; } @@ -738,6 +742,7 @@ export function resolveRuntimeSessionOptions( semanticParentSessionId: runtimeSessionOptions?.semanticParentSessionId, semanticSpawnedByRequestId: runtimeSessionOptions?.semanticSpawnedByRequestId, subagentRuntimeHost: runtimeSessionOptions?.subagentRuntimeHost, + remoteHost: runtimeSessionOptions?.remoteHost ?? sessionOptions.remoteHost, }; } @@ -848,7 +853,11 @@ async function prepareRuntimeServices(options: { })), ]; - const modelPatterns = config.models ?? settingsManager.getEnabledModels(); + const effectiveConfig = { + ...config, + remoteHost: config.remoteHost ?? settingsManager.getRemotePythonHost(), + }; + const modelPatterns = effectiveConfig.models ?? settingsManager.getEnabledModels(); const scopedModels = modelPatterns && modelPatterns.length > 0 ? await resolveModelScope(modelPatterns, modelRegistry) : []; const { @@ -856,7 +865,7 @@ async function prepareRuntimeServices(options: { cliThinkingFromModel, diagnostics: sessionOptionDiagnostics, } = buildSessionOptions( - config, + effectiveConfig, scopedModels, sessionManager.buildSessionContext().messages.length > 0, modelRegistry, diff --git a/packages/coding-agent/src/modes/agent-connection/snapshot.ts b/packages/coding-agent/src/modes/agent-connection/snapshot.ts index 3b08cc4cd..646e80fa2 100644 --- a/packages/coding-agent/src/modes/agent-connection/snapshot.ts +++ b/packages/coding-agent/src/modes/agent-connection/snapshot.ts @@ -29,6 +29,7 @@ export function createAgentConnectionState( activeSessionId, cwd: sessionManager.getCwd(), model: toConnectionModel(session.model), + remoteHost: session.remoteHost, thinkingLevel: session.thinkingLevel, serviceTier: session.serviceTier, availableThinkingLevels: session.getAvailableThinkingLevels(), diff --git a/packages/coding-agent/src/modes/agent-connection/types.ts b/packages/coding-agent/src/modes/agent-connection/types.ts index 775b78bb5..c9c2b10d8 100644 --- a/packages/coding-agent/src/modes/agent-connection/types.ts +++ b/packages/coding-agent/src/modes/agent-connection/types.ts @@ -332,6 +332,7 @@ export interface AgentConnectionState { activeSessionId?: string; cwd: string; model?: AgentConnectionModel; + remoteHost?: string; thinkingLevel: ThinkingLevel; serviceTier: ServiceTier; availableThinkingLevels: ThinkingLevel[]; @@ -581,6 +582,7 @@ export interface AgentConnectionRlmChildAgentSnapshot { /** Set when a running child has had no tracked activity for the staleness threshold. */ activityStaleMs?: number; error?: string; + remoteHost?: string; } export type AgentConnectionSessionEvent = diff --git a/packages/coding-agent/src/modes/agents-view/agents-view-mode.ts b/packages/coding-agent/src/modes/agents-view/agents-view-mode.ts index 26cb25eb8..f8181c9a5 100644 --- a/packages/coding-agent/src/modes/agents-view/agents-view-mode.ts +++ b/packages/coding-agent/src/modes/agents-view/agents-view-mode.ts @@ -854,12 +854,18 @@ export class AgentsViewMode implements Component, Focusable { topPadding: true, getExtraMetadata: () => { const root = this.scopeRootSummary; + const host = this.rows[this.selectedIndex]?.summary.remoteHost ?? root?.remoteHost; return [ { label: "agents", value: this.getAgentCountsText() }, + ...(root ? [{ label: "scope", value: getAgentsViewSessionTitle(root) }] : []), ...(root ? [{ label: "depth", value: String(getAgentsViewDepth(root)) }] : []), + ...(host ? [{ label: "host", value: host }] : []), ]; }, }); +>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) + }, + }); } async run(): Promise { @@ -1327,7 +1333,7 @@ export class AgentsViewMode implements Component, Focusable { this.scopeKey, this.recursiveRollups, this.anchorSessionId, - this.sessionPins.ids(), + this.sessionPins?.ids?.() ?? new Set(), ); const index = selectedIdentity === undefined ? -1 : this.rows.findIndex((row) => row.identity === selectedIdentity); @@ -2225,7 +2231,7 @@ export class AgentsViewMode implements Component, Focusable { this.scopeKey, this.recursiveRollups, this.anchorSessionId, - this.sessionPins.ids(), + this.sessionPins?.ids?.() ?? new Set(), ); this.applyPendingAncestorExpansion(); this.restoreSelection(); @@ -2634,6 +2640,7 @@ export class AgentsViewMode implements Component, Focusable { cells.push(theme.fg("dim", details)); return markRow(formatTableCell(cells.join(" "), width)); } +>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) // Spawn-code rows are read-only context. They render deemphasized — muted // text on a panel background (applied in finalizeRenderedLine) so the program // reads as one quiet segmented block rather than competing with agent rows. @@ -2908,7 +2915,9 @@ function formatSessionModel(row: AgentsViewRow): string { if (!id) return "-"; const bare = id.slice(id.lastIndexOf("/") + 1) || id; const level = row.summary.thinkingLevel; - return level && level !== "off" ? `${bare}:${level}` : bare; + const label = level && level !== "off" ? `${bare}:${level}` : bare; + return row.summary.remoteHost ? `${label}@${row.summary.remoteHost}` : label; +>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) } function formatSessionDuration(summary: SessionSummary): string { diff --git a/packages/coding-agent/src/modes/daemon/daemon-session-list.ts b/packages/coding-agent/src/modes/daemon/daemon-session-list.ts index 21e0794e3..fb7847993 100644 --- a/packages/coding-agent/src/modes/daemon/daemon-session-list.ts +++ b/packages/coding-agent/src/modes/daemon/daemon-session-list.ts @@ -51,6 +51,7 @@ export interface SessionSummary { sessionName?: string; cwd: string; model?: Model; + remoteHost?: string; thinkingLevel?: ThinkingLevel; isStreaming: boolean; isCompacting: boolean; @@ -255,6 +256,7 @@ export function summaryForActiveSession( sessionName: session.sessionName, cwd: session.sessionManager.getCwd(), model: session.model as Model | undefined, + remoteHost: session.remoteHost, thinkingLevel: session.thinkingLevel, isStreaming: session.isStreaming, isCompacting: session.isCompacting, diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 07c2a2ea8..ed8ad91e5 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -1704,7 +1704,12 @@ export class InteractiveMode { topPadding: true, getModelId: () => this.getCurrentModelId(), getHidden: () => this.onboardingUiActive, + getExtraMetadata: () => { + const remoteHost = this.getCurrentRemoteHost(); + return remoteHost ? [{ label: "host", value: remoteHost }] : []; + }, }); +>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) this.headerContainer.addChild(this.builtInHeader); this.headerContainer.addChild(new Spacer(1)); } else { @@ -3218,6 +3223,10 @@ export class InteractiveMode { return this.getCurrentModel()?.id; } + private getCurrentRemoteHost(): string | undefined { + return this.connectionState?.remoteHost; + } + private isAgentStreaming(): boolean { return this.connectionState?.isStreaming ?? false; } @@ -6631,12 +6640,15 @@ export class InteractiveMode { private getTrayLocationLabel(): string | undefined { if (this.isInlinePickerOpen()) return undefined; const sessionDepth = this.options.sessionDepth; +>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) const hasChildren = this.options.sessionHasChildren === true || (this.subagentSnapshots?.size ?? 0) > 0; // Depth is subagent-session context: a root session (depth 0) never shows // a depth label, even while its children run. const depthLabel = sessionDepth ? formatAgentDepthLabel(sessionDepth, hasChildren) : undefined; const agentsHint = this.getAgentsViewTrayHint(); - return [agentsHint, depthLabel].filter((label): label is string => label !== undefined).join(" "); + const remoteHost = this.getCurrentRemoteHost(); + const hostLabel = remoteHost ? `${theme.fg("dim", "on")} ${theme.fg("accent", remoteHost)}` : undefined; + return [agentsHint, depthLabel, hostLabel].filter((label): label is string => label !== undefined).join(" "); } private isNewChat(): boolean { diff --git a/packages/coding-agent/test/agent-session-config.test.ts b/packages/coding-agent/test/agent-session-config.test.ts index 1cd09bf80..3da026405 100644 --- a/packages/coding-agent/test/agent-session-config.test.ts +++ b/packages/coding-agent/test/agent-session-config.test.ts @@ -43,4 +43,23 @@ describe("agent session runtime config persistence", () => { } as unknown as AgentSessionRuntimeConfig), ).toEqual({ agentDir: "/agent" }); }); + + it("merges and preserves remoteHost across session config merges and durable config", () => { + const defaults: AgentSessionRuntimeConfig = { + cwd: "/repo", + remoteHost: "chernobog", + }; + + const merged = mergeAgentSessionRuntimeConfig(defaults, { cwd: "/override" }); + expect(merged.remoteHost).toBe("chernobog"); + + const overridden = mergeAgentSessionRuntimeConfig(defaults, { remoteHost: "other-host" }); + expect(overridden.remoteHost).toBe("other-host"); + + const durable = durableAgentSessionRuntimeConfig({ + cwd: "/repo", + remoteHost: "chernobog", + }); + expect(durable.remoteHost).toBe("chernobog"); + }); }); diff --git a/packages/coding-agent/test/agent-session-recursion.test.ts b/packages/coding-agent/test/agent-session-recursion.test.ts index 174fb9592..92baa50f6 100644 --- a/packages/coding-agent/test/agent-session-recursion.test.ts +++ b/packages/coding-agent/test/agent-session-recursion.test.ts @@ -223,6 +223,7 @@ describe("AgentSession rlm recursion", () => { sessionManager?: SessionManager; settingsManager?: SettingsManager; extensionsResult?: LoadExtensionsResult; + remoteHost?: string; } = {}, ): AgentSession { const authStorage = AuthStorage.create(join(tempDir, "auth.json")); @@ -275,6 +276,7 @@ describe("AgentSession rlm recursion", () => { rlmDepth: options.depth, rlmMaxDepth: options.maxDepth, rlmSessionDir: options.rlmSessionDir, + remoteHost: options.remoteHost, }); return session; } @@ -1921,6 +1923,57 @@ describe("AgentSession rlm recursion", () => { expect((activeRun.session as any)._remoteHost).toBe("chernobog"); }); + it("exposes remoteHost via session.remoteHost getter", () => { + const root = createSession({ remoteHost: "chernobog" }); + expect(root.remoteHost).toBe("chernobog"); + }); + + it("inherits parent remoteHost for subagents when no host is specified", async () => { + const root = createSession({ + remoteHost: "chernobog", + streamFn: () => streamAnswer("child finished"), + }); + + const handle = await root.runRlmChild("child task"); + const activeRun = (root as any)._activeRlmChildRuns.get(handle.rlm_child_id); + expect(activeRun).toBeDefined(); + expect((activeRun.session as any)._remoteHost).toBe("chernobog"); + }); + + it("allows subagent to opt out of remote execution via host='local'", async () => { + const root = createSession({ + remoteHost: "chernobog", + streamFn: () => streamAnswer("child finished"), + }); + + const handle = await root.runRlmChild("local task", { host: "local" }); + const activeRun = (root as any)._activeRlmChildRuns.get(handle.rlm_child_id); + expect(activeRun).toBeDefined(); + expect((activeRun.session as any)._remoteHost).toBeUndefined(); + }); + + it("cancels active rlm children when the parent session is disposed", async () => { + let releaseChild: () => void = () => {}; + const release = new Promise((resolve) => { + releaseChild = resolve; + }); + let childStarted = false; + const root = createSession({ + streamFn: (_model, context) => { + const text = userText(context); + const stream = createAssistantMessageEventStream(); + if (text === "slow shard") { + childStarted = true; + void release.then(() => { + stream.push({ type: "done", reason: "stop", message: assistantMessage(`child answer: ${text}`) }); + }); + } + return stream; + }, + }); + + const spawned = await root.runRlmChild("slow shard"); + await waitFor(() => childStarted); it.each<{ label: string; stop: (root: AgentSession) => void | Promise; @@ -1947,7 +2000,7 @@ describe("AgentSession rlm recursion", () => { }, ])("leaves an active rlm child $status when $label", async ({ stop, status, error }) => { const { root, releaseChild, hasStarted } = createGatedRoot(); const spawned = await root.runRlmChild("slow shard"); - await waitFor(hasStarted); + await waitFor(hasStarted);>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) const runs = (root as unknown as InspectableRlmSession)._activeRlmChildRuns; expect(runs.size).toBe(1); const run = runs.get(spawned.rlm_child_id); diff --git a/packages/coding-agent/test/agents-view-mode.test.ts b/packages/coding-agent/test/agents-view-mode.test.ts index e71f54aaa..4fc8b6056 100644 --- a/packages/coding-agent/test/agents-view-mode.test.ts +++ b/packages/coding-agent/test/agents-view-mode.test.ts @@ -712,6 +712,541 @@ describe("AgentsViewMode", () => { stopThemeWatcher(); } }); + + it("keeps subagent expansion across the active-to-persisted identity flip", () => { + const rowsOf = (self: Record) => Reflect.get(self, "rows") as AgentsViewRow[]; + const buildView = (expand: boolean) => { + const parent = summary({ + id: "root-active", + activeSessionId: "root-active", + sessionId: "root-session", + sessionFile: undefined, + runtimeKind: "top-level", + }); + const child = summary({ + id: "child-active", + activeSessionId: "child-active", + sessionId: "child-session", + sessionFile: undefined, + runtimeKind: "subagent", + parentSessionId: "root-session", + parentActiveSessionId: "root-active", + }); + const expandedSubagentParents = new Set(); + const self: Record = { + persistentState: {}, + lastListedSummaries: [parent, child], + savedSessions: [], + heartbeats: [], + inactiveAgentIdentities: new Set(), + pendingDeleteAgent: undefined, + savedCatalogReady: true, + expandedSubagentParents, + programShownParents: new Set(), + editor: { getText: () => "" }, + getFilteredRecords: () => Reflect.get(self, "scopedRecords"), + applyPendingAncestorExpansion: vi.fn(), + restoreSelection: vi.fn(), + ui: { requestRender: vi.fn() }, + setStatusMessage: vi.fn(), + withPendingDeleteSession: (sessions: SessionSummary[]) => sessions, + }; + invoke("reconcileCatalogs", self); + if (expand) { + const parentRow = rowsOf(self).find( + (row) => row.kind === "agent" && row.summary.sessionId === "root-session", + ); + expect(parentRow?.identity).toBe("session:root-session"); + expandedSubagentParents.add(parentRow!.identity); + invoke("reconcileCatalogs", self); + expect(rowsOf(self).some((row) => row.kind === "subagent")).toBe(true); + } + // The runtime flushes the session file; the record identity flips to file:. + self.lastListedSummaries = [{ ...parent, sessionFile: "/tmp/root.jsonl" }, child]; + invoke("reconcileCatalogs", self); + return { self, expandedSubagentParents }; + }; + + const expandedView = buildView(true); + const expandedRows = rowsOf(expandedView.self); + expect( + expandedRows.find((row) => row.kind === "agent" && row.summary.sessionId === "root-session")?.identity, + ).toBe("file:/tmp/root.jsonl"); + expect(expandedRows.some((row) => row.kind === "subagent-summary")).toBe(false); + expect(expandedRows.some((row) => row.kind === "subagent" && row.summary.sessionId === "child-session")).toBe( + true, + ); + expect([...expandedView.expandedSubagentParents]).toEqual(["file:/tmp/root.jsonl"]); + + const collapsedView = buildView(false); + const collapsedRows = rowsOf(collapsedView.self); + expect(collapsedRows.some((row) => row.kind === "subagent-summary" && row.expanded)).toBe(false); + expect(collapsedRows.some((row) => row.kind === "subagent")).toBe(false); + expect(collapsedView.expandedSubagentParents.size).toBe(0); + }); + + it("toggles subagent list expansion from the parent row", () => { + const expandedSubagentParents = new Set(["root-row"]); + const programShownParents = new Set(["root-row"]); + const persistentState: AgentsViewPersistentState = { + expandedSubagentParents, + programShownParents, + }; + const self: Record = { + persistentState, + expandedSubagentParents, + programShownParents, + rebuildRows: vi.fn(), + syncSelectedRowState: vi.fn(), + ui: { requestRender: vi.fn() }, + }; + const summaryRow = { kind: "agent", identity: "root-row", expanded: true }; + + invoke("toggleSubagentList", self, summaryRow); + expect(expandedSubagentParents.size).toBe(0); + // Collapsing the list hides its revealed program too. + expect(programShownParents.size).toBe(0); + expect(self.rebuildRows).toHaveBeenCalledTimes(1); + + invoke("toggleSubagentList", self, { ...summaryRow, expanded: false }); + expect(expandedSubagentParents).toEqual(new Set(["root-row"])); + expect(programShownParents.size).toBe(0); + expect(self.rebuildRows).toHaveBeenCalledTimes(2); + }); + + it("renders roster recovery and stale-worker status labels", () => { + const rows = buildAgentsViewRows([ + summary({ id: "recovering", sessionId: "recovering", statusLabel: "recovering" }), + summary({ + id: "stale", + sessionId: "stale", + lastHeardFromAt: new Date(Date.now() - 60_000).toISOString(), + }), + ]); + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + Reflect.set(view, "rows", rows); + + try { + expect(invoke("renderRow", view, rows[0], 160)).toContain("recovering"); + expect(invoke("renderRow", view, rows[1], 160)).toContain("last heard"); + } finally { + stopThemeWatcher(); + } + }); + + it("shows each session model and aligned total cost including collapsed descendants", () => { + const created = new Date(Date.now() - 120_000).toISOString(); + const parent = summary({ + id: "spender", + activeSessionId: "spender", + sessionId: "spender-session", + sessionName: "spender", + model: { ...getModel("openai", "gpt-4o"), id: "gpt-5.6-sol" }, + created, + summary: "Analyzing runtime composition", + usage: { inputTokens: 12437, outputTokens: 1234, cost: 0.42 }, + }); + const child = summary({ + id: "child", + activeSessionId: "child", + sessionId: "child-session", + sessionFile: "/tmp/child.jsonl", + runtimeKind: "subagent", + parentActiveSessionId: "spender", + model: { ...getModel("openai", "gpt-4o"), provider: "prime-inference", id: "glm-5.2-fast" }, + created, + usage: { inputTokens: 500, outputTokens: 50, cost: 0.68 }, + }); + const inactive = summary({ + id: "saved", + activeSessionId: undefined, + sessionId: "saved-session", + sessionFile: "/tmp/saved.jsonl", + rosterStatus: "inactive", + created, + model: { ...getModel("openai", "gpt-4o"), provider: "prime-inference", id: "glm-5.2-fast" }, + usage: { inputTokens: 900, outputTokens: 80, cost: 123.45 }, + }); + const rows = buildAgentsViewRows([parent, child, inactive]); + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + Reflect.set(view, "rows", rows); + Reflect.set(view, "selectedIndex", -1); + try { + const parentRow = rows.find((row) => row.summary.sessionId === parent.sessionId)!; + const savedRow = rows.find((row) => row.summary.sessionId === inactive.sessionId)!; + const render = (row: AgentsViewRow, width: number) => + stripAnsi(invoke("renderRow", view, row, width, buildCompactAgentsViewLayout(rows, width)) as string); + const parentLine = render(parentRow, 120); + const savedLine = render(savedRow, 120); + expect(parentLine).toContain("gpt-5.6-sol"); + expect(savedLine).toContain("glm-5.2-fast"); + expect(parentLine).toContain("$1.10"); + expect(parentLine).not.toContain("$0.42"); + expect(parentLine).not.toMatch(/[↑↓]/); + expect(parentLine).toMatch(/2m\s*$/); + expect(parentLine.indexOf("$1.10") + "$1.10".length).toBe(savedLine.indexOf("$123.45") + "$123.45".length); + for (const width of [60, 80]) { + const narrow = render(parentRow, width); + expect(narrow).toContain("gpt-5.6-sol"); + expect(narrow).toContain("$1.10"); + expect(narrow).toMatch(/2m\s*$/); + expect(narrow.length).toBeLessThanOrEqual(width); + } + } finally { + stopThemeWatcher(); + } + }); + + it("shows the recorded model on inactive saved sessions and keeps '-' without one", () => { + const saved = (id: string, model?: { provider: string; modelId: string }) => ({ + path: `/tmp/${id}.jsonl`, + id, + cwd: "/tmp", + created: new Date("2026-01-01T00:00:00Z"), + modified: new Date("2026-01-01T00:00:00Z"), + messageCount: 1, + firstMessage: "hello", + allMessagesText: "hello", + ...(model ? { model } : {}), + }); + const records = reconcileUnifiedSessions( + [], + [saved("with-model", { provider: "prime-inference", modelId: "glm-4.7" }), saved("bare")], + ); + const rows = buildAgentsViewRows(records); + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + Reflect.set(view, "rows", rows); + Reflect.set(view, "selectedIndex", -1); + try { + const render = (id: string) => + stripAnsi(invoke("renderRow", view, rows.find((row) => row.summary.sessionId === id)!, 120) as string); + expect(render("with-model")).toContain("glm-4.7"); + expect(render("bare")).toMatch(/\s-\s/); + expect(render("bare")).not.toContain("glm-4.7"); + // The actions panel shows the same recorded model instead of "unknown". + Reflect.set( + view, + "selectedIndex", + rows.findIndex((row) => row.summary.sessionId === "with-model"), + ); + const actions = (invoke("renderActions", view, 120) as string[]).map(stripAnsi).join("\n"); + expect(actions).toContain("Model: prime-inference/glm-4.7"); + } finally { + stopThemeWatcher(); + } + }); + + it("renders remote host on session list row and actions pane", () => { + const remoteSession = summary({ + id: "remote", + activeSessionId: "remote", + sessionId: "remote-session", + sessionName: "remote-task", + model: { ...getModel("anthropic", "claude-sonnet-4-5"), provider: "anthropic", id: "claude-sonnet-4-5" }, + remoteHost: "chernobog", + }); + const rows = buildAgentsViewRows([remoteSession]); + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + Reflect.set(view, "rows", rows); + Reflect.set(view, "selectedIndex", 0); + try { + const render = (row: AgentsViewRow, width: number) => + stripAnsi(invoke("renderRow", view, row, width, buildCompactAgentsViewLayout(rows, width)) as string); + const line = render(rows[0]!, 120); + expect(line).toContain("claude-sonnet-4-5@chernobog"); + + const actions = (invoke("renderActions", view, 120) as string[]).map((l) => stripAnsi(l)); + expect(actions.some((l) => l.includes("Host: chernobog"))).toBe(true); + } finally { + stopThemeWatcher(); + } + }); + + it("renders one column header across status groups without repeating subagent hints", () => { + const summaries = [ + summary({ + id: "busy", + activeSessionId: "busy", + sessionId: "busy-session", + sessionName: "busy", + activity: "working", + isStreaming: true, + }), + summary({ + id: "idle", + activeSessionId: "idle", + sessionId: "idle-session", + sessionName: "idle", + sessionFile: "/tmp/idle.jsonl", + }), + summary({ + id: "child", + sessionId: "child-session", + sessionFile: "/tmp/child.jsonl", + runtimeKind: "subagent", + parentActiveSessionId: "busy", + }), + ]; + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + try { + Reflect.set(view, "lastListedSummaries", summaries); + invoke("reconcileCatalogs", view); + Reflect.set(view, "selectedIndex", -1); + Reflect.set(view, "ui", { terminal: { rows: 60 }, requestRender: () => {} }); + const rendered = invoke("renderSessionRows", view, 120, 40) as string[]; + const lines = rendered.map(stripAnsi); + expect(lines.filter((line) => /Model/.test(line) && /Age/i.test(line))).toHaveLength(1); + expect(lines.some((line) => line.startsWith("Running"))).toBe(true); + expect(lines.some((line) => line.startsWith("Idle"))).toBe(true); + expect(lines.join("\n")).not.toMatch(/show program|#sub|\$agent|↑in|↓out/); + const rows = Reflect.get(view, "rows") as AgentsViewRow[]; + expect(rows.filter((row) => row.kind === "subagent-summary")).toHaveLength(0); + for (const line of rendered) { + expect(invoke("finalizeRenderedLine", view, line, 120)).not.toContain("\x1b[48"); + } + } finally { + stopThemeWatcher(); + } + }); + + it("always renders inactive sessions; search is the only filter", () => { + const live = summary({ sessionName: "live" }); + const saved = summary({ + id: "saved", + activeSessionId: undefined, + sessionId: "saved-session", + sessionName: "archive-match", + sessionFile: "/tmp/saved.jsonl", + rosterStatus: "inactive", + lifecycle: "archived", + }); + // The stale pre-removal collapse flag must be ignored. + const persistentState = { savedCatalogLoaded: true, inactiveExpanded: false } as AgentsViewPersistentState; + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, persistentState); + const rows = () => Reflect.get(view, "rows") as AgentsViewRow[]; + const showsSaved = () => rows().some((row) => row.summary.sessionId === saved.sessionId); + try { + Reflect.set(view, "lastListedSummaries", [live]); + Reflect.set(view, "savedSessions", [ + { + path: saved.sessionFile!, + id: saved.sessionId, + cwd: saved.cwd, + name: saved.sessionName, + created: new Date(), + modified: new Date(), + messageCount: 1, + firstMessage: "archive-match", + allMessagesText: "archive-match", + }, + ]); + invoke("reconcileCatalogs", view); + expect(showsSaved()).toBe(true); + // The removed Alt+I chord must not hide anything. + view.handleInput("\x1bi"); + expect(showsSaved()).toBe(true); + invoke("setSearchQuery", view, "no-such-session"); + expect(showsSaved()).toBe(false); + } finally { + stopThemeWatcher(); + } + }); + + it("opens a parent with Enter and reveals its spawn program only on request", () => { + const parent = summary({ sessionName: "parent" }); + const child = summary({ + id: "child", + activeSessionId: "child", + sessionId: "child-session", + sessionFile: "/tmp/child.jsonl", + runtimeKind: "subagent", + parentActiveSessionId: parent.activeSessionId, + spawnCode: 'await rlm("Inspect the code")', + }); + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + const rows = () => Reflect.get(view, "rows") as AgentsViewRow[]; + try { + Reflect.set(view, "lastListedSummaries", [parent, child]); + invoke("reconcileCatalogs", view); + expect(rows().map((row) => row.kind)).toEqual(["agent"]); + const finish = vi.fn(); + Reflect.set(view, "finish", finish); + invoke("openSelected", view); + expect(finish).toHaveBeenCalledWith( + expect.objectContaining({ + type: "open", + summary: expect.objectContaining({ sessionId: parent.sessionId }), + }), + ); + invoke("cycleProgramForSelected", view); + expect(rows().some((row) => row.kind === "subagent-code" && row.code === child.spawnCode)).toBe(true); + expect(rows().some((row) => row.kind === "subagent" && row.summary.sessionId === child.sessionId)).toBe(true); + expect(rows().some((row) => row.kind === "subagent-summary")).toBe(false); + invoke("cycleProgramForSelected", view); + expect(rows().some((row) => row.kind === "subagent-code")).toBe(false); + } finally { + stopThemeWatcher(); + } + }); + + it("keeps a selection at the end of the list visible when the leading ellipsis is shown", () => { + const summaries = Array.from({ length: 12 }, (_, index) => + summary({ + id: `saved-${index}`, + activeSessionId: undefined, + sessionId: `saved-${index}-session`, + sessionName: `saved-${index}`, + sessionFile: `/tmp/saved-${index}.jsonl`, + rosterStatus: "inactive" as const, + created: `2026-01-${String(index + 1).padStart(2, "0")}T00:00:00Z`, + }), + ); + const rows = buildAgentsViewRows(summaries); + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + + try { + Reflect.set(view, "rows", rows); + Reflect.set(view, "selectedIndex", rows.length - 1); + Reflect.set(view, "ui", { terminal: { rows: 13 }, requestRender: () => {} }); + const lines = (invoke("renderSessionRows", view, 120, 4) as string[]).map(stripAnsi); + expect(lines[1]).toContain("..."); + expect(lines).toHaveLength(4); + const lastTitle = rows.at(-1)!.title; + expect(lines.some((line) => line.includes(lastTitle))).toBe(true); + } finally { + stopThemeWatcher(); + } + }); + + it("reveals usage details through actions and closes them before searching", () => { + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, { savedCatalogLoaded: true }); + try { + Reflect.set(view, "lastListedSummaries", [ + summary({ sessionName: "parent", usage: { inputTokens: 1234, outputTokens: 56, cost: 1.23 } }), + ]); + invoke("reconcileCatalogs", view); + view.handleInput("?"); + const actions = (invoke("renderSessionRows", view, 120, 20) as string[]).map(stripAnsi).join("\n"); + expect(actions).toContain("1234 in"); + expect(actions).toContain("$1.23"); + view.handleInput("p"); + expect(Reflect.get(view, "showActions")).toBe(false); + const rows = (invoke("renderSessionRows", view, 120, 20) as string[]).map(stripAnsi).join("\n"); + expect(rows).toContain("parent"); + expect(rows).not.toContain("1234 in"); + } finally { + stopThemeWatcher(); + } + }); + + it("shows running-subagent counts only while collapsed and work remains", () => { + const parent = summary({ sessionName: "parent" }); + const child = summary({ + id: "child", + activeSessionId: "child", + sessionId: "child-session", + sessionFile: "/tmp/child.jsonl", + runtimeKind: "subagent", + parentActiveSessionId: parent.activeSessionId, + activity: "working", + isStreaming: true, + }); + const secondChild = { + ...child, + id: "child-2", + activeSessionId: "child-2", + sessionId: "child-session-2", + sessionFile: "/tmp/child-2.jsonl", + }; + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + const rows = () => Reflect.get(view, "rows") as AgentsViewRow[]; + const lines = () => (invoke("renderSessionRows", view, 120, 20) as string[]).map(stripAnsi); + try { + Reflect.set(view, "lastListedSummaries", [parent, child, secondChild]); + invoke("reconcileCatalogs", view); + expect(rows()).toHaveLength(1); + expect(invoke("renderRow", view, rows()[0], 120)).toContain("▸"); + const collapsed = lines(); + const parentIndex = collapsed.findIndex((line) => line.includes("parent")); + expect(collapsed[parentIndex + 1]).toBe(" 2 subagents running"); + invoke("moveSelection", view, 1); + expect(Reflect.get(view, "selectedIndex")).toBe(0); + view.handleInput("\x1b[1;3C"); + expect(rows().map((row) => row.kind)).toEqual(["agent", "subagent", "subagent"]); + expect(lines().join("\n")).not.toContain("subagents running"); + expect(invoke("renderRow", view, rows()[0], 120)).toContain("▾"); + view.handleInput("\x1b[1;3C"); + expect(rows()).toHaveLength(1); + expect(lines()).toContain(" 2 subagents running"); + const idleChild = { ...child, activity: "idle", isStreaming: false }; + Reflect.set(view, "lastListedSummaries", [parent, idleChild, secondChild]); + invoke("reconcileCatalogs", view); + expect(lines()).toContain(" 1 subagent running"); + Reflect.set(view, "lastListedSummaries", [ + parent, + idleChild, + { ...secondChild, activity: "idle", isStreaming: false }, + ]); + invoke("reconcileCatalogs", view); + expect(lines().join("\n")).not.toMatch(/subagents? running/); + } finally { + stopThemeWatcher(); + } + }); + + it("dims a paused-only heartbeat badge and keeps active badges in the error color", () => { + const job = (status: "active" | "paused") => ({ + job: { + id: `${status}-job`, + status, + activeSessionId: "scope-active", + sessionId: "scope-session", + sessionFile: "/tmp/scope.jsonl", + cwd: "/tmp", + prompt: "tick", + schedule: { kind: "interval" as const, expression: "5m" }, + createdAt: "2026-01-01T00:00:00Z", + updatedAt: "2026-01-01T00:00:00Z", + runCount: 0, + }, + }); + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + + try { + const [pausedRow] = buildAgentsViewRows(reconcileUnifiedSessions([summary()], [], [job("paused")])); + Reflect.set(view, "rows", [pausedRow]); + const pausedLine = invoke("renderRow", view, pausedRow, 160) as string; + expect(pausedLine).toContain(theme.fg("dim", "♥ 1")); + expect(pausedRow).toMatchObject({ section: "idle" }); + + const [activeRow] = buildAgentsViewRows(reconcileUnifiedSessions([summary()], [], [job("active")])); + Reflect.set(view, "rows", [activeRow]); + const activeLine = invoke("renderRow", view, activeRow, 160) as string; + expect(activeLine).toContain(theme.fg("error", "♥ 1")); + } finally { + stopThemeWatcher(); + } + }); + + it("warns about the armed heartbeat in the delete confirmation", () => { + const view = new AgentsViewMode({ config: {}, uiServices: createUiServices() }, {}); + Reflect.set(view, "deleteConfirmExpiresAt", Date.now() + 10_000); + const confirmLine = (row: AgentsViewRow): string => { + Reflect.set(view, "rows", [row]); + Reflect.set(view, "pendingDeleteAgent", { identity: row.identity, summary: row.summary, stopped: false }); + return stripAnsi(invoke("renderRow", view, row, 160) as string); + }; + + try { + const [armedRow] = buildAgentsViewRows([summary({ hasActiveHeartbeat: true })]); + expect(confirmLine(armedRow!)).toContain("has an armed heartbeat — "); + const [plainRow] = buildAgentsViewRows([summary()]); + expect(confirmLine(plainRow!)).not.toContain("armed heartbeat"); + expect(confirmLine(plainRow!)).toContain("again to remove"); + } finally { + stopThemeWatcher(); + } + });>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) }); function createUiServices(): InteractiveModeUiServices { diff --git a/packages/coding-agent/test/args.test.ts b/packages/coding-agent/test/args.test.ts index 5db4f60e7..265050463 100644 --- a/packages/coding-agent/test/args.test.ts +++ b/packages/coding-agent/test/args.test.ts @@ -290,7 +290,9 @@ const AUTONOMOUS_VALUE_FLAGS = [ "--autonomous-max-turns", "--autonomous-max-tokens", "--autonomous-timeout-ms", -]; +];>>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) + + describe("parseArgs", () => { test.each(parseCases)("parses %s", (_label, argv, expected) => { @@ -346,4 +348,65 @@ describe("parseArgs", () => { expect(listed.listModels).toBeUndefined(); expect(listed.messages).toEqual([]); }); + + describe("remote python flags", () => { + const originalEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + test("--remote-python sets remotePython and environment variable", () => { + const result = parseArgs(["--remote-python", "/usr/bin/python3"]); + expect(result.remotePython).toBe("/usr/bin/python3"); + expect(process.env.PRIME_AGENT_REMOTE_PYTHON).toBe("/usr/bin/python3"); + expect(result.diagnostics).toEqual([]); + }); + + test("--remote-python without value produces an error", () => { + const result = parseArgs(["--remote-python"]); + expect(result.remotePython).toBeUndefined(); + expect(result.diagnostics).toEqual([{ type: "error", message: "--remote-python requires a value" }]); + }); + + test("--remote-python-host sets remotePythonHost and environment variable", () => { + const result = parseArgs(["--remote-python-host", "chernobog"]); + expect(result.remotePythonHost).toBe("chernobog"); + expect(process.env.PRIME_AGENT_REMOTE_PYTHON_HOST).toBe("chernobog"); + expect(result.diagnostics).toEqual([]); + }); + + test("--remote-host aliases --remote-python-host", () => { + const result = parseArgs(["--remote-host", "mayer@chernobog"]); + expect(result.remotePythonHost).toBe("mayer@chernobog"); + expect(process.env.PRIME_AGENT_REMOTE_PYTHON_HOST).toBe("mayer@chernobog"); + expect(result.diagnostics).toEqual([]); + }); + + test("--remote-python-host without value produces an error", () => { + const result = parseArgs(["--remote-python-host"]); + expect(result.remotePythonHost).toBeUndefined(); + expect(result.diagnostics).toEqual([{ type: "error", message: "--remote-python-host requires a value" }]); + }); + + test("falls back to PRIME_AGENT_REMOTE_PYTHON_HOST when flag not provided", () => { + delete process.env.PRIME_AGENT_REMOTE_HOST; + process.env.PRIME_AGENT_REMOTE_PYTHON_HOST = "env-host"; + const result = parseArgs([]); + expect(result.remotePythonHost).toBe("env-host"); + }); + + test("falls back to PRIME_AGENT_REMOTE_HOST when flag not provided", () => { + delete process.env.PRIME_AGENT_REMOTE_PYTHON_HOST; + process.env.PRIME_AGENT_REMOTE_HOST = "legacy-env-host"; + const result = parseArgs([]); + expect(result.remotePythonHost).toBe("legacy-env-host"); + }); + + test("CLI flag takes precedence over environment variable", () => { + process.env.PRIME_AGENT_REMOTE_PYTHON_HOST = "env-host"; + const result = parseArgs(["--remote-python-host", "cli-host"]); + expect(result.remotePythonHost).toBe("cli-host"); + }); + }); }); diff --git a/packages/coding-agent/test/daemon-list-format.test.ts b/packages/coding-agent/test/daemon-list-format.test.ts new file mode 100644 index 000000000..360c2b87e --- /dev/null +++ b/packages/coding-agent/test/daemon-list-format.test.ts @@ -0,0 +1,103 @@ +import type { Api, Model } from "@earendil-works/pi-ai"; +import stripAnsi from "strip-ansi"; +import { describe, expect, it } from "vitest"; +import { formatSessionListTable } from "../src/cli/daemon-list-format.js"; +import type { SessionActivity, SessionLifecycle, SessionSummary } from "../src/modes/daemon/daemon-session-list.js"; + +describe("formatSessionListTable", () => { + it("sorts sessions by status and renders compact suffix ids", () => { + const nowMs = Date.parse("2026-05-29T12:00:00.000Z"); + const table = stripAnsi( + formatSessionListTable( + [ + makeSummary({ + name: "sleep", + id: "019e71ec-e08a-75a9-b573-fc10e9f8380f", + lifecycle: "archived", + activity: "idle", + }), + makeSummary({ name: "tool", id: "ccccddddeeee", lifecycle: "live", activity: "working" }), + makeSummary({ + name: "crash", + id: "019e71ec-e08a-75a9-b573-abcdef123456", + lifecycle: "archived", + activity: "idle", + }), + makeSummary({ name: "idle", id: "bbbbccccdddd", lifecycle: "live", activity: "idle" }), + makeSummary({ name: "model", id: "ddddeeeeffff", lifecycle: "live", activity: "working" }), + makeSummary({ + name: "user", + id: "aaaabbbbcccc", + lifecycle: "live", + activity: "idle", + clients: 1, + model: { provider: "openai-codex", id: "gpt-5.5" } as Model, + }), + ], + nowMs, + ), + ); + + const lines = table.split("\n"); + expect(lines[0]!.trim().split(/\s+/)).toEqual(["name", "id", "status", "age", "model", "messages", "clients"]); + expect(lines.slice(1).map((line) => line.trim().split(/\s+/).slice(0, 3))).toEqual([ + ["tool", "ccccddddeeee", "working"], + ["model", "ddddeeeeffff", "working"], + ["idle", "bbbbccccdddd", "idle"], + ["user", "aaaabbbbcccc", "idle"], + ["sleep", "fc10e9f8380f", "archived"], + ["crash", "abcdef123456", "archived"], + ]); + expect(table).toContain("openai-codex/gpt-5.5"); + expect(table).not.toContain("/tmp/project"); + expect(table).not.toContain("019e71ec-e08a"); + }); + + it("appends remoteHost to model in session list table", () => { + const nowMs = Date.parse("2026-05-29T12:00:00.000Z"); + const table = stripAnsi( + formatSessionListTable( + [ + makeSummary({ + name: "remote-worker", + id: "aaaabbbbcccc", + lifecycle: "live", + activity: "idle", + model: { provider: "anthropic", id: "claude-3-7-sonnet" } as Model, + remoteHost: "chernobog", + }), + ], + nowMs, + ), + ); + expect(table).toContain("anthropic/claude-3-7-sonnet@chernobog"); + }); +}); + +function makeSummary(options: { + name: string; + id: string; + lifecycle: SessionLifecycle; + activity: SessionActivity; + clients?: number; + model?: Model; + remoteHost?: string; +}): SessionSummary { + return { + id: options.id, + lifecycle: options.lifecycle, + activity: options.activity, + isSessionActive: options.activity === "working", + sessionId: options.id, + sessionName: options.name, + cwd: "/tmp/project", + model: options.model, + remoteHost: options.remoteHost, + isStreaming: options.lifecycle === "live" && options.activity === "working", + isCompacting: false, + attachedClients: options.clients ?? 0, + messageCount: 2, + sessionActions: { queuedCount: 0, steering: [], followUps: [] }, + modified: "2026-05-29T10:00:00.000Z", + }; +} diff --git a/packages/coding-agent/test/daemon-session-list.test.ts b/packages/coding-agent/test/daemon-session-list.test.ts index 708e83a7e..e83814a9f 100644 --- a/packages/coding-agent/test/daemon-session-list.test.ts +++ b/packages/coding-agent/test/daemon-session-list.test.ts @@ -500,6 +500,17 @@ describe("summaryForActiveSession recap currency", () => { expect(summary.summary).toBe(expected.summary); expect(summary.taskState).toBe(expected.taskState); + it("populates remoteHost from active session", () => { + const summary = summaryForActiveSession( + makeState({ + activeSessionId: "s1", + remoteHost: "chernobog", + }), + ); + expect(summary.remoteHost).toBe("chernobog"); + }); + + >>>>>>> c359252ae (feat(coding-agent): add remote python host support and ui indicators) }); }); @@ -594,6 +605,7 @@ interface StateOptions { usage?: SessionUsageSummary; hasRunningRlmChildren?: boolean; hasAcceptedPromptInFlight?: boolean; + remoteHost?: string; unfinishedActionCount?: number; contextTokens?: number; streamingMessage?: AgentMessage; @@ -635,6 +647,7 @@ function makeState(options: StateOptions): ActiveSessionState { sessionId: options.sessionId ?? `session-${options.activeSessionId}`, rlmDepth: options.rlmDepth ?? 0, sessionName: `session ${options.activeSessionId}`, + remoteHost: options.remoteHost, sessionManager: { getCwd: () => "/tmp/project", getHeader: () => ({ timestamp: "2026-05-01T00:00:00.000Z" }), diff --git a/packages/coding-agent/test/interactive-mode-status.test.ts b/packages/coding-agent/test/interactive-mode-status.test.ts index c4c898da7..5d4c2061c 100644 --- a/packages/coding-agent/test/interactive-mode-status.test.ts +++ b/packages/coding-agent/test/interactive-mode-status.test.ts @@ -17,7 +17,8 @@ import type { AgentConnectionState, } from "../src/modes/agent-connection/types.js"; import type { ToolExecutionComponent } from "../src/modes/interactive/components/tool-execution.js"; -import { InteractiveMode } from "../src/modes/interactive/interactive-mode.js"; +import stripAnsi from "strip-ansi"; +import { BrandSplashHeader, InteractiveMode } from "../src/modes/interactive/interactive-mode.js"; import { QueueSelection } from "../src/modes/interactive/queue-selection.js"; import { initTheme } from "../src/modes/interactive/theme/theme.js"; @@ -1281,3 +1282,50 @@ describe("InteractiveMode Fast mode concurrency", () => { expect(context.showStatus).not.toHaveBeenCalled(); }); }); + +describe("remote host indicators", () => { + function createMode(remoteHost: string | undefined) { + const mode = { + options: { sessionDepth: undefined, sessionHasChildren: false, returnToAgentsView: false }, + editor: {}, + editorContainer: { children: [] }, + ui: { hasOverlay: () => false }, + subagentSnapshots: undefined, + connectionState: { + model: { name: "test-model", reasoning: true }, + thinkingLevel: "high" as const, + messageCount: 0, + isStreaming: false, + remoteHost, + }, + }; + Object.setPrototypeOf(mode, InteractiveMode.prototype); + return mode; + } + + it("shows the remote host in the tray location label", () => { + const mode = createMode("chernobog"); + const label = Reflect.get(InteractiveMode.prototype, "getTrayLocationLabel").call(mode) as string; + + expect(stripAnsi(label)).toContain("on chernobog"); + }); + + it("omits the host label when no remote host is set", () => { + const mode = createMode(undefined); + const label = Reflect.get(InteractiveMode.prototype, "getTrayLocationLabel").call(mode) as string; + + expect(stripAnsi(label)).not.toContain("on "); + }); + + it("renders remote host in splash header metadata when provided", () => { + const header = new BrandSplashHeader("0.0.0", () => "/tmp/project", undefined, { + topPadding: true, + getExtraMetadata: () => [{ label: "host", value: "chernobog" }], + }); + + const output = stripAnsi(header.render(120).join("\n")); + + expect(output).toContain("host"); + expect(output).toContain("chernobog"); + }); +}); diff --git a/packages/coding-agent/test/remote-python-host.test.ts b/packages/coding-agent/test/remote-python-host.test.ts new file mode 100644 index 000000000..ac88518a5 --- /dev/null +++ b/packages/coding-agent/test/remote-python-host.test.ts @@ -0,0 +1,79 @@ +import { mkdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { getModel } from "@earendil-works/pi-ai"; +import { describe, expect, it } from "vitest"; +import { parseArgs } from "../src/cli/args.js"; +import { mergeAgentSessionRuntimeConfig } from "../src/core/agent-session-config.js"; +import { AuthStorage } from "../src/core/auth-storage.js"; +import { createAgentSession } from "../src/core/sdk.js"; +import { SessionManager } from "../src/core/session-manager.js"; +import { SettingsManager } from "../src/core/settings-manager.js"; +import { createAgentConnectionState } from "../src/modes/agent-connection/snapshot.js"; + +describe("remote python host configuration", () => { + it("reads remotePythonHost and remotePython from settings", () => { + const settings = SettingsManager.inMemory({ + remotePythonHost: "chernobog", + remotePython: "/opt/venv/bin/python", + }); + expect(settings.getRemotePythonHost()).toBe("chernobog"); + expect(settings.getRemotePython()).toBe("/opt/venv/bin/python"); + }); + + it("defaults remotePythonHost and remotePython to undefined when unset", () => { + const settings = SettingsManager.inMemory({}); + expect(settings.getRemotePythonHost()).toBeUndefined(); + expect(settings.getRemotePython()).toBeUndefined(); + }); + + it("parses --remote-python-host from CLI args", () => { + const parsed = parseArgs(["--remote-python-host", "chernobog"]); + expect(parsed.remotePythonHost).toBe("chernobog"); + expect(parsed.diagnostics).toEqual([]); + }); + + it("parses --remote-host alias from CLI args", () => { + const parsed = parseArgs(["--remote-host", "mayer@chernobog"]); + expect(parsed.remotePythonHost).toBe("mayer@chernobog"); + expect(parsed.diagnostics).toEqual([]); + }); + + it("merges remoteHost in runtime config", () => { + const base = { remoteHost: "chernobog" }; + const override = { cwd: "/new/cwd" }; + const merged = mergeAgentSessionRuntimeConfig(base, override); + expect(merged.remoteHost).toBe("chernobog"); + + const hostOverride = mergeAgentSessionRuntimeConfig(base, { remoteHost: "perun" }); + expect(hostOverride.remoteHost).toBe("perun"); + }); + + it("configures AgentSession with remoteHost via createAgentSession", async () => { + const tempDir = join(tmpdir(), `pi-remote-test-${Date.now()}`); + mkdirSync(tempDir, { recursive: true }); + try { + const authStorage = AuthStorage.create(join(tempDir, "auth.json")); + authStorage.setRuntimeApiKey("anthropic", "test-key"); + const { session } = await createAgentSession({ + cwd: tempDir, + agentDir: tempDir, + model: getModel("anthropic", "claude-sonnet-4-5")!, + settingsManager: SettingsManager.inMemory({}), + sessionManager: SessionManager.inMemory(tempDir), + authStorage, + remoteHost: "chernobog", + }); + expect(session.remoteHost).toBe("chernobog"); + expect(session.systemPrompt).toContain("Remote execution host: chernobog"); + expect(session.systemPrompt).toContain( + "The `ipython` tool and `bash()` execute on remote host chernobog over SSH.", + ); + const connectionState = createAgentConnectionState({ session } as any); + expect(connectionState.remoteHost).toBe("chernobog"); + session.dispose(); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/coding-agent/test/system-prompt.test.ts b/packages/coding-agent/test/system-prompt.test.ts new file mode 100644 index 000000000..67d14bcf5 --- /dev/null +++ b/packages/coding-agent/test/system-prompt.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from "vitest"; +import { buildSystemPrompt } from "../src/core/system-prompt.js"; + +describe("buildSystemPrompt", () => { + test("includes remote host in system prompt when remoteHost is provided", () => { + const prompt = buildSystemPrompt({ + selectedTools: ["ipython"], + contextFiles: [], + skills: [], + remoteHost: "chernobog", + }); + + expect(prompt).toContain("Remote execution host: chernobog"); + expect(prompt).toContain("The `ipython` tool and `bash()` execute on remote host chernobog over SSH."); + }); +}); -- 2.51.2