@myagentroam/agent 0.10.7 → 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.
Files changed (52) hide show
  1. package/dist/agent-core/extensions/compaction.d.ts +1 -0
  2. package/dist/agent-core/extensions/model-request.js +1 -1
  3. package/dist/agent-core/extensions/session-messages.d.ts +1 -1
  4. package/dist/agent-core/host/contracts.d.ts +2 -0
  5. package/dist/agent-core/host/local-agent-host.d.ts +2 -0
  6. package/dist/agent-core/host/local-agent-host.js +2 -0
  7. package/dist/agent-core/model/anthropic-messages.js +15 -23
  8. package/dist/agent-core/model/catalog-file.d.ts +14 -0
  9. package/dist/agent-core/model/configuration.d.ts +7 -0
  10. package/dist/agent-core/model/configuration.js +10 -0
  11. package/dist/agent-core/model/contracts.d.ts +8 -0
  12. package/dist/agent-core/model/openai-responses.d.ts +1 -1
  13. package/dist/agent-core/model/openai-responses.js +62 -29
  14. package/dist/agent-core/model/runtime-credential.d.ts +2 -0
  15. package/dist/agent-core/model/runtime-credential.js +15 -0
  16. package/dist/agent-core/model/sse.d.ts +1 -1
  17. package/dist/agent-core/model/sse.js +22 -4
  18. package/dist/agent-core/model/tool-input.d.ts +3 -0
  19. package/dist/agent-core/model/tool-input.js +18 -0
  20. package/dist/agent-core/model/usage-observer.d.ts +31 -0
  21. package/dist/agent-core/model/usage-observer.js +82 -0
  22. package/dist/agent-core/runtime/events.d.ts +2 -0
  23. package/dist/agent-core/sdk/agent.js +9 -1
  24. package/dist/agent-core/sdk/extension-models.d.ts +1 -1
  25. package/dist/agent-core/sdk/extension-models.js +9 -1
  26. package/dist/agent-core/sdk/session.js +31 -3
  27. package/dist/agent-core/session/catalog.d.ts +1 -0
  28. package/dist/agent-core/session/catalog.js +1 -0
  29. package/dist/agent-core/session/jsonl-store.d.ts +9 -0
  30. package/dist/agent-core/session/jsonl-store.js +24 -0
  31. package/dist/agent-core/session/tool-result-resources.d.ts +26 -0
  32. package/dist/agent-core/session/tool-result-resources.js +285 -0
  33. package/dist/agent-core/session/transaction-recovery.js +1 -1
  34. package/dist/agent-core/tools/code-mode/dispatch.js +5 -0
  35. package/dist/agent-core/tools/code-mode/index.js +8 -2
  36. package/dist/agent-core/tools/code-mode/observations.d.ts +1 -0
  37. package/dist/agent-core/tools/contracts.d.ts +2 -0
  38. package/dist/agent-core/tools/execution.js +11 -2
  39. package/dist/agent-core/tools/file-change-diff.d.ts +10 -0
  40. package/dist/agent-core/tools/file-change-diff.js +139 -0
  41. package/dist/agent-core/tools/local/apply-patch.js +19 -8
  42. package/dist/agent-core/tools/result-extensions.d.ts +46 -0
  43. package/dist/agent-core/tools/result-extensions.js +134 -0
  44. package/dist/agent-core/tools/runtime-context.d.ts +1 -0
  45. package/dist/agent-core/tools/runtime.js +6 -3
  46. package/dist/index.d.ts +4 -0
  47. package/dist/index.js +1 -0
  48. package/dist/shared/auto-title.d.ts +2 -0
  49. package/dist/shared/auto-title.js +6 -0
  50. package/dist/shared/model-text.d.ts +2 -0
  51. package/dist/shared/model-text.js +6 -0
  52. package/package.json +1 -1
@@ -19,6 +19,7 @@ export interface CoreCompactMessage {
19
19
  readonly arguments?: unknown;
20
20
  }
21
21
  export interface CoreCompactContext {
22
+ readonly reportModelUsage?: ((usage: import('../model/usage-observer.js').ModelRequestUsage) => Promise<void> | void) | undefined;
22
23
  readonly operationId: string;
23
24
  readonly reason: 'manual' | 'automatic';
24
25
  readonly messages: readonly CoreCompactMessage[];
@@ -9,7 +9,7 @@ export class ExtensionRequestSnapshots {
9
9
  #current;
10
10
  #completed;
11
11
  record(modelId, request) {
12
- const { onAttemptDiagnostic: _diagnostic, ...serializable } = request;
12
+ const { onAttemptDiagnostic: _diagnostic, onModelUsage: _usage, onModelUsageFailure: _usageFailure, usageRequestId: _usageRequestId, ...serializable } = request;
13
13
  const retained = structuredClone(serializable);
14
14
  let view;
15
15
  // Preserve dispatch-time isolation without projecting unused public views.
@@ -29,7 +29,7 @@ export interface CoreSessionMessages {
29
29
  readonly nextCursor?: string;
30
30
  }
31
31
  export declare function normalizeSessionMessagesInput(input: CoreSessionMessagesInput): Readonly<{
32
- view: "model" | "request" | "conversation";
32
+ view: "model" | "conversation" | "request";
33
33
  agentId: string | undefined;
34
34
  includeImages: boolean;
35
35
  cursor: number;
@@ -53,6 +53,8 @@ export interface HostSessionResourceSnapshot {
53
53
  }
54
54
  /** The Host owns workspace/tool effects; the SDK owns JSONL storage beneath its supplied HOME. */
55
55
  export interface MarAgentHost {
56
+ /** Independent model consumption facts, including child and auxiliary calls. */
57
+ modelUsage?: import('../model/usage-observer.js').ModelUsageObserver | undefined;
56
58
  readonly extensionPorts?: CoreHostExtensionPorts;
57
59
  describe(): Promise<HostDescription>;
58
60
  executeTool(call: ToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
@@ -23,6 +23,7 @@ export interface LocalAgentExecutionExtensions extends HostExecutionExtensions {
23
23
  }
24
24
  export declare class LocalAgentHost implements MarAgentHost {
25
25
  #private;
26
+ readonly modelUsage: import('../model/usage-observer.js').ModelUsageObserver | undefined;
26
27
  readonly extensionPorts: import('./contracts.js').CoreHostExtensionPorts;
27
28
  readonly events: MarAgentEvent[];
28
29
  constructor(input: {
@@ -31,6 +32,7 @@ export declare class LocalAgentHost implements MarAgentHost {
31
32
  beforeOperationToolExecute?: (input: ToolCall, call: CoreOperationContext) => Promise<void> | void;
32
33
  callMcpOperation?: (input: ToolCall, call: CoreOperationContext, expected: NonNullable<McpToolCallContext['expectedTool']>) => Promise<ToolExecutionResult>;
33
34
  network?: CoreNetworkBackend | false;
35
+ modelUsage?: import('../model/usage-observer.js').ModelUsageObserver;
34
36
  homeDirectory: string;
35
37
  workspace: string;
36
38
  mode?: 'normal' | 'node';
@@ -12,6 +12,7 @@ import { directLocalProcessExecutor } from './local-process-executor.js';
12
12
  import { BUILTIN_TOOL_DEFINITIONS } from '../tools/registry.js';
13
13
  import { assertMcpToolCatalog } from '../tools/mcp/catalog.js';
14
14
  export class LocalAgentHost {
15
+ modelUsage;
15
16
  extensionPorts;
16
17
  #home;
17
18
  #workspace;
@@ -37,6 +38,7 @@ export class LocalAgentHost {
37
38
  #shell;
38
39
  #resourceIdleTtlMs;
39
40
  constructor(input) {
41
+ this.modelUsage = input.modelUsage;
40
42
  this.#home = resolve(input.homeDirectory);
41
43
  this.#workspace = resolve(input.workspace);
42
44
  this.#environment = input.environment ?? process.env;
@@ -1,3 +1,5 @@
1
+ import { observeModelRequestUsage } from './usage-observer.js';
2
+ import { jsonToolInput } from './tool-input.js';
1
3
  import { observationModelParts } from '../tools/code-mode/observations.js';
2
4
  import { stableToolSchema } from '../tools/definition-projection.js';
3
5
  import { randomUUID } from 'node:crypto';
@@ -38,7 +40,7 @@ export class AnthropicMessagesAdapter {
38
40
  let firstSemanticEventType;
39
41
  await reportModelAttempt(request, { ...attemptDiagnostic, state: 'STARTED' });
40
42
  try {
41
- for await (const event of this.startAttempt(request, signal)) {
43
+ for await (const event of observeModelRequestUsage(() => this.startAttempt(request, signal), request, this.configuration, signal)) {
42
44
  if (event.type.startsWith('output.')) {
43
45
  resetOutputTiming = true;
44
46
  yield event;
@@ -143,14 +145,18 @@ export class AnthropicMessagesAdapter {
143
145
  let terminal;
144
146
  let latestInputUsage;
145
147
  let thinkingBytes = 0;
146
- for await (const raw of parseAnthropicSse(response, signal)) {
148
+ for await (const raw of parseAnthropicSse(response, signal, apiKey.reveal())) {
147
149
  const event = record(raw);
148
150
  const type = text(event.type) ?? 'unknown';
149
151
  const index = number(event.index) ?? -1;
150
152
  if (type === 'message_start') {
151
153
  const message = record(event.message);
152
154
  responseId = text(message.id) ?? responseId;
153
- yield observeModelStreamEvent({ type: 'response.started', responseId }, modelStreamEventObservedAt(raw));
155
+ yield observeModelStreamEvent({
156
+ type: 'response.started',
157
+ responseId,
158
+ ...(text(message.model) ? { modelId: text(message.model) } : {})
159
+ }, modelStreamEventObservedAt(raw));
154
160
  const usage = record(message.usage);
155
161
  const inputTokens = inputTokenCount(usage.input_tokens);
156
162
  const cacheReadTokens = inputTokenCount(usage.cache_read_input_tokens);
@@ -419,9 +425,9 @@ export class AnthropicMessagesAdapter {
419
425
  });
420
426
  }
421
427
  }
422
- async function* parseAnthropicSse(response, signal) {
428
+ async function* parseAnthropicSse(response, signal, apiKey) {
423
429
  try {
424
- yield* parseSse(response, signal);
430
+ yield* parseSse(response, signal, undefined, undefined, [apiKey]);
425
431
  }
426
432
  catch (cause) {
427
433
  if (response.ok &&
@@ -491,6 +497,9 @@ function inputUsage(inputTokens, cacheReadTokens, cacheWriteTokens) {
491
497
  return {
492
498
  inputTokens,
493
499
  contextInputTokens: tokenSum(inputTokens, cacheReadTokens, cacheWriteTokens),
500
+ ...(inputTokens === undefined
501
+ ? {}
502
+ : { totalInputTokens: tokenSum(inputTokens, cacheReadTokens, cacheWriteTokens) }),
494
503
  cacheReadTokens,
495
504
  cacheWriteTokens
496
505
  };
@@ -543,7 +552,7 @@ function anthropicMessages(messages, images, tools = [], modelId, automaticCache
543
552
  type: 'tool_use',
544
553
  id: message.callId,
545
554
  name: message.name,
546
- input: anthropicToolInput(message, toolDefinitions.get(message.name ?? ''))
555
+ input: jsonToolInput(message, toolDefinitions.get(message.name ?? ''))
547
556
  };
548
557
  const previous = result.at(-1);
549
558
  if (previous?.role === 'assistant' && Array.isArray(previous.content))
@@ -665,23 +674,6 @@ function uniqueModelImages(...groups) {
665
674
  return [image];
666
675
  }));
667
676
  }
668
- function anthropicToolInput(message, tool) {
669
- if (isObject(message.arguments))
670
- return message.arguments;
671
- if (message.inputMode !== 'freeform')
672
- return {};
673
- const property = freeformInputProperty(tool?.inputSchema);
674
- return { [property ?? 'input']: String(message.arguments ?? '') };
675
- }
676
- function freeformInputProperty(schema) {
677
- if (schema?.type !== 'object' || !isObject(schema.properties) || !Array.isArray(schema.required))
678
- return undefined;
679
- const required = schema.required.filter((value) => typeof value === 'string');
680
- if (required.length !== 1)
681
- return undefined;
682
- const property = schema.properties[required[0]];
683
- return isObject(property) && property.type === 'string' ? required[0] : undefined;
684
- }
685
677
  function isObject(value) {
686
678
  return value !== null && typeof value === 'object' && !Array.isArray(value);
687
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()
@@ -82,6 +82,10 @@ export interface ModelRetryState {
82
82
  retryDelayMs?: number;
83
83
  }
84
84
  export interface ModelRequest {
85
+ /** Metering identity and sink are local only, never serialized upstream. */
86
+ usageRequestId?: string;
87
+ onModelUsage?: (usage: import('./usage-observer.js').ModelRequestUsage) => Promise<void> | void;
88
+ onModelUsageFailure?: () => void;
85
89
  /** Shared local attempt accounting across Adapter retries and Runtime continuation. Never sent upstream. */
86
90
  retryState?: ModelRetryState;
87
91
  system: string;
@@ -106,6 +110,9 @@ export interface ModelRequest {
106
110
  onAttemptDiagnostic?: (diagnostic: ModelAttemptDiagnostic) => Promise<void> | void;
107
111
  }
108
112
  export interface ModelUsage {
113
+ /** Normalized total input including cache; distinct from protocol-native inputTokens. */
114
+ totalInputTokens?: number | undefined;
115
+ reasoningTokens?: number | undefined;
109
116
  inputTokens?: number | undefined;
110
117
  contextInputTokens?: number | undefined;
111
118
  outputTokens?: number | undefined;
@@ -119,6 +126,7 @@ export type ModelStopReason = 'completed' | 'tool_use' | 'pause' | 'max_output'
119
126
  export type ModelEvent = {
120
127
  type: 'response.started';
121
128
  responseId: string;
129
+ modelId?: string;
122
130
  } | {
123
131
  type: 'output.started';
124
132
  } | {
@@ -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 {};
@@ -1,3 +1,4 @@
1
+ import { observeModelRequestUsage } from './usage-observer.js';
1
2
  import { observationModelParts } from '../tools/code-mode/observations.js';
2
3
  import { stableToolSchema } from '../tools/definition-projection.js';
3
4
  import { isDeepStrictEqual } from 'node:util';
@@ -7,8 +8,9 @@ import { marAgentReasoningEffortSchema, resolveModelDefaultReasoningEffort, uses
7
8
  import WebSocket from 'ws';
8
9
  import { isRetryableUpstreamOverloadCode, number, parseSse, record, text, observeModelStreamEvent, modelStreamEventObservedAt } from './sse.js';
9
10
  import { connectSocks5Target, fetchModelResponse, isRetryableModelHttpStatus, isRetryableModelTransportError, modelRetryMaxAttempts, isRetryableModelRequestError, modelRetryDelay, waitForModelRetry } from './http.js';
10
- import { acquireModelCredential, appendModelCredentialHeaders } from './runtime-credential.js';
11
+ import { acquireModelCredential, appendModelCredentialHeaders, modelCredentialSensitiveValues } from './runtime-credential.js';
11
12
  import { resolveModelTextBlocks } from './text-blocks.js';
13
+ import { jsonToolInput } from './tool-input.js';
12
14
  import { reportModelAttempt } from './attempt-diagnostics.js';
13
15
  const WEBSOCKET_IDLE_TIMEOUT_MS = 300_000;
14
16
  const WEBSOCKET_SEND_FRAME_BYTES = 1024 * 1024;
@@ -114,7 +116,7 @@ export class OpenAiResponsesAdapter {
114
116
  ...requestPrefixDiagnostic
115
117
  });
116
118
  try {
117
- for await (const event of this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, turn)) {
119
+ for await (const event of observeModelRequestUsage(() => this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, turn), request, this.configuration, signal)) {
118
120
  if (event.type.startsWith('output.')) {
119
121
  resetOutputTiming = true;
120
122
  yield event;
@@ -252,11 +254,13 @@ export class OpenAiResponsesAdapter {
252
254
  }
253
255
  async *startAttempt(request, signal, body, continuation, credentialReason, turn) {
254
256
  let latestCredentialRevision;
257
+ let diagnosticSecrets = [];
255
258
  const fetchResponse = async (requestBody) => {
256
259
  const serializedBody = JSON.stringify(requestBody);
257
260
  return await fetchModelResponse(responsesEndpoint(this.configuration.baseUrl), async () => {
258
261
  const credential = await acquireResponsesCredential(this.configuration, credentialReason, signal);
259
262
  latestCredentialRevision = credential.revision;
263
+ diagnosticSecrets = modelCredentialSensitiveValues(credential);
260
264
  return {
261
265
  request: {
262
266
  method: 'POST',
@@ -290,7 +294,7 @@ export class OpenAiResponsesAdapter {
290
294
  else {
291
295
  response = await fetchResponse(body);
292
296
  turn.captureTurnState(response.headers.get(X_CODEX_TURN_STATE));
293
- rawEvents = parseOpenAiResponsesSse(response, signal, continuation);
297
+ rawEvents = parseOpenAiResponsesSse(response, signal, continuation, diagnosticSecrets);
294
298
  }
295
299
  }
296
300
  catch (error) {
@@ -383,7 +387,10 @@ export class OpenAiResponsesAdapter {
383
387
  else if (type === 'response.created')
384
388
  yield observeModelStreamEvent({
385
389
  type: 'response.started',
386
- responseId: responseEventId(event) ?? 'unknown'
390
+ responseId: responseEventId(event) ?? 'unknown',
391
+ ...(text(record(event.response).model)
392
+ ? { modelId: text(record(event.response).model) }
393
+ : {})
387
394
  }, modelStreamEventObservedAt(raw));
388
395
  else if (type === 'response.output_text.delta')
389
396
  yield observeModelStreamEvent({
@@ -564,6 +571,8 @@ export class OpenAiResponsesAdapter {
564
571
  final: true,
565
572
  usage: {
566
573
  inputTokens: number(usage.input_tokens),
574
+ totalInputTokens: number(usage.input_tokens),
575
+ reasoningTokens: number(record(usage.output_tokens_details).reasoning_tokens),
567
576
  contextInputTokens: number(usage.input_tokens),
568
577
  outputTokens: number(usage.output_tokens),
569
578
  cacheReadTokens: number(inputDetails.cached_tokens),
@@ -891,14 +900,15 @@ function responsesRequestBody(configuration, request, messages) {
891
900
  throw new MarAgentError('MAR_AGENT_MODEL_CAPABILITY_UNAVAILABLE', 'This model protocol does not support explicit text-block cache markers.');
892
901
  const tools = request.allowTools === false
893
902
  ? []
894
- : request.tools.map((tool) => tool.nativeType === 'tool_search'
903
+ : request.tools.map((tool) => tool.nativeType === 'tool_search' &&
904
+ configuration.responsesOptionalParameters?.nativeTools !== false
895
905
  ? {
896
906
  type: 'tool_search',
897
907
  execution: 'client',
898
908
  description: tool.description,
899
909
  parameters: stableToolSchema(strictFunctionSchema(stableToolSchema(tool.inputSchema)))
900
910
  }
901
- : responsesFunctionTool(tool));
911
+ : responsesFunctionTool(tool, configuration.responsesOptionalParameters?.nativeTools !== false));
902
912
  if (configuration.responsesToolNamespace && tools.length > 0) {
903
913
  const native = tools.filter((tool) => record(tool).type === 'tool_search');
904
914
  const nested = tools.filter((tool) => record(tool).type !== 'tool_search');
@@ -914,7 +924,7 @@ function responsesRequestBody(configuration, request, messages) {
914
924
  if (request.allowTools !== false && configuration.hostedWebSearch)
915
925
  tools.push({ type: 'web_search' });
916
926
  const toolChoice = request.toolChoice ?? 'auto';
917
- 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);
918
928
  if (request.systemBlocks !== undefined && (request.systemBlocks.length || request.system.length))
919
929
  input.unshift({
920
930
  role: 'developer',
@@ -937,25 +947,40 @@ function responsesRequestBody(configuration, request, messages) {
937
947
  : {}),
938
948
  instructions: request.systemBlocks === undefined ? request.system : '',
939
949
  tools,
940
- tool_choice: toolChoice,
950
+ ...(tools.length > 0 ? { tool_choice: toolChoice, parallel_tool_calls: true } : {}),
941
951
  input,
942
952
  stream: true,
943
- parallel_tool_calls: true,
944
953
  store: false,
945
- text: { verbosity: 'low' },
954
+ ...(configuration.responsesOptionalParameters?.textVerbosity === false
955
+ ? {}
956
+ : { text: { verbosity: 'low' } }),
946
957
  include: [
947
958
  'reasoning.encrypted_content',
948
959
  ...(configuration.hostedWebSearch && request.allowTools !== false && toolChoice !== 'none'
949
960
  ? ['web_search_call.action.sources']
950
961
  : [])
951
962
  ],
952
- ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
953
- reasoning: {
954
- effort: request.requestReasoningEffort?.trim() ||
955
- request.reasoningEffort?.trim() ||
956
- resolveModelDefaultReasoningEffort(configuration),
957
- summary: 'auto'
958
- }
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
+ })
959
984
  };
960
985
  }
961
986
  function incrementalResponsesInput(state, current) {
@@ -1025,9 +1050,9 @@ function turnStateFromEvent(event) {
1025
1050
  }
1026
1051
  return undefined;
1027
1052
  }
1028
- async function* parseOpenAiResponsesSse(response, signal, continuation) {
1053
+ async function* parseOpenAiResponsesSse(response, signal, continuation, sensitiveValues) {
1029
1054
  try {
1030
- yield* parseSse(response, signal);
1055
+ yield* parseSse(response, signal, undefined, undefined, sensitiveValues);
1031
1056
  }
1032
1057
  catch (cause) {
1033
1058
  if (response.ok && !signal.aborted && isRetryableOpenAiStreamReadError(cause))
@@ -1765,11 +1790,13 @@ function responseCitations(response) {
1765
1790
  function responseEventId(event) {
1766
1791
  return text(record(event.response).id);
1767
1792
  }
1768
- export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true) {
1793
+ export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true, nativeTools = true, tools = []) {
1769
1794
  const compatibleMessages = messages.filter((message) => message.role !== 'provider' ||
1770
1795
  (message.provider === 'OPENAI_RESPONSES' &&
1771
1796
  (message.providerModelId === undefined || message.providerModelId === modelId) &&
1772
1797
  (configurationUpdates || message.item?.type !== 'configuration_update') &&
1798
+ (nativeTools ||
1799
+ !['custom_tool_call', 'tool_search_call', 'tool_search_output'].includes(String(message.item?.type))) &&
1773
1800
  (!requireEncryptedReasoning ||
1774
1801
  message.item?.type !== 'reasoning' ||
1775
1802
  typeof message.item.encrypted_content === 'string')));
@@ -1804,7 +1831,10 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
1804
1831
  }
1805
1832
  }
1806
1833
  for (const message of compatibleMessages)
1807
- 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)
1808
1838
  customCallIds.add(message.callId);
1809
1839
  const orderedMessages = [];
1810
1840
  for (const message of compatibleMessages) {
@@ -1822,9 +1852,9 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
1822
1852
  return orderedMessages
1823
1853
  .filter((message) => !(message.role === 'tool_call' && message.callId && providerCallIds.has(message.callId)) &&
1824
1854
  !(message.role === 'assistant' && providerMessageTexts.has(message.content)))
1825
- .map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds));
1855
+ .map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds, nativeTools, tools));
1826
1856
  }
1827
- function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set()) {
1857
+ function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set(), nativeTools = true, tools = []) {
1828
1858
  const userImages = message.role === 'user' ? uniqueModelImages(message.images, images) : [];
1829
1859
  if (message.role === 'provider') {
1830
1860
  if (message.provider !== 'OPENAI_RESPONSES' || !message.item)
@@ -1839,14 +1869,14 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1839
1869
  throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Provider context item type is unsupported.');
1840
1870
  return item;
1841
1871
  }
1842
- if (message.role === 'tool_call' && message.inputMode === 'freeform')
1872
+ if (nativeTools && message.role === 'tool_call' && message.inputMode === 'freeform')
1843
1873
  return {
1844
1874
  type: 'custom_tool_call',
1845
1875
  call_id: message.callId,
1846
1876
  name: message.name,
1847
1877
  input: String(message.arguments ?? '')
1848
1878
  };
1849
- if (message.role === 'tool_call' && message.name === 'tool_search')
1879
+ if (nativeTools && message.role === 'tool_call' && message.name === 'tool_search')
1850
1880
  return {
1851
1881
  type: 'tool_search_call',
1852
1882
  call_id: message.callId,
@@ -1859,9 +1889,12 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1859
1889
  type: 'function_call',
1860
1890
  call_id: message.callId,
1861
1891
  name: message.name,
1862
- 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 ?? {}))
1863
1895
  };
1864
1896
  if (message.role === 'tool' &&
1897
+ nativeTools &&
1865
1898
  message.toolSearchTools &&
1866
1899
  !functionCallIds.has(message.callId ?? ''))
1867
1900
  return {
@@ -1869,7 +1902,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1869
1902
  call_id: message.callId,
1870
1903
  status: 'completed',
1871
1904
  execution: 'client',
1872
- tools: message.toolSearchTools.map(responsesFunctionTool)
1905
+ tools: message.toolSearchTools.map((tool) => responsesFunctionTool(tool))
1873
1906
  };
1874
1907
  if (message.role === 'tool')
1875
1908
  return {
@@ -1928,9 +1961,9 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
1928
1961
  : message.content
1929
1962
  };
1930
1963
  }
1931
- function responsesFunctionTool(tool) {
1964
+ function responsesFunctionTool(tool, nativeTools = true) {
1932
1965
  const strict = !hasOpenFunctionObject(tool.inputSchema);
1933
- return tool.inputMode === 'freeform'
1966
+ return tool.inputMode === 'freeform' && nativeTools
1934
1967
  ? { type: 'custom', name: tool.name, description: tool.description }
1935
1968
  : {
1936
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
+ }