@myagentroam/agent 0.10.8 → 0.10.9

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,4 +1,5 @@
1
1
  import { observeModelRequestUsage } from './usage-observer.js';
2
+ import { jsonToolInput } from './tool-input.js';
2
3
  import { observationModelParts } from '../tools/code-mode/observations.js';
3
4
  import { stableToolSchema } from '../tools/definition-projection.js';
4
5
  import { randomUUID } from 'node:crypto';
@@ -144,7 +145,7 @@ export class AnthropicMessagesAdapter {
144
145
  let terminal;
145
146
  let latestInputUsage;
146
147
  let thinkingBytes = 0;
147
- for await (const raw of parseAnthropicSse(response, signal)) {
148
+ for await (const raw of parseAnthropicSse(response, signal, apiKey.reveal())) {
148
149
  const event = record(raw);
149
150
  const type = text(event.type) ?? 'unknown';
150
151
  const index = number(event.index) ?? -1;
@@ -424,9 +425,9 @@ export class AnthropicMessagesAdapter {
424
425
  });
425
426
  }
426
427
  }
427
- async function* parseAnthropicSse(response, signal) {
428
+ async function* parseAnthropicSse(response, signal, apiKey) {
428
429
  try {
429
- yield* parseSse(response, signal);
430
+ yield* parseSse(response, signal, undefined, undefined, [apiKey]);
430
431
  }
431
432
  catch (cause) {
432
433
  if (response.ok &&
@@ -551,7 +552,7 @@ function anthropicMessages(messages, images, tools = [], modelId, automaticCache
551
552
  type: 'tool_use',
552
553
  id: message.callId,
553
554
  name: message.name,
554
- input: anthropicToolInput(message, toolDefinitions.get(message.name ?? ''))
555
+ input: jsonToolInput(message, toolDefinitions.get(message.name ?? ''))
555
556
  };
556
557
  const previous = result.at(-1);
557
558
  if (previous?.role === 'assistant' && Array.isArray(previous.content))
@@ -673,23 +674,6 @@ function uniqueModelImages(...groups) {
673
674
  return [image];
674
675
  }));
675
676
  }
676
- function anthropicToolInput(message, tool) {
677
- if (isObject(message.arguments))
678
- return message.arguments;
679
- if (message.inputMode !== 'freeform')
680
- return {};
681
- const property = freeformInputProperty(tool?.inputSchema);
682
- return { [property ?? 'input']: String(message.arguments ?? '') };
683
- }
684
- function freeformInputProperty(schema) {
685
- if (schema?.type !== 'object' || !isObject(schema.properties) || !Array.isArray(schema.required))
686
- return undefined;
687
- const required = schema.required.filter((value) => typeof value === 'string');
688
- if (required.length !== 1)
689
- return undefined;
690
- const property = schema.properties[required[0]];
691
- return isObject(property) && property.type === 'string' ? required[0] : undefined;
692
- }
693
677
  function isObject(value) {
694
678
  return value !== null && typeof value === 'object' && !Array.isArray(value);
695
679
  }
@@ -32,6 +32,13 @@ export declare function serializeModel(model: CliModelConfiguration): {
32
32
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
33
33
  hostedWebSearch: boolean;
34
34
  responsesToolNamespace?: string | undefined;
35
+ responsesOptionalParameters?: {
36
+ reasoningEffort: boolean;
37
+ reasoningSummary: boolean;
38
+ textVerbosity: boolean;
39
+ promptCacheKey: boolean;
40
+ nativeTools?: boolean | undefined;
41
+ } | undefined;
35
42
  enabled: boolean;
36
43
  } | {
37
44
  credential: {
@@ -58,5 +65,12 @@ export declare function serializeModel(model: CliModelConfiguration): {
58
65
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
59
66
  hostedWebSearch: boolean;
60
67
  responsesToolNamespace?: string | undefined;
68
+ responsesOptionalParameters?: {
69
+ reasoningEffort: boolean;
70
+ reasoningSummary: boolean;
71
+ textVerbosity: boolean;
72
+ promptCacheKey: boolean;
73
+ nativeTools?: boolean | undefined;
74
+ } | undefined;
61
75
  enabled: boolean;
62
76
  };
@@ -90,6 +90,13 @@ declare const rawModelSchema: z.ZodObject<{
90
90
  }, z.core.$strict>], "transport">>;
91
91
  responsesToolNamespace: z.ZodOptional<z.ZodString>;
92
92
  responsesPreviousResponseId: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
93
+ responsesOptionalParameters: z.ZodOptional<z.ZodObject<{
94
+ reasoningEffort: z.ZodBoolean;
95
+ reasoningSummary: z.ZodBoolean;
96
+ textVerbosity: z.ZodBoolean;
97
+ promptCacheKey: z.ZodBoolean;
98
+ nativeTools: z.ZodOptional<z.ZodBoolean>;
99
+ }, z.core.$strict>>;
93
100
  enabled: z.ZodBoolean;
94
101
  }, z.core.$strict>;
95
102
  export interface MarAgentModelConfiguration extends Omit<z.infer<typeof rawModelSchema>, 'apiKey' | 'responsesPreviousResponseId' | 'responsesTransport'> {
@@ -58,6 +58,16 @@ const rawModelSchema = z
58
58
  .regex(/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/u)
59
59
  .optional(),
60
60
  responsesPreviousResponseId: z.boolean().optional().default(false),
61
+ responsesOptionalParameters: z
62
+ .object({
63
+ reasoningEffort: z.boolean(),
64
+ reasoningSummary: z.boolean(),
65
+ textVerbosity: z.boolean(),
66
+ promptCacheKey: z.boolean(),
67
+ nativeTools: z.boolean().optional()
68
+ })
69
+ .strict()
70
+ .optional(),
61
71
  enabled: z.boolean()
62
72
  })
