@myagentroam/agent 0.9.151 → 0.9.153

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.
@@ -1,7 +1,7 @@
1
1
  import type { MarAgentEvent } from '../runtime/events.js';
2
2
  import type { ClientToolDefinition, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolExecutionContext, ToolExecutionResult } from '../tools/contracts.js';
3
3
  export declare const MAR_AGENT_HOST_API_VERSION = "1.0";
4
- export type MarAgentHostCapability = 'workspace.read' | 'workspace.patch' | 'process.manage' | 'code.execute' | 'user-input.request' | 'event.publish' | 'session.store' | 'mcp.tools' | 'image.read' | 'web.search' | 'web.fetch';
4
+ export type MarAgentHostCapability = 'workspace.read' | 'workspace.patch' | 'process.manage' | 'code.execute' | 'user-input.request' | 'event.publish' | 'session.store' | 'mcp.tools' | 'image.read';
5
5
  export interface HostDescription {
6
6
  apiVersion: string;
7
7
  mode: 'normal' | 'node';
@@ -16,6 +16,7 @@ export interface LocalAgentExecutionMcpBinding extends LocalAgentMcpBinding {
16
16
  export interface LocalAgentExecutionExtensions extends HostExecutionExtensions {
17
17
  /** Host-private process environment, snapshotted when bound to an execution. */
18
18
  readonly environment?: Readonly<Record<string, string>>;
19
+ isToolAvailable?(name: string, context: Pick<ToolExecutionContext, 'sessionId' | 'executionId' | 'modelId'>): boolean;
19
20
  executeTool?(call: ToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
20
21
  }
21
22
  export declare class LocalAgentHost implements MarAgentHost {
@@ -36,12 +37,6 @@ export declare class LocalAgentHost implements MarAgentHost {
36
37
  extensions?: LocalAgentExecutionExtensions;
37
38
  environment?: NodeJS.ProcessEnv;
38
39
  guaranteedCommands?: Readonly<Record<string, string>>;
39
- webSearch?: (input: {
40
- query: string;
41
- maxResults: number;
42
- }, signal: AbortSignal) => Promise<ToolExecutionResult>;
43
- webFetch?: boolean;
44
- allowUntrustedTls?: boolean;
45
40
  resourceIdleTtlMs?: number;
46
41
  processExecutor?: LocalProcessExecutor;
47
42
  codeModeEngineFactory?: CodeModeEngineFactory;
@@ -25,8 +25,6 @@ export class LocalAgentHost {
25
25
  #environment;
26
26
  #guaranteedCommands;
27
27
  #shell;
28
- #hasWebSearch;
29
- #hasWebFetch;
30
28
  #resourceIdleTtlMs;
31
29
  constructor(input) {
32
30
  this.#home = resolve(input.homeDirectory);
@@ -34,15 +32,9 @@ export class LocalAgentHost {
34
32
  this.#environment = input.environment ?? process.env;
35
33
  this.#guaranteedCommands = input.guaranteedCommands ?? {};
36
34
  this.#shell = selectExecutionShell(this.#environment);
37
- this.#hasWebSearch = input.webSearch !== undefined;
38
- this.#hasWebFetch = input.webFetch !== false;
39
35
  this.#resourceIdleTtlMs = input.resourceIdleTtlMs;
40
36
  const processExecutor = input.processExecutor ?? directLocalProcessExecutor;
41
37
  this.#tools = new LocalToolRegistry(this.#workspace, this.#environment, this.#shell.path, {
42
- ...(input.webSearch ? { webSearch: input.webSearch } : {}),
43
- webFetch: this.#hasWebFetch,
44
- allowUntrustedTls: input.allowUntrustedTls ??
45
- this.#environment['MAR_AGENT_WEB_FETCH_ALLOW_UNTRUSTED_TLS'] === '1',
46
38
  ...(input.codeModeEngineFactory === undefined
47
39
  ? {}
48
40
  : { codeModeEngineFactory: input.codeModeEngineFactory }),
@@ -97,9 +89,7 @@ export class LocalAgentHost {
97
89
  'code.execute',
98
90
  'user-input.request',
99
91
  'event.publish',
100
- 'session.store',
101
- ...(this.#hasWebFetch ? ['web.fetch'] : []),
102
- ...(this.#hasWebSearch ? ['web.search'] : [])
92
+ 'session.store'
103
93
  ],
104
94
  limits: {
105
95
  maxProcesses: TOOL_EXECUTION_LIMITS.maxProcesses,
@@ -113,7 +103,9 @@ export class LocalAgentHost {
113
103
  const binding = this.#executionMcp.get(context.executionId);
114
104
  const extensions = binding?.extensions ?? this.#extensions;
115
105
  if (extensions?.tools?.some((tool) => tool.name === call.name)) {
116
- if (binding?.active === false || !extensions.executeTool)
106
+ if (binding?.active === false ||
107
+ !extensions.executeTool ||
108
+ extensions.isToolAvailable?.(call.name, context) === false)
117
109
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Host tool execution is unavailable.');
118
110
  context.signal.throwIfAborted();
119
111
  return extensions.executeTool(call, context);
@@ -141,8 +133,15 @@ export class LocalAgentHost {
141
133
  }
142
134
  async listExecutionExtensions(context) {
143
135
  const binding = this.#executionBinding(context);
144
- const { environment: _environment, ...extensions } = binding?.extensions ?? this.#extensions ?? {};
145
- return extensions;
136
+ const { environment: _environment, isToolAvailable, ...extensions } = binding?.extensions ?? this.#extensions ?? {};
137
+ return {
138
+ ...extensions,
139
+ ...(extensions.tools === undefined
140
+ ? {}
141
+ : {
142
+ tools: extensions.tools.filter((tool) => isToolAvailable?.(tool.name, context) !== false)
143
+ })
144
+ };
146
145
  }
147
146
  emit(event) {
148
147
  this.events.push(event);
@@ -1,3 +1,3 @@
1
1
  import type { RolloutBudgetStage } from '../runtime/rollout-budget.js';
2
- export type ExecutionBudgetEnvironmentStage = Exclude<RolloutBudgetStage, 'normal'> | 'inactive';
2
+ export type ExecutionBudgetEnvironmentStage = Exclude<RolloutBudgetStage, 'normal' | 'exhausted'> | 'inactive';
3
3
  export declare function executionBudgetEnvironmentEntry(stage: ExecutionBudgetEnvironmentStage): string;
@@ -2,10 +2,8 @@ export function executionBudgetEnvironmentEntry(stage) {
2
2
  if (stage === 'inactive')
3
3
  return '<execution_guidance status="inactive" />';
4
4
  const privateInstruction = 'This is internal runtime guidance. Never mention the budget, thresholds, remaining amount, or budget stage in user-visible progress or the final answer.';
5
- const guidance = stage === 'exhausted'
6
- ? 'The shared rollout budget is exhausted. Do not start or continue investigation, optional work, new tool exploration, or new subagents. Return a concise final answer now using the current verified staged result. Clearly distinguish completed work from anything still incomplete, without mentioning this budget.'
7
- : stage === 'finalize'
8
- ? 'Enter finalization now. Do not start new investigation or new subagents. Complete only the current atomic change, the minimum necessary verification, and the final answer as soon as possible.'
9
- : 'Stop expanding scope and stop optional work. Prefer completing the current objective with the evidence already gathered, and avoid new investigation unless it is strictly required for correctness.';
5
+ const guidance = stage === 'finalize'
6
+ ? 'Enter finalization now. Do not start new investigation or new subagents. Complete only the current atomic change, the minimum necessary verification, and the final answer as soon as possible.'
7
+ : 'Stop expanding scope and stop optional work. Prefer completing the current objective with the evidence already gathered, and avoid new investigation unless it is strictly required for correctness.';
10
8
  return `<execution_guidance stage="${stage}">\n${privateInstruction}\n\n${guidance}\n</execution_guidance>`;
11
9
  }
@@ -34,6 +34,7 @@ export class ExecutionLoop {
34
34
  const pendingTools = [];
35
35
  let incompleteToolCall = false;
36
36
  const round = await input.prepareRound(pendingOutputContinuation);
37
+ input.rolloutBudget?.assertAvailable();
37
38
  let sawModelSemanticEvent = false;
38
39
  let requestStartedAt;
39
40
  let firstModelResponseAt;
@@ -293,11 +294,6 @@ export class ExecutionLoop {
293
294
  }
294
295
  recoveringFromContextOverflow = false;
295
296
  input.observeUsage(roundServerUsage);
296
- if (pendingTools.length === 0 && input.mailbox.hasPending()) {
297
- await input.mailbox.append();
298
- this.finalAnswer = '';
299
- continue;
300
- }
301
297
  const toolResults = await executeRoundTools(pendingTools, round.toolScope);
302
298
  for (const result of toolResults) {
303
299
  if ('images' in result && result.images?.length)
@@ -335,11 +331,12 @@ export class ExecutionLoop {
335
331
  this.finalAnswer = '';
336
332
  }
337
333
  input.rolloutBudget?.recordUsage(latestUsage);
334
+ if (!input.signal.aborted)
335
+ input.rolloutBudget?.assertAvailable();
338
336
  if (mode !== 'compact' && stop === 'max_output') {
339
337
  const needsContinuation = !sawTool || incompleteToolCall;
340
338
  if (needsContinuation) {
341
- if (maxOutputContinuationUsed ||
342
- (input.rolloutBudget?.active && input.rolloutBudget.stage === 'exhausted'))
339
+ if (maxOutputContinuationUsed)
343
340
  throw new MarAgentError('MAR_AGENT_MODEL_OUTPUT_INCOMPLETE', 'Model output reached its limit before completing.');
344
341
  maxOutputContinuationUsed = true;
345
342
  pendingOutputContinuation = {
@@ -358,6 +355,7 @@ export class ExecutionLoop {
358
355
  const aborted = toolResults.find((result) => result.failed)?.error;
359
356
  throw aborted ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.');
360
357
  }
358
+ input.rolloutBudget?.assertAvailable();
361
359
  if (input.mailbox.hasPending()) {
362
360
  await input.mailbox.append();
363
361
  this.finalAnswer = '';
@@ -19,6 +19,7 @@ export declare class RolloutBudget {
19
19
  get stage(): RolloutBudgetStage;
20
20
  recordUsage(usage: ModelUsage | undefined): number;
21
21
  discard(): void;
22
+ assertAvailable(): void;
22
23
  }
23
24
  export declare function resolveRolloutBudgetOptions(options: false | RolloutBudgetOptions | undefined): ResolvedRolloutBudgetOptions | undefined;
24
25
  export declare function createRolloutBudget(options: false | RolloutBudgetOptions | undefined): RolloutBudget | undefined;
@@ -48,6 +48,10 @@ export class RolloutBudget {
48
48
  discard() {
49
49
  this.#active = false;
50
50
  }
51
+ assertAvailable() {
52
+ if (this.stage === 'exhausted')
53
+ throw new MarAgentError('MAR_AGENT_ROLLOUT_BUDGET_EXCEEDED', 'The shared execution token budget has been exhausted.');
54
+ }
51
55
  }
52
56
  export function resolveRolloutBudgetOptions(options) {
53
57
  if (options === false)
@@ -234,7 +234,10 @@ export function createSessionOpener(services) {
234
234
  let recordedEnvironment = restored.environmentState;
235
235
  let projectedExecutionBudgetStage;
236
236
  const appendExecutionBudgetEnvironment = () => {
237
+ rolloutBudget?.assertAvailable();
237
238
  const currentStage = rolloutBudget?.active ? rolloutBudget.stage : 'inactive';
239
+ if (currentStage === 'exhausted')
240
+ return;
238
241
  if (currentStage === projectedExecutionBudgetStage)
239
242
  return;
240
243
  const previousStage = projectedExecutionBudgetStage;
@@ -420,6 +423,7 @@ export function createSessionOpener(services) {
420
423
  const catalogContext = {
421
424
  sessionId,
422
425
  executionId,
426
+ modelId: selectedModel.id,
423
427
  ...(sessionSource.type === 'subagent'
424
428
  ? internal.mcpExecutionScopeId === undefined
425
429
  ? {}
@@ -479,12 +483,8 @@ export function createSessionOpener(services) {
479
483
  ? selectedModelForTools.codeMode && description.capabilities.includes('code.execute')
480
484
  : tool.name === 'question'
481
485
  ? mode === 'plan'
482
- : tool.name === 'web_search'
483
- ? !selectedModelForTools.hostedWebSearch &&
484
- description.capabilities.includes('web.search')
485
- : tool.name === 'view_image'
486
- ? selectedModelForTools.inputCapabilities.includes('IMAGE')
487
- : tool.name !== 'web_fetch' || description.capabilities.includes('web.fetch');
486
+ : tool.name !== 'view_image' ||
487
+ selectedModelForTools.inputCapabilities.includes('IMAGE');
488
488
  };
489
489
  mcpRegistry.update(mcpCatalog, (tool) => toolEligible(tool.definition));
490
490
  rebindMcpToolSearchOutputs(messages, mcpRegistry);
@@ -806,9 +806,7 @@ export function createSessionOpener(services) {
806
806
  executionEnvironment = { ...currentEnvironment, ...currentLocalTimeContext() };
807
807
  await recordEnvironment();
808
808
  }
809
- const toolsAllowed = mode !== 'compact' &&
810
- (!rolloutBudget?.active || rolloutBudget.stage !== 'exhausted');
811
- const roundTools = mode === 'compact' ? executionTools : toolsAllowed ? executionTools : [];
809
+ const roundTools = executionTools;
812
810
  const codeModeEnabled = roundTools.some((tool) => tool.name === 'code_exec');
813
811
  const residentTools = mcpRegistry.projectDirect(baseTools());
814
812
  const requestTools = (() => {
@@ -839,6 +837,7 @@ export function createSessionOpener(services) {
839
837
  mode,
840
838
  sessionId,
841
839
  executionId,
840
+ modelId: selectedModel.id,
842
841
  signal: controller.signal,
843
842
  emit,
844
843
  skills: instructionContext?.skills ?? new Map()
@@ -891,11 +890,7 @@ export function createSessionOpener(services) {
891
890
  system: systemPrompt,
892
891
  messages: modelMessages.map((message) => projectCodeModeSearchResult(message, codeModeEnabled)),
893
892
  tools: requestTools,
894
- ...(mode === 'compact'
895
- ? { toolChoice: 'none' }
896
- : toolsAllowed
897
- ? {}
898
- : { allowTools: false }),
893
+ ...(mode === 'compact' ? { toolChoice: 'none' } : {}),
899
894
  promptCacheKey: sessionId,
900
895
  reasoningEffort,
901
896
  ...(requestReasoningEffort === undefined ? {} : { requestReasoningEffort }),
@@ -11,6 +11,8 @@ import { contextGcBaseFromRecords, contextGcBaseSequence, contextGcMinimumUsageG
11
11
  import { beginSessionStorageTransaction, isUnjournaledTransactionFile, recoverSessionStorageTransaction } from './transaction-recovery.js';
12
12
  const MAX_SEGMENT_BYTES = 16 * 1024 * 1024;
13
13
  const REVERSE_READ_CHUNK_BYTES = 64 * 1024;
14
+ const MAX_HISTORY_PAGE_FILES = 32;
15
+ const MAX_HISTORY_PAGE_OFFSETS = 128;
14
16
  const MAX_INPUT_IMAGES = 5;
15
17
  const MAX_INPUT_IMAGE_BYTES = 5 * 1024 * 1024;
16
18
  const MAX_INPUT_IMAGES_BYTES = 10 * 1024 * 1024;
@@ -161,7 +163,8 @@ export class JsonlSessionStore {
161
163
  recordWrites: new Map(),
162
164
  writerLeases: new Map(),
163
165
  segments: new Map(),
164
- executionModelReads: new Map()
166
+ executionModelReads: new Map(),
167
+ historyPagePositions: new Map()
165
168
  };
166
169
  }
167
170
  /** Merge dirty metadata with disk membership; only createdIds publish locally new entries. */
@@ -1171,7 +1174,7 @@ export class JsonlSessionStore {
1171
1174
  if (segment !== undefined && segment.firstSequence >= before)
1172
1175
  continue;
1173
1176
  const needed = limit - page.length;
1174
- const window = await readRecordPageFromTail(path, before, needed);
1177
+ const window = await readRecordPageFromTail(path, before, needed, this.#shared.historyPagePositions);
1175
1178
  page.unshift(...window.records);
1176
1179
  if (window.hasEarlier) {
1177
1180
  hasEarlier = true;
@@ -2338,9 +2341,9 @@ async function repairSegmentTail(path) {
2338
2341
  await handle.close();
2339
2342
  }
2340
2343
  }
2341
- async function readRecordPageFromTail(path, beforeSequence, limit) {
2344
+ async function readRecordPageFromTail(path, beforeSequence, limit, positions) {
2342
2345
  const newestFirst = [];
2343
- for await (const record of readRecordsFromTail(path)) {
2346
+ for await (const record of readRecordsFromTail(path, { beforeSequence, limit, positions })) {
2344
2347
  if (!(record.sequence < beforeSequence))
2345
2348
  continue;
2346
2349
  newestFirst.push(record);
@@ -2353,14 +2356,42 @@ async function readRecordPageFromTail(path, beforeSequence, limit) {
2353
2356
  };
2354
2357
  }
2355
2358
  /** Decode complete records newest-first, releasing the file as soon as a reader stops. */
2356
- async function* readRecordsFromTail(path) {
2359
+ async function* readRecordsFromTail(path, page) {
2357
2360
  const handle = await open(path, 'r');
2358
2361
  try {
2359
- const size = (await handle.stat()).size;
2362
+ const metadata = await handle.stat({ bigint: true });
2363
+ const size = Number(metadata.size);
2360
2364
  let position = size;
2365
+ let cached;
2366
+ if (page !== undefined) {
2367
+ const signature = `${metadata.dev}:${metadata.ino}:${metadata.size}:${metadata.mtimeNs}:${metadata.ctimeNs}`;
2368
+ const previous = page.positions.get(path);
2369
+ cached = previous?.signature === signature ? previous : { signature, offsets: new Map() };
2370
+ page.positions.delete(path);
2371
+ page.positions.set(path, cached);
2372
+ if (page.positions.size > MAX_HISTORY_PAGE_FILES)
2373
+ page.positions.delete(page.positions.keys().next().value);
2374
+ let nearestSequence = Number.POSITIVE_INFINITY;
2375
+ for (const [sequence, offset] of cached.offsets) {
2376
+ if (sequence >= page.beforeSequence && sequence < nearestSequence) {
2377
+ nearestSequence = sequence;
2378
+ position = offset;
2379
+ }
2380
+ }
2381
+ }
2382
+ let accepted = 0;
2383
+ const remember = (record, offset) => {
2384
+ if (page === undefined || cached === undefined || record.sequence >= page.beforeSequence)
2385
+ return;
2386
+ if (++accepted !== page.limit)
2387
+ return;
2388
+ cached.offsets.set(record.sequence, offset);
2389
+ if (cached.offsets.size > MAX_HISTORY_PAGE_OFFSETS)
2390
+ cached.offsets.delete(cached.offsets.keys().next().value);
2391
+ };
2361
2392
  let pendingLineParts = [];
2362
2393
  let invalidFinalLineMayBeIgnored = false;
2363
- let inspectedFileEnd = false;
2394
+ let inspectedFileEnd = position !== size;
2364
2395
  const decode = (line, allowInvalidFinalLine) => {
2365
2396
  if (line.length === 0)
2366
2397
  return undefined;
@@ -2402,8 +2433,10 @@ async function* readRecordsFromTail(path) {
2402
2433
  invalidFinalLineMayBeIgnored = false;
2403
2434
  pendingLineParts = [];
2404
2435
  lineEnd = index;
2405
- if (record !== undefined)
2436
+ if (record !== undefined) {
2437
+ remember(record, start + index + 1);
2406
2438
  yield record;
2439
+ }
2407
2440
  }
2408
2441
  if (lineEnd > 0)
2409
2442
  pendingLineParts.unshift(Buffer.from(value.subarray(0, lineEnd)));
@@ -2411,8 +2444,10 @@ async function* readRecordsFromTail(path) {
2411
2444
  }
2412
2445
  if (pendingLineParts.length > 0) {
2413
2446
  const record = decode(Buffer.concat(pendingLineParts), invalidFinalLineMayBeIgnored);
2414
- if (record !== undefined)
2447
+ if (record !== undefined) {
2448
+ remember(record, 0);
2415
2449
  yield record;
2450
+ }
2416
2451
  }
2417
2452
  }
2418
2453
  finally {
@@ -25,6 +25,8 @@ export interface ToolExecutionContext {
25
25
  signal: AbortSignal;
26
26
  sessionId: string;
27
27
  executionId: string;
28
+ /** Current model identity for neutral Host-owned tool policies. */
29
+ modelId?: string;
28
30
  recentImages?: readonly {
29
31
  readonly mimeType: string;
30
32
  readonly data: Buffer;
@@ -39,7 +41,7 @@ export interface ToolExecutionContext {
39
41
  invoke(call: ToolCall, signal: AbortSignal): Promise<ToolExecutionResult>;
40
42
  };
41
43
  }
42
- export interface McpToolCatalogContext extends Pick<ToolExecutionContext, 'sessionId' | 'executionId'> {
44
+ export interface McpToolCatalogContext extends Pick<ToolExecutionContext, 'sessionId' | 'executionId' | 'modelId'> {
43
45
  executionScopeId?: string;
44
46
  claimExecutionScope?: boolean;
45
47
  }
@@ -4,13 +4,7 @@ import type { ToolCall, ToolExecutionContext, ToolExecutionResult } from '../con
4
4
  import { type WorkspacePathMount } from '../shared/path-resolver.js';
5
5
  export declare class LocalToolRegistry {
6
6
  #private;
7
- constructor(workspace: string, environment?: NodeJS.ProcessEnv, shell?: string, web?: {
8
- webSearch?: (input: {
9
- query: string;
10
- maxResults: number;
11
- }, signal: AbortSignal) => Promise<ToolExecutionResult>;
12
- webFetch?: boolean;
13
- allowUntrustedTls?: boolean;
7
+ constructor(workspace: string, environment?: NodeJS.ProcessEnv, shell?: string, options?: {
14
8
  processExecutor?: LocalProcessExecutor;
15
9
  codeModeEngineFactory?: CodeModeEngineFactory;
16
10
  restrictPaths?: boolean;
@@ -3,25 +3,25 @@ import { ApplyPatchTool, applyPatchToolInput } from './apply-patch.js';
3
3
  import { CodeModeTool } from '../code-mode/index.js';
4
4
  import { ExecTool, execToolInput } from './exec.js';
5
5
  import { ReadTool, formatBatchReadOutput } from './read.js';
6
- import { executeWebFetch } from './web-fetch.js';
7
- import { executeWebSearch } from './web-search.js';
8
6
  import { WorkspacePathResolver } from '../shared/path-resolver.js';
9
7
  import { ViewImageTool } from './view-image.js';
10
8
  export class LocalToolRegistry {
11
9
  #exec;
12
10
  #code;
13
11
  #handlers;
14
- constructor(workspace, environment = process.env, shell, web = {}) {
12
+ constructor(workspace, environment = process.env, shell, options = {}) {
15
13
  const paths = new WorkspacePathResolver(workspace, {
16
- ...(web.restrictPaths === undefined ? {} : { restrictPaths: web.restrictPaths }),
17
- ...(web.workspaceWritable === undefined ? {} : { rootWritable: web.workspaceWritable }),
18
- ...(web.pathMounts === undefined ? {} : { mounts: web.pathMounts })
14
+ ...(options.restrictPaths === undefined ? {} : { restrictPaths: options.restrictPaths }),
15
+ ...(options.workspaceWritable === undefined
16
+ ? {}
17
+ : { rootWritable: options.workspaceWritable }),
18
+ ...(options.pathMounts === undefined ? {} : { mounts: options.pathMounts })
19
19
  });
20
20
  const read = new ReadTool(paths);
21
21
  const viewImage = new ViewImageTool(paths);
22
22
  const patch = new ApplyPatchTool(paths);
23
- this.#exec = new ExecTool(paths, undefined, undefined, environment, shell, web.processExecutor);
24
- this.#code = new CodeModeTool(paths, environment, web.processExecutor, web.codeModeEngineFactory);
23
+ this.#exec = new ExecTool(paths, undefined, undefined, environment, shell, options.processExecutor);
24
+ this.#code = new CodeModeTool(paths, environment, options.processExecutor, options.codeModeEngineFactory);
25
25
  this.#handlers = new Map([
26
26
  ['code_exec', (arguments_, context) => this.#code.start(arguments_, context)],
27
27
  ['code_exec_wait', (arguments_, context) => this.#code.wait(arguments_, context)],
@@ -57,27 +57,7 @@ export class LocalToolRegistry {
57
57
  });
58
58
  return { content: JSON.stringify(result), data: result, truncated: result.truncated };
59
59
  }
60
- ]),
61
- ...(web.webSearch
62
- ? [
63
- [
64
- 'web_search',
65
- async (arguments_, context) => {
66
- return executeWebSearch(arguments_, context.signal, (input, signal) => web.webSearch(input, signal));
67
- }
68
- ]
69
- ]
70
- : []),
71
- ...(web.webFetch === false
72
- ? []
73
- : [
74
- [
75
- 'web_fetch',
76
- async (arguments_, context) => {
77
- return executeWebFetch(arguments_, context.signal, web.allowUntrustedTls === true);
78
- }
79
- ]
80
- ])
60
+ ])
81
61
  ]);
82
62
  }
83
63
  execute(call, context) {
@@ -12,8 +12,6 @@ import { executeTodoRead, todoReadToolDefinition } from './agent/todo-read.js';
12
12
  import { executeTodoWrite, todoWriteToolDefinition } from './agent/todo-write.js';
13
13
  import { executeToolSearch, toolSearchToolDefinition } from './mcp/tool-search.js';
14
14
  import { viewImageToolDefinition } from './local/view-image.js';
15
- import { webFetchToolDefinition } from './local/web-fetch.js';
16
- import { webSearchToolDefinition } from './local/web-search.js';
17
15
  export const BUILTIN_TOOL_DEFINITIONS = [
18
16
  skillToolDefinition,
19
17
  toolSearchToolDefinition,
@@ -30,9 +28,7 @@ export const BUILTIN_TOOL_DEFINITIONS = [
30
28
  agentStartToolDefinition,
31
29
  agentWaitToolDefinition,
32
30
  agentMessageToolDefinition,
33
- agentCancelToolDefinition,
34
- webSearchToolDefinition,
35
- webFetchToolDefinition
31
+ agentCancelToolDefinition
36
32
  ];
37
33
  export const AGENT_TOOL_HANDLERS = new Map([
38
34
  ['skill', executeSkill],
@@ -44,6 +44,7 @@ export interface ToolRuntimeContext {
44
44
  mode: ExecutionMode;
45
45
  sessionId: string;
46
46
  executionId: string;
47
+ modelId?: string;
47
48
  signal: AbortSignal;
48
49
  codeMode?: import('./contracts.js').ToolExecutionContext['codeMode'];
49
50
  skills: ReadonlyMap<string, AgentSkill>;
@@ -20,6 +20,7 @@ export async function executeRegisteredTool(name, arguments_, callId, context) {
20
20
  signal: context.signal,
21
21
  sessionId: context.sessionId,
22
22
  executionId: context.executionId,
23
+ ...(context.modelId === undefined ? {} : { modelId: context.modelId }),
23
24
  ...(context.recentImages === undefined ? {} : { recentImages: context.recentImages }),
24
25
  ...(context.codeMode === undefined ? {} : { codeMode: context.codeMode })
25
26
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.151",
3
+ "version": "0.9.153",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,4 +0,0 @@
1
- import type { ToolExecutionResult } from '../contracts.js';
2
- import type { ClientToolDefinition } from '../../model/contracts.js';
3
- export declare const webFetchToolDefinition: ClientToolDefinition;
4
- export declare function executeWebFetch(input: unknown, signal: AbortSignal, allowUntrustedTls?: boolean): Promise<ToolExecutionResult>;
@@ -1,85 +0,0 @@
1
- import { request as httpRequest } from 'node:http';
2
- import { request as httpsRequest } from 'node:https';
3
- import { MarAgentError } from '../../error.js';
4
- export const webFetchToolDefinition = {
5
- name: 'web_fetch',
6
- allowedCallers: ['direct', 'programmatic'],
7
- parallelSafety: 'safe',
8
- description: 'Fetch a specific HTTP or HTTPS URL, including explicitly permitted intranet URLs.',
9
- inputSchema: {
10
- type: 'object',
11
- properties: { url: { type: 'string' } },
12
- required: ['url'],
13
- additionalProperties: false
14
- }
15
- };
16
- export async function executeWebFetch(input, signal, allowUntrustedTls = false) {
17
- const value = input;
18
- if (typeof value?.url !== 'string')
19
- throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'URL is required.');
20
- let url;
21
- try {
22
- url = new URL(value.url);
23
- }
24
- catch {
25
- throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'URL is invalid.');
26
- }
27
- if (url.protocol !== 'http:' && url.protocol !== 'https:')
28
- throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Only HTTP and HTTPS URLs are supported.');
29
- return fetchWebUrl(url, signal, allowUntrustedTls);
30
- }
31
- async function fetchWebUrl(url, signal, allowUntrustedTls, redirects = 0) {
32
- if (redirects > 5)
33
- throw new MarAgentError('MAR_AGENT_WEB_FETCH_FAILED', 'Too many redirects.');
34
- return new Promise((resolve, reject) => {
35
- const request = (url.protocol === 'https:' ? httpsRequest : httpRequest)(url, {
36
- signal,
37
- headers: {
38
- accept: 'text/html,application/json,text/plain;q=0.9,*/*;q=0.1',
39
- 'accept-encoding': 'identity',
40
- 'user-agent': 'MyAgentRoam/0.9'
41
- },
42
- ...(url.protocol === 'https:' ? { rejectUnauthorized: !allowUntrustedTls } : {})
43
- }, (response) => {
44
- const status = response.statusCode ?? 0;
45
- if (status >= 300 && status < 400 && response.headers.location) {
46
- response.resume();
47
- void fetchWebUrl(new URL(response.headers.location, url), signal, allowUntrustedTls, redirects + 1).then(resolve, reject);
48
- return;
49
- }
50
- if (status < 200 || status >= 300) {
51
- response.resume();
52
- reject(new MarAgentError('MAR_AGENT_WEB_FETCH_FAILED', `HTTP ${status}.`));
53
- return;
54
- }
55
- const contentType = String(response.headers['content-type'] ?? 'text/plain').toLowerCase();
56
- if (!/^(text\/|application\/(json|xml|xhtml\+xml))/u.test(contentType)) {
57
- response.resume();
58
- reject(new MarAgentError('MAR_AGENT_WEB_FETCH_UNSUPPORTED_CONTENT', 'Response is not readable text.'));
59
- return;
60
- }
61
- const chunks = [];
62
- let bytes = 0;
63
- let truncated = false;
64
- response.on('data', (chunk) => {
65
- if (bytes >= 1_000_000) {
66
- truncated = true;
67
- return;
68
- }
69
- const remaining = 1_000_000 - bytes;
70
- chunks.push(chunk.subarray(0, remaining));
71
- bytes += Math.min(chunk.length, remaining);
72
- if (chunk.length > remaining)
73
- truncated = true;
74
- });
75
- response.on('end', () => resolve({
76
- content: Buffer.concat(chunks).toString('utf8'),
77
- data: { url: url.href, status },
78
- truncated
79
- }));
80
- response.on('error', reject);
81
- });
82
- request.on('error', reject);
83
- request.end();
84
- });
85
- }
@@ -1,7 +0,0 @@
1
- import type { ClientToolDefinition } from '../../model/contracts.js';
2
- import type { ToolExecutionResult } from '../contracts.js';
3
- export declare const webSearchToolDefinition: ClientToolDefinition;
4
- export declare function executeWebSearch(input: unknown, signal: AbortSignal, search: (input: {
5
- query: string;
6
- maxResults: number;
7
- }, signal: AbortSignal) => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>;
@@ -1,34 +0,0 @@
1
- import { MarAgentError } from '../../error.js';
2
- export const webSearchToolDefinition = {
3
- name: 'web_search',
4
- allowedCallers: ['direct', 'programmatic'],
5
- parallelSafety: 'safe',
6
- description: 'Search the web for current information. Use web_fetch to open a specific result.',
7
- inputSchema: {
8
- type: 'object',
9
- properties: {
10
- query: { type: 'string', description: 'Search query.' },
11
- maxResults: { type: 'integer', minimum: 1, maximum: 10, default: 8 }
12
- },
13
- required: ['query'],
14
- additionalProperties: false
15
- }
16
- };
17
- export async function executeWebSearch(input, signal, search) {
18
- const value = input;
19
- if (typeof value?.query !== 'string' || value.query.trim().length === 0)
20
- throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Search query is required.');
21
- try {
22
- return await search({
23
- query: value.query.trim(),
24
- maxResults: typeof value.maxResults === 'number'
25
- ? Math.min(10, Math.max(1, Math.trunc(value.maxResults)))
26
- : 8
27
- }, signal);
28
- }
29
- catch (error) {
30
- if (error instanceof MarAgentError)
31
- throw error;
32
- throw new MarAgentError('MAR_AGENT_WEB_SEARCH_FAILED', 'Web search failed.', { cause: error });
33
- }
34
- }