@saadnvd1/agent-os 0.3.472 → 0.3.474

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,6 +52,14 @@ export function envArgs(env: Record<string, string>): string[] {
52
52
  return Object.entries(env).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
53
53
  }
54
54
 
55
+ // Tool search is off on a routed provider, so every MCP server the user or
56
+ // the project has would put all its schemas in every prompt. A terminal
57
+ // agent reaches AgentOS through the aos CLI, so it needs none. Shell text
58
+ // for the agent's command line; empty on Claude.
59
+ export function routedMcpFlags(provider?: string | null): string {
60
+ return provider ? "--strict-mcp-config " : "";
61
+ }
62
+
55
63
  // Written just before the agent's tmux session is created: a session with
56
64
  // no prompt file is not one launchClaude started.
57
65
  export const promptFileFor = (sessionId: string) =>
@@ -97,7 +105,8 @@ export async function launchClaude(opts: {
97
105
  model: opts.model,
98
106
  model_provider: opts.provider ?? null,
99
107
  });
100
- const agent = `export PATH=${shellQuote(AOS_BIN_DIR)}:"$HOME/.local/bin:$PATH"; ${routing.exportKey} ${provider.command} ${flags} ${CLAUDE_STATUS_SETTINGS_FLAG} --append-system-prompt-file ${shellQuote(briefFile)} "$(cat ${shellQuote(promptFile)})"; exec "\${SHELL:-/bin/sh}" -l`;
108
+ const mcpFlags = routedMcpFlags(opts.provider);
109
+ const agent = `export PATH=${shellQuote(AOS_BIN_DIR)}:"$HOME/.local/bin:$PATH"; ${routing.exportKey} ${provider.command} ${flags} ${mcpFlags}${CLAUDE_STATUS_SETTINGS_FLAG} --append-system-prompt-file ${shellQuote(briefFile)} "$(cat ${shellQuote(promptFile)})"; exec "\${SHELL:-/bin/sh}" -l`;
101
110
  await execFileAsync(
102
111
  "tmux",
103
112
  [
@@ -30,6 +30,9 @@ export interface ChatStartOptions {
30
30
  // Tools served in-process for this conversation, and tools it may use
31
31
  // without asking.
32
32
  mcpServers?: Record<string, McpServerConfig>;
33
+ // Load no MCP servers but those above: the user's own would otherwise
34
+ // put every tool schema in each prompt.
35
+ strictMcpConfig?: boolean;
33
36
  allowedTools?: string[];
34
37
  disallowedTools?: string[];
35
38
  // A fixed permission mode in place of the access setting, which then
@@ -188,6 +188,7 @@ export const claudeDriver: ChatDriver = {
188
188
  allowDangerouslySkipPermissions: true,
189
189
  canUseTool: approvals.canUseTool,
190
190
  mcpServers: options.mcpServers,
191
+ strictMcpConfig: options.strictMcpConfig,
191
192
  allowedTools: options.allowedTools,
192
193
  disallowedTools: options.disallowedTools,
193
194
  hooks: {
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { db } from "../db";
14
+ import { effectiveModels } from "../models/config";
14
15
  import { MAX_REFRESH_TOKENS, MIN_REFRESH_TOKENS } from "./refresh-limits";
15
16
 
16
17
  export const ORCHESTRATOR_KEY = "orchestrator.refresh_tokens";
@@ -87,6 +88,25 @@ export function setRefreshSettings(input: Partial<RefreshSettings>): void {
87
88
  // as 150 and clamps anything under 100k up to it).
88
89
  export const AUTO_COMPACT_ENV = "CLAUDE_CODE_AUTO_COMPACT_WINDOW";
89
90
  const AUTO_COMPACT_MIN = 100_000;
91
+ // Claude Code turns tool search off for a host that isn't Anthropic's, so
92
+ // every MCP and deferred tool schema rides in each prompt: measured on
93
+ // DeepSeek, the first call after a compaction is ~100k tokens against ~53k
94
+ // on Claude. A window measured from zero left it 15-30k of room, so it
95
+ // compacted every couple of minutes. Providers get that extra on top.
96
+ export const PROVIDER_BASE_TOKENS = 60_000;
97
+
98
+ // The compact threshold for a session on this provider (null: Anthropic).
99
+ export function compactThreshold(
100
+ tokens: number,
101
+ provider: string | null | undefined
102
+ ): number {
103
+ if (tokens <= 0 || !provider) return tokens;
104
+ const window = effectiveModels().providers.find(
105
+ (p) => p.id === provider
106
+ )?.contextWindow;
107
+ const wanted = tokens + PROVIDER_BASE_TOKENS;
108
+ return window ? Math.min(wanted, window) : wanted;
109
+ }
90
110
 
91
111
  // What an agent is launched with so it compacts itself at the threshold.
92
112
  // Not an orchestrator: it starts over from its brief at its own threshold
@@ -95,11 +115,17 @@ const AUTO_COMPACT_MIN = 100_000;
95
115
  // the user's own environment still applies.
96
116
  export function autoCompactEnv(sessionId: string): Record<string, string> {
97
117
  const row = db
98
- .prepare(`SELECT role FROM sessions WHERE id = ?`)
99
- .get(sessionId) as { role: string | null } | undefined;
118
+ .prepare(`SELECT role, model_provider FROM sessions WHERE id = ?`)
119
+ .get(sessionId) as
120
+ | { role: string | null; model_provider: string | null }
121
+ | undefined;
100
122
  const tokens = refreshSettings().compactTokens;
101
123
  if (row?.role === "orchestrator" || tokens <= 0) return {};
102
- return { [AUTO_COMPACT_ENV]: String(Math.max(AUTO_COMPACT_MIN, tokens)) };
124
+ const window = compactThreshold(
125
+ Math.max(AUTO_COMPACT_MIN, tokens),
126
+ row?.model_provider
127
+ );
128
+ return { [AUTO_COMPACT_ENV]: String(window) };
103
129
  }
104
130
 
105
131
  const k = (tokens: number) => `${Math.round(tokens / 1000)}k`;
@@ -40,7 +40,7 @@ import {
40
40
  visualsTools,
41
41
  } from "../../artifacts/tools";
42
42
  import type { RoleExtras } from "./extras";
43
- import { refreshSettings, refreshedNote } from "../refresh";
43
+ import { compactThreshold, refreshSettings, refreshedNote } from "../refresh";
44
44
  import { compactionDue, staleLabel, type Staleness } from "../stale";
45
45
  import type { WorkerCommand, WorkerEvent } from "./protocol";
46
46
 
@@ -203,6 +203,10 @@ export class ChatHost {
203
203
  mcpServers: visuals
204
204
  ? { ...extras.mcpServers, [VISUALS_SERVER]: visuals }
205
205
  : extras.mcpServers,
206
+ // Claude Code turns tool search off on a non-Anthropic host, so each
207
+ // MCP server the user has adds all its schemas to every prompt
208
+ // (~45k tokens): a routed agent gets only the servers AgentOS gives it.
209
+ strictMcpConfig: !!session.model_provider,
206
210
  allowedTools: extras.allowedTools,
207
211
  disallowedTools: extras.disallowedTools,
208
212
  permissionMode: extras.permissionMode,
@@ -743,7 +747,10 @@ export class ChatHost {
743
747
  }
744
748
  return compactionDue(context, {
745
749
  state: this.state,
746
- thresholdTokens: refreshSettings().compactTokens,
750
+ thresholdTokens: compactThreshold(
751
+ refreshSettings().compactTokens,
752
+ this.session.model_provider
753
+ ),
747
754
  });
748
755
  }
749
756
 
@@ -100,6 +100,7 @@ const providerSchema = z.strictObject({
100
100
  z.string().max(200)
101
101
  )
102
102
  .optional(),
103
+ contextWindow: z.number().int().min(100_000).max(10_000_000).optional(),
103
104
  enabled: z.boolean(),
104
105
  });
105
106
 
@@ -37,6 +37,9 @@ export interface ModelProvider {
37
37
  tiers?: ModelTiers;
38
38
  // Plain settings the endpoint wants (a longer timeout, say). Never a key.
39
39
  env?: Record<string, string>;
40
+ // The context window of its models, in tokens: Claude Code assumes a
41
+ // size from the model's name, and the compact window is capped at this.
42
+ contextWindow?: number;
40
43
  enabled: boolean;
41
44
  }
42
45
 
@@ -77,6 +80,9 @@ export const PROVIDER_PRESETS: ModelProvider[] = [
77
80
  sonnet: "deepseek-flash",
78
81
  haiku: "deepseek-flash",
79
82
  },
83
+ // 1M for deepseek-flash (api-docs.deepseek.com/quick_start/pricing);
84
+ // deepseek-v4-pro's isn't stated there, and is assumed to match.
85
+ contextWindow: 1_000_000,
80
86
  enabled: false,
81
87
  },
82
88
  {
@@ -88,6 +94,8 @@ export const PROVIDER_PRESETS: ModelProvider[] = [
88
94
  models: ["glm-5.3", "glm-5.3-flash"],
89
95
  tiers: { opus: "glm-5.3", sonnet: "glm-5.3", haiku: "glm-5.3-flash" },
90
96
  env: { API_TIMEOUT_MS: "3000000" },
97
+ // docs.z.ai/guides/llm/glm-5.3
98
+ contextWindow: 1_000_000,
91
99
  enabled: false,
92
100
  },
93
101
  ];
@@ -218,6 +218,9 @@ function routingEnv(p: ModelProvider, model: string): Record<string, string> {
218
218
  : "ANTHROPIC_API_KEY";
219
219
  return {
220
220
  ...p.env,
221
+ ...(p.contextWindow
222
+ ? { CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(p.contextWindow) }
223
+ : {}),
221
224
  ANTHROPIC_BASE_URL: p.baseUrl!,
222
225
  [other]: "",
223
226
  ANTHROPIC_DEFAULT_OPUS_MODEL: p.tiers?.opus ?? model,
@@ -27,10 +27,15 @@ const modelRef = z
27
27
  const modelOverride = z
28
28
  .union([
29
29
  modelRef,
30
- z.partialRecord(
31
- z.enum(TASK_ROLES as [ModelRole, ...ModelRole[]]),
32
- modelRef
33
- ),
30
+ // Named optional keys, not z.partialRecord: its "propertyNames" makes
31
+ // Claude Code drop every tool on this server, not just this one.
32
+ z
33
+ .object(
34
+ Object.fromEntries(
35
+ TASK_ROLES.map((role) => [role, modelRef.optional()])
36
+ ) as Record<ModelRole, z.ZodOptional<typeof modelRef>>
37
+ )
38
+ .strict(),
34
39
  ])
35
40
  .optional()
36
41
  .describe(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.472",
3
+ "version": "0.3.474",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"