63
73
  .strict()
@@ -68,5 +68,5 @@ declare class OpenAiResponsesTurnSession implements ModelTurnSession {
68
68
  private connectWebSocket;
69
69
  }
70
70
  export declare function isOpenAiResponsesRecoverableStreamError(error: unknown): error is MarAgentError;
71
- export declare function openAiInput(messages: ModelRequest['messages'], images: ModelRequest['images'] | undefined, modelId: string, requireEncryptedReasoning: boolean, configurationUpdates?: boolean): Record<string, unknown>[];
71
+ export declare function openAiInput(messages: ModelRequest['messages'], images: ModelRequest['images'] | undefined, modelId: string, requireEncryptedReasoning: boolean, configurationUpdates?: boolean, nativeTools?: boolean, tools?: ModelRequest['tools']): Record<string, unknown>[];
72
72
  export {};
@@ -8,8 +8,9 @@ import { marAgentReasoningEffortSchema, resolveModelDefaultReasoningEffort, uses
8
8
  import WebSocket from 'ws';
9
9
  import { isRetryableUpstreamOverloadCode, number, parseSse, record, text, observeModelStreamEvent, modelStreamEventObservedAt } from './sse.js';
10
10
  import { connectSocks5Target, fetchModelResponse, isRetryableModelHttpStatus, isRetryableModelTransportError, modelRetryMaxAttempts, isRetryableModelRequestError, modelRetryDelay, waitForModelRetry } from './http.js';
11
- import { acquireModelCredential, appendModelCredentialHeaders } from './runtime-credential.js';
11
+ import { acquireModelCredential, appendModelCredentialHeaders, modelCredentialSensitiveValues } from './runtime-credential.js';
12
12
  import { resolveModelTextBlocks } from './text-blocks.js';
13
+ import { jsonToolInput } from './tool-input.js';
13
14
  import { reportModelAttempt } from './attempt-diagnostics.js';
14
15
  const WEBSOCKET_IDLE_TIMEOUT_MS = 300_000;
15
16
  const WEBSOCKET_SEND_FRAME_BYTES = 1024 * 1024;
@@ -253,11 +254,13 @@ export class OpenAiResponsesAdapter {
253
254
  }
254
255
  async *startAttempt(request, signal, body, continuation, credentialReason, turn) {
255
256
  let latestCredentialRevision;
257
+ let diagnosticSecrets = [];
256
258
  const fetchResponse = async (requestBody) => {
257
259
  const serializedBody = JSON.stringify(requestBody);
258
260
  return await fetchModelResponse(responsesEndpoint(this.configuration.baseUrl), async () => {
259
261
  const credential = await acquireResponsesCredential(this.configuration, credentialReason, signal);
260
262
  latestCredentialRevision = credential.revision;
263
+ diagnosticSecrets = modelCredentialSensitiveValues(credential);
261
264
  return {
262
265
  request: {
263
266
  method: 'POST',
@@ -291,7 +294,7 @@ export class OpenAiResponsesAdapter {
291
294
  else {
292
295
  response = await fetchResponse(body);
293
296
  turn.captureTurnState(response.headers.get(X_CODEX_TURN_STATE));
294
- rawEvents = parseOpenAiResponsesSse(response, signal, continuation);
297
+ rawEvents = parseOpenAiResponsesSse(response, signal, continuation, diagnosticSecrets);
295
298
  }
296
299
  }
297
300
  catch (error) {
@@ -897,14 +900,15 @@ function responsesRequestBody(configuration, request, messages) {
897
900
  throw new MarAgentError('MAR_AGENT_MODEL_CAPABILITY_UNAVAILABLE', 'This model protocol does not support explicit text-block cache markers.');
898
901
  const tools = request.allowTools === false
899
902
  ? []
900
- : request.tools.map((tool) => tool.nativeType === 'tool_search'
903
+ : request.tools.map((tool) => tool.nativeType === 'tool_search' &&
904
+ configuration.responsesOptionalParameters?.nativeTools !== false
901
905
  ? {
902
906
  type: 'tool_search',
903
907
  execution: 'client',
904
908
  description: tool.description,
905
909
  parameters: stableToolSchema(strictFunctionSchema(stableToolSchema(tool.inputSchema)))
906
910
  }
907
- : responsesFunctionTool(tool));
911
+ : responsesFunctionTool(tool, configuration.responsesOptionalParameters?.nativeTools !== false));
908
912
  if (configuration.responsesToolNamespace && tools.length > 0) {
909
913
  const native = tools.filter((tool) => record(tool).type === 'tool_search');
910
914
  const nested = tools.filter((tool) => record(tool).type !== 'tool_search');
@@ -920,7 +924,7 @@ function responsesRequestBody(configuration, request, messages) {
920
924
  if (request.allowTools !== false && configuration.hostedWebSearch)
921
925
  tools.push({ type: 'web_search' });
922
926
  const toolChoice = request.toolChoice ?? 'auto';
923
- const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration));
927
+ const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration), configuration.responsesOptionalParameters?.nativeTools !== false, request.tools);
924
928
  if (request.systemBlocks !== undefined && (request.systemBlocks.length || request.system.length))
925
929
  input.unshift({
926
930
  role: 'developer',
@@ -943,25 +947,40 @@ function responsesRequestBody(configuration, request, messages) {
943
947
  : {}),
944
948
  instructions: request.systemBlocks === undefined ? request.system : '',
945
949
  tools,
946
- tool_choice: toolChoice,
950
+ ...(tools.length > 0 ? { tool_choice: toolChoice, parallel_tool_calls: true } : {}),
947
951
  input,
948
952
  stream: true,
949
- parallel_tool_calls: true,
950
953
  store: false,
951
- text: { verbosity: 'low' },
954
+ ...(configuration.responsesOptionalParameters?.textVerbosity === false
955
+ ? {}
956
+ : { text: { verbosity: 'low' } }),
952
957
  include: [
953
958
  'reasoning.encrypted_content',
954
959
  ...(configuration.hostedWebSearch && request.allowTools !== false && toolChoice !== 'none'
955
960
  ? ['web_search_call.action.sources']
956
961
  : [])
957
962
  ],
958
- ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
959
- reasoning: {
960
- effort: request.requestReasoningEffort?.trim() ||
961
- request.reasoningEffort?.trim() ||
962
- resolveModelDefaultReasoningEffort(configuration),
963
- summary: 'auto'
964
- }
963
+ ...(request.promptCacheKey &&
964
+ configuration.responsesOptionalParameters?.promptCacheKey !== false
965
+ ? { prompt_cache_key: request.promptCacheKey }
966
+ : {}),
967
+ ...(configuration.responsesOptionalParameters?.reasoningEffort === false &&
968
+ configuration.responsesOptionalParameters?.reasoningSummary === false
969
+ ? {}
970
+ : {
971
+ reasoning: {
972
+ ...(configuration.responsesOptionalParameters?.reasoningEffort === false
973
+ ? {}
974
+ : {
975
+ effort: request.requestReasoningEffort?.trim() ||
976
+ request.reasoningEffort?.trim() ||
977
+ resolveModelDefaultReasoningEffort(configuration)
978
+ }),
979
+ ...(configuration.responsesOptionalParameters?.reasoningSummary === false
980
+ ? {}
981
+ : { summary: 'auto' })
982
+ }
983
+ })
965
984
  };
966
985
  }
967
986
  function incrementalResponsesInput(state, current) {
@@ -1031,9 +1050,9 @@ function turnStateFromEvent(event) {
1031
1050
  }
1032
1051
  return undefined;
1033
1052
  }
1034
- async function* parseOpenAiResponsesSse(response, signal, continuation) {
1053
+ async function* parseOpenAiResponsesSse(response, signal, continuation, sensitiveValues) {
1035
1054
  try {
1036
- yield* parseSse(response, signal);
1055
+ yield* parseSse(response, signal, undefined, undefined, sensitiveValues);
1037
1056
  }
1038
1057
  catch (cause) {
1039
1058
  if (response.ok && !signal.aborted && isRetryableOpenAiStreamReadError(cause))
@@ -1771,11 +1790,13 @@ function responseCitations(response) {
1771
1790
  function responseEventId(event) {
1772
1791
  return text(record(event.response).id);
1773
1792
  }
1774
- export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true) {
1793
+ export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true, nativeTools = true, tools = []) {
1775
1794
  const compatibleMessages = messages.filter((message) => message.role !== 'provider' ||
1776
1795
  (message.provider === 'OPENAI_RESPONSES' &&
1777
1796
  (message.providerModelId === undefined || message.providerModelId === modelId) &&
1778
1797
  (configurationUpdates || message.item?.type !== 'configuration_update') &&
1798
+ (nativeTools ||
1799
+ !['custom_tool_call', 'tool_search_call', 'tool_search_output'].includes(String(message.item?.type))) &&
1779
1800
  (!requireEncryptedReasoning ||
1780
1801
  message.item?.type !== 'reasoning' ||
1781
1802
  typeof message.item.encrypted_content === 'string')));
@@ -1810,7 +1831,10 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
1810
1831
  }
1811
1832
  }
1812
1833
  for (const message of compatibleMessages)
1813
- if (message.role === 'tool_call' && message.inputMode === 'freeform' && message.callId)
1834
+ if (nativeTools &&
1835
+ message.role === 'tool_call' &&
1836
+ message.inputMode === 'freeform' &&
1837
+ message.callId)
1814
1838
  customCallIds.add(message.callId);
1815
1839
  const orderedMessages = [];
1816
1840
  for (const message of compatibleMessages) {
@@ -1828,9 +1852,9 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
1828
1852
  return orderedMessages
1829
1853
  .filter((message) => !(message.role === 'tool_call' && message.callId && providerCallIds.has(message.callId)) &&
1830
1854
  !(message.role === 'assistant' && providerMessageTexts.has(message.content)))
1831
- .map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds));
1855
+ .map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds, nativeTools, tools));
1832
1856
  }
1833
- function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set()) {
1857
+ function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set(), nativeTools = true, tools = []) {
1834
1858
  const userImages = message.role === 'user' ? uniqueModelImages(message.images, images) : [];
1835
1859
  if (message.role === 'provider') {
1836
1860
  if (message.provider !== 'OPENAI_RESPONSES' || !message.item)
@@ -1845,14 +1869,14 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1845
1869
  throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Provider context item type is unsupported.');
1846
1870
  return item;
1847
1871
  }
1848
- if (message.role === 'tool_call' && message.inputMode === 'freeform')
1872
+ if (nativeTools && message.role === 'tool_call' && message.inputMode === 'freeform')
1849
1873
  return {
1850
1874
  type: 'custom_tool_call',
1851
1875
  call_id: message.callId,
1852
1876
  name: message.name,
1853
1877
  input: String(message.arguments ?? '')
1854
1878
  };
1855
- if (message.role === 'tool_call' && message.name === 'tool_search')
1879
+ if (nativeTools && message.role === 'tool_call' && message.name === 'tool_search')
1856
1880
  return {
1857
1881
  type: 'tool_search_call',
1858
1882
  call_id: message.callId,
@@ -1865,9 +1889,12 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1865
1889
  type: 'function_call',
1866
1890
  call_id: message.callId,
1867
1891
  name: message.name,
1868
- arguments: JSON.stringify(message.arguments ?? {})
1892
+ arguments: JSON.stringify(message.inputMode === 'freeform'
1893
+ ? jsonToolInput(message, tools.find((tool) => tool.name === message.name))
1894
+ : (message.arguments ?? {}))
1869
1895
  };
1870
1896
  if (message.role === 'tool' &&
1897
+ nativeTools &&
1871
1898
  message.toolSearchTools &&
1872
1899
  !functionCallIds.has(message.callId ?? ''))
1873
1900
  return {
@@ -1875,7 +1902,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1875
1902
  call_id: message.callId,
1876
1903
  status: 'completed',
1877
1904
  execution: 'client',
1878
- tools: message.toolSearchTools.map(responsesFunctionTool)
1905
+ tools: message.toolSearchTools.map((tool) => responsesFunctionTool(tool))
1879
1906
  };
1880
1907
  if (message.role === 'tool')
1881
1908
  return {
@@ -1934,9 +1961,9 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1934
1961
  : message.content
1935
1962
  };
1936
1963
  }
1937
- function responsesFunctionTool(tool) {
1964
+ function responsesFunctionTool(tool, nativeTools = true) {
1938
1965
  const strict = !hasOpenFunctionObject(tool.inputSchema);
1939
- return tool.inputMode === 'freeform'
1966
+ return tool.inputMode === 'freeform' && nativeTools
1940
1967
  ? { type: 'custom', name: tool.name, description: tool.description }
1941
1968
  : {
1942
1969
  type: 'function',
@@ -4,3 +4,5 @@ export declare function acquireModelCredential(configuration: Pick<MarAgentModel
4
4
  readonly credentialProvider?: ModelCredentialProvider;
5
5
  }, reason: ModelCredentialAcquireReason, missingCredentialMessage: string, signal?: AbortSignal): Promise<ModelCredential>;
6
6
  export declare function appendModelCredentialHeaders(headers: Headers, credential: ModelCredential, reservedHeaders: ReadonlySet<string>): Headers;
7
+ /** Values known to be credentials, used only when producing public HTTP error diagnostics. */
8
+ export declare function modelCredentialSensitiveValues(credential: ModelCredential): readonly string[];
@@ -55,3 +55,18 @@ export function appendModelCredentialHeaders(headers, credential, reservedHeader
55
55
  }
56
56
  return headers;
57
57
  }
58
+ /** Values known to be credentials, used only when producing public HTTP error diagnostics. */
59
+ export function modelCredentialSensitiveValues(credential) {
60
+ const values = [
61
+ credential.bearer.reveal(),
62
+ ...Object.values(credential.headers)
63
+ .filter((value) => value instanceof SecretValue)
64
+ .map((value) => value.reveal())
65
+ ];
66
+ if (credential.socks5Url !== undefined) {
67
+ const proxy = credential.socks5Url.reveal();
68
+ const url = new URL(proxy);
69
+ values.push(proxy, decodeURIComponent(url.username), decodeURIComponent(url.password));
70
+ }
71
+ return values;
72
+ }
@@ -1,6 +1,6 @@
1
1
  export declare function observeModelStreamEvent<const T>(value: T, observedAt: number | undefined): T;
2
2
  export declare function modelStreamEventObservedAt(value: unknown): number | undefined;
3
- export declare function parseSse(response: Response, signal: AbortSignal, maxBytes?: number, idleTimeoutMs?: number): AsyncIterable<unknown>;
3
+ export declare function parseSse(response: Response, signal: AbortSignal, maxBytes?: number, idleTimeoutMs?: number, sensitiveValues?: readonly string[]): AsyncIterable<unknown>;
4
4
  export declare function isRetryableUpstreamOverloadCode(code: string | undefined): boolean;
5
5
  export declare function record(value: unknown): Record<string, unknown>;
6
6
  export declare function text(value: unknown): string | undefined;
@@ -10,9 +10,14 @@ export function observeModelStreamEvent(value, observedAt) {
10
10
  export function modelStreamEventObservedAt(value) {
11
11
  return typeof value === 'object' && value !== null ? streamEventObservedAt.get(value) : undefined;
12
12
  }
13
- export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, idleTimeoutMs = DEFAULT_MODEL_STREAM_IDLE_TIMEOUT_MS) {
13
+ export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, idleTimeoutMs = DEFAULT_MODEL_STREAM_IDLE_TIMEOUT_MS, sensitiveValues = []) {
14
14
  if (!response.ok) {
15
- const body = await readResponseText(response, signal, maxBytes, idleTimeoutMs);
15
+ const redact = responseRedactor(sensitiveValues);
16
+ const body = redact(await readResponseText(response, signal, maxBytes, idleTimeoutMs));
17
+ const header = (name, limit) => {
18
+ const value = response.headers.get(name);
19
+ return value === null ? null : redact(value).slice(0, limit);
20
+ };
16
21
  const upstream = upstreamError(body);
17
22
  const code = upstream?.code ??
18
23
  (response.status === 401 || response.status === 403
@@ -25,8 +30,11 @@ export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, i
25
30
  throw new MarAgentError(code, upstream?.message ?? `Model endpoint returned HTTP ${response.status}.`, {
26
31
  details: {
27
32
  status: response.status,
28
- body: body.slice(0, 512),
29
- retryAfter: response.headers.get('retry-after')
33
+ body: body.slice(0, 4096),
34
+ bodyTruncated: body.length > 4096,
35
+ contentType: header('content-type', 256),
36
+ requestId: header('x-request-id', 256) ?? header('request-id', 256),
37
+ retryAfter: header('retry-after', 128)
30
38
  },
31
39
  retryable: isRetryableModelHttpStatus(response.status)
32
40
  });
@@ -208,6 +216,12 @@ async function readStreamChunk(reader, signal, idleTimeoutMs) {
208
216
  });
209
217
  });
210
218
  }
219
+ function responseRedactor(sensitiveValues) {
220
+ const secrets = [
221
+ ...new Set(sensitiveValues.flatMap((value) => value ? [value, JSON.stringify(value).slice(1, -1), encodeURIComponent(value)] : []))
222
+ ].sort((left, right) => right.length - left.length);
223
+ return (value) => secrets.reduce((text, secret) => text.replaceAll(secret, '[REDACTED]'), value);
224
+ }
211
225
  function upstreamError(body) {
212
226
  let parsed;
213
227
  try {
@@ -217,6 +231,10 @@ function upstreamError(body) {
217
231
  return undefined;
218
232
  }
219
233
  const root = record(parsed);
234
+ if (typeof root.error === 'string') {
235
+ const message = bounded(root.error, 1_000);
236
+ return message === undefined ? undefined : { message };
237
+ }
220
238
  const error = record(root.error);
221
239
  // K3-compatible gateways can put the error discriminator in `type` and omit `code`.
222
240
  const code = bounded(text(error.code) ?? text(error.type), 80);
@@ -0,0 +1,3 @@
1
+ import type { ModelRequest } from './contracts.js';
2
+ /** Preserve a freeform call when replaying it through a JSON-only tool protocol. */
3
+ export declare function jsonToolInput(message: ModelRequest['messages'][number], tool: ModelRequest['tools'][number] | undefined): Record<string, unknown>;
@@ -0,0 +1,18 @@
1
+ /** Preserve a freeform call when replaying it through a JSON-only tool protocol. */
2
+ export function jsonToolInput(message, tool) {
3
+ if (isObject(message.arguments))
4
+ return message.arguments;
5
+ if (message.inputMode !== 'freeform')
6
+ return {};
7
+ const schema = tool?.inputSchema;
8
+ const required = schema?.type === 'object' && Array.isArray(schema.required)
9
+ ? schema.required.filter((value) => typeof value === 'string')
10
+ : [];
11
+ const name = required.length === 1 && typeof required[0] === 'string' ? required[0] : undefined;
12
+ const property = name && isObject(schema?.properties) ? schema.properties[name] : undefined;
13
+ const key = name && isObject(property) && property.type === 'string' ? name : 'input';
14
+ return { [key]: String(message.arguments ?? '') };
15
+ }
16
+ function isObject(value) {
17
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
18
+ }
@@ -26,7 +26,7 @@ export const codeModeToolDefinition = {
26
26
  - Observe only evidence needed for the next decision or verification: use observe.result for original results, observe.text for derived values, and observe.image for image values. Preserve source and failure details needed for reasoning; keep other intermediate data in the cell.
27
27
  - Single-tool cells are valid. Keep loops and retries bounded, and do not blindly replay operations with side effects.
28
28
  - Evaluates source in a fresh restricted ECMAScript runtime with top-level await and Promise.all. It is not Node.js or a browser. JavaScript globals do not survive between cells; bounded JSON state is available through state.
29
- - Accepts raw JavaScript source text, not JSON, quoted strings, or markdown code fences. Responses uses the source directly; Anthropic uses the code field of its tool input object. In either case, do not quote or fence the source again.
29
+ - Accepts JavaScript source text. When exposed as a freeform tool, send the source directly; when exposed with a JSON input schema, put the source in its code field. Do not add another JSON wrapper, quoted string, or markdown code fence inside the source.
30
30
  - Use only standard ECMAScript built-ins and the explicitly documented tools, observe, state, catalog and cell namespaces. Do not use Node.js, browser or Web APIs. Module imports, filesystem and network APIs are unavailable. Obtain external capabilities only through available tools. Use tools.exec to start managed processes when permitted; use tools.exec_wait afterward to wait, write stdin or cancel.
31
31
 
32
32
  Tool calls:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.10.8",
3
+ "version": "0.10.9",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",