@myagentroam/agent 0.10.8 → 0.10.10

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) {
@@ -583,7 +586,9 @@ export class OpenAiResponsesAdapter {
583
586
  }, modelStreamEventObservedAt(raw));
584
587
  completed = true;
585
588
  }
586
- else if (type === 'error' || type === 'response.failed') {
589
+ else if (type === 'error' ||
590
+ type === 'response.failed' ||
591
+ (event.type === undefined && event.error !== undefined)) {
587
592
  const topLevel = record(event.error);
588
593
  const nested = record(record(event.response).error);
589
594
  const code = boundedError(text(event.code) ??
@@ -591,7 +596,10 @@ export class OpenAiResponsesAdapter {
591
596
  text(topLevel.type) ??
592
597
  text(nested.code) ??
593
598
  text(nested.type), 80);
594
- const message = boundedError(text(event.message) ?? text(topLevel.message) ?? text(nested.message), 1_000);
599
+ const message = boundedError(text(event.message) ??
600
+ text(topLevel.message) ??
601
+ text(nested.message) ??
602
+ text(event.error), 1_000);
595
603
  const status = upstreamHttpStatus(event.status, topLevel.status, record(event.response).status, nested.status);
596
604
  const connectionLimit = this.configuration.responsesTransport?.transport === 'WEBSOCKET' &&
597
605
  isWebSocketConnectionLimitCode(code);
@@ -897,14 +905,15 @@ function responsesRequestBody(configuration, request, messages) {
897
905
  throw new MarAgentError('MAR_AGENT_MODEL_CAPABILITY_UNAVAILABLE', 'This model protocol does not support explicit text-block cache markers.');
898
906
  const tools = request.allowTools === false
899
907
  ? []
900
- : request.tools.map((tool) => tool.nativeType === 'tool_search'
908
+ : request.tools.map((tool) => tool.nativeType === 'tool_search' &&
909
+ configuration.responsesOptionalParameters?.nativeTools !== false
901
910
  ? {
902
911
  type: 'tool_search',
903
912
  execution: 'client',
904
913
  description: tool.description,
905
914
  parameters: stableToolSchema(strictFunctionSchema(stableToolSchema(tool.inputSchema)))
906
915
  }
907
- : responsesFunctionTool(tool));
916
+ : responsesFunctionTool(tool, configuration.responsesOptionalParameters?.nativeTools !== false));
908
917
  if (configuration.responsesToolNamespace && tools.length > 0) {
909
918
  const native = tools.filter((tool) => record(tool).type === 'tool_search');
910
919
  const nested = tools.filter((tool) => record(tool).type !== 'tool_search');
@@ -920,7 +929,7 @@ function responsesRequestBody(configuration, request, messages) {
920
929
  if (request.allowTools !== false && configuration.hostedWebSearch)
921
930
  tools.push({ type: 'web_search' });
922
931
  const toolChoice = request.toolChoice ?? 'auto';
923
- const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration));
932
+ const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration), configuration.responsesOptionalParameters?.nativeTools !== false, request.tools);
924
933
  if (request.systemBlocks !== undefined && (request.systemBlocks.length || request.system.length))
925
934
  input.unshift({
926
935
  role: 'developer',
@@ -943,25 +952,40 @@ function responsesRequestBody(configuration, request, messages) {
943
952
  : {}),
944
953
  instructions: request.systemBlocks === undefined ? request.system : '',
945
954
  tools,
946
- tool_choice: toolChoice,
955
+ ...(tools.length > 0 ? { tool_choice: toolChoice, parallel_tool_calls: true } : {}),
947
956
  input,
948
957
  stream: true,
949
- parallel_tool_calls: true,
950
958
  store: false,
951
- text: { verbosity: 'low' },
959
+ ...(configuration.responsesOptionalParameters?.textVerbosity === false
960
+ ? {}
961
+ : { text: { verbosity: 'low' } }),
952
962
  include: [
953
963
  'reasoning.encrypted_content',
954
964
  ...(configuration.hostedWebSearch && request.allowTools !== false && toolChoice !== 'none'
955
965
  ? ['web_search_call.action.sources']
956
966
  : [])
957
967
  ],
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
- }
968
+ ...(request.promptCacheKey &&
969
+ configuration.responsesOptionalParameters?.promptCacheKey !== false
970
+ ? { prompt_cache_key: request.promptCacheKey }
971
+ : {}),
972
+ ...(configuration.responsesOptionalParameters?.reasoningEffort === false &&
973
+ configuration.responsesOptionalParameters?.reasoningSummary === false
974
+ ? {}
975
+ : {
976
+ reasoning: {
977
+ ...(configuration.responsesOptionalParameters?.reasoningEffort === false
978
+ ? {}
979
+ : {
980
+ effort: request.requestReasoningEffort?.trim() ||
981
+ request.reasoningEffort?.trim() ||
982
+ resolveModelDefaultReasoningEffort(configuration)
983
+ }),
984
+ ...(configuration.responsesOptionalParameters?.reasoningSummary === false
985
+ ? {}
986
+ : { summary: 'auto' })
987
+ }
988
+ })
965
989
  };
966
990
  }
967
991
  function incrementalResponsesInput(state, current) {
@@ -1031,9 +1055,9 @@ function turnStateFromEvent(event) {
1031
1055
  }
1032
1056
  return undefined;
1033
1057
  }
1034
- async function* parseOpenAiResponsesSse(response, signal, continuation) {
1058
+ async function* parseOpenAiResponsesSse(response, signal, continuation, sensitiveValues) {
1035
1059
  try {
1036
- yield* parseSse(response, signal);
1060
+ yield* parseSse(response, signal, undefined, undefined, sensitiveValues);
1037
1061
  }
1038
1062
  catch (cause) {
1039
1063
  if (response.ok && !signal.aborted && isRetryableOpenAiStreamReadError(cause))
@@ -1771,11 +1795,13 @@ function responseCitations(response) {
1771
1795
  function responseEventId(event) {
1772
1796
  return text(record(event.response).id);
1773
1797
  }
1774
- export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true) {
1798
+ export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true, nativeTools = true, tools = []) {
1775
1799
  const compatibleMessages = messages.filter((message) => message.role !== 'provider' ||
1776
1800
  (message.provider === 'OPENAI_RESPONSES' &&
1777
1801
  (message.providerModelId === undefined || message.providerModelId === modelId) &&
1778
1802
  (configurationUpdates || message.item?.type !== 'configuration_update') &&
1803
+ (nativeTools ||
1804
+ !['custom_tool_call', 'tool_search_call', 'tool_search_output'].includes(String(message.item?.type))) &&
1779
1805
  (!requireEncryptedReasoning ||
1780
1806
  message.item?.type !== 'reasoning' ||
1781
1807
  typeof message.item.encrypted_content === 'string')));
@@ -1810,7 +1836,10 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
1810
1836
  }
1811
1837
  }
1812
1838
  for (const message of compatibleMessages)
1813
- if (message.role === 'tool_call' && message.inputMode === 'freeform' && message.callId)
1839
+ if (nativeTools &&
1840
+ message.role === 'tool_call' &&
1841
+ message.inputMode === 'freeform' &&
1842
+ message.callId)
1814
1843
  customCallIds.add(message.callId);
1815
1844
  const orderedMessages = [];
1816
1845
  for (const message of compatibleMessages) {
@@ -1828,9 +1857,9 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
1828
1857
  return orderedMessages
1829
1858
  .filter((message) => !(message.role === 'tool_call' && message.callId && providerCallIds.has(message.callId)) &&
1830
1859
  !(message.role === 'assistant' && providerMessageTexts.has(message.content)))
1831
- .map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds));
1860
+ .map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds, nativeTools, tools));
1832
1861
  }
1833
- function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set()) {
1862
+ function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set(), nativeTools = true, tools = []) {
1834
1863
  const userImages = message.role === 'user' ? uniqueModelImages(message.images, images) : [];
1835
1864
  if (message.role === 'provider') {
1836
1865
  if (message.provider !== 'OPENAI_RESPONSES' || !message.item)
@@ -1845,14 +1874,14 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1845
1874
  throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Provider context item type is unsupported.');
1846
1875
  return item;
1847
1876
  }
1848
- if (message.role === 'tool_call' && message.inputMode === 'freeform')
1877
+ if (nativeTools && message.role === 'tool_call' && message.inputMode === 'freeform')
1849
1878
  return {
1850
1879
  type: 'custom_tool_call',
1851
1880
  call_id: message.callId,
1852
1881
  name: message.name,
1853
1882
  input: String(message.arguments ?? '')
1854
1883
  };
1855
- if (message.role === 'tool_call' && message.name === 'tool_search')
1884
+ if (nativeTools && message.role === 'tool_call' && message.name === 'tool_search')
1856
1885
  return {
1857
1886
  type: 'tool_search_call',
1858
1887
  call_id: message.callId,
@@ -1865,9 +1894,12 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1865
1894
  type: 'function_call',
1866
1895
  call_id: message.callId,
1867
1896
  name: message.name,
1868
- arguments: JSON.stringify(message.arguments ?? {})
1897
+ arguments: JSON.stringify(message.inputMode === 'freeform'
1898
+ ? jsonToolInput(message, tools.find((tool) => tool.name === message.name))
1899
+ : (message.arguments ?? {}))
1869
1900
  };
1870
1901
  if (message.role === 'tool' &&
1902
+ nativeTools &&
1871
1903
  message.toolSearchTools &&
1872
1904
  !functionCallIds.has(message.callId ?? ''))
1873
1905
  return {
@@ -1875,7 +1907,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1875
1907
  call_id: message.callId,
1876
1908
  status: 'completed',
1877
1909
  execution: 'client',
1878
- tools: message.toolSearchTools.map(responsesFunctionTool)
1910
+ tools: message.toolSearchTools.map((tool) => responsesFunctionTool(tool))
1879
1911
  };
1880
1912
  if (message.role === 'tool')
1881
1913
  return {
@@ -1934,9 +1966,9 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1934
1966
  : message.content
1935
1967
  };
1936
1968
  }
1937
- function responsesFunctionTool(tool) {
1969
+ function responsesFunctionTool(tool, nativeTools = true) {
1938
1970
  const strict = !hasOpenFunctionObject(tool.inputSchema);
1939
- return tool.inputMode === 'freeform'
1971
+ return tool.inputMode === 'freeform' && nativeTools
1940
1972
  ? { type: 'custom', name: tool.name, description: tool.description }
1941
1973
  : {
1942
1974
  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
+ }
@@ -34,6 +34,6 @@ export class ModelGenerationTiming {
34
34
  this.completed = true;
35
35
  }
36
36
  get durationMs() {
37
- return this.completed && this.valid && this.duration >= 1 ? this.duration : undefined;
37
+ return this.completed && this.valid && this.duration >= 1_000 ? this.duration : undefined;
38
38
  }
39
39
  }
@@ -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.10",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",