@jini-ai/agent-runtime 0.3.10 → 0.4.0

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 (200) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +34 -17
  3. package/dist/acp-model-probe.d.ts +6 -4
  4. package/dist/acp-model-probe.js +3 -3
  5. package/dist/agent-protocol/acp/account-failure.d.ts +4 -2
  6. package/dist/agent-protocol/acp/account-failure.js +1 -1
  7. package/dist/agent-protocol/acp/json.d.ts +6 -6
  8. package/dist/agent-protocol/acp/json.js +3 -3
  9. package/dist/agent-protocol/acp/models.d.ts +10 -5
  10. package/dist/agent-protocol/acp/models.js +32 -31
  11. package/dist/agent-protocol/acp/session-params.d.ts +3 -1
  12. package/dist/agent-protocol/acp/session-params.js +2 -1
  13. package/dist/agent-protocol/acp/session.d.ts +28 -22
  14. package/dist/agent-protocol/acp/session.js +181 -147
  15. package/dist/agent-protocol/acp/text-suppression.d.ts +1 -1
  16. package/dist/agent-protocol/acp/text-suppression.js +1 -1
  17. package/dist/agent-protocol/acp/types.d.ts +3 -2
  18. package/dist/agent-protocol/acp/updates.d.ts +11 -11
  19. package/dist/agent-protocol/acp/updates.js +2 -2
  20. package/dist/agent-protocol/core/json-line-stream.d.ts +24 -8
  21. package/dist/agent-protocol/core/json-line-stream.js +8 -8
  22. package/dist/agent-protocol/pi-rpc/events.d.ts +5 -1
  23. package/dist/agent-protocol/pi-rpc/events.js +36 -36
  24. package/dist/agent-protocol/pi-rpc/internal.d.ts +4 -1
  25. package/dist/agent-protocol/pi-rpc/models.d.ts +3 -1
  26. package/dist/agent-protocol/pi-rpc/models.js +1 -1
  27. package/dist/agent-protocol/pi-rpc/session.d.ts +1 -1
  28. package/dist/agent-protocol/pi-rpc/session.js +33 -32
  29. package/dist/amr-model-cache.d.ts +12 -4
  30. package/dist/amr-model-cache.js +17 -21
  31. package/dist/amr-profile-resolver.d.ts +3 -1
  32. package/dist/anthropic-live-models.js +0 -0
  33. package/dist/artifact-taxonomy.d.ts +6 -2
  34. package/dist/auth.d.ts +52 -15
  35. package/dist/auth.js +36 -36
  36. package/dist/claude-code-models.js +2 -2
  37. package/dist/claude-stream.d.ts +22 -3
  38. package/dist/claude-stream.js +24 -8
  39. package/dist/copilot-stream.d.ts +7 -3
  40. package/dist/copilot-stream.js +3 -3
  41. package/dist/defs/aider.d.ts +8 -1
  42. package/dist/defs/aider.js +2 -2
  43. package/dist/defs/amp.d.ts +8 -1
  44. package/dist/defs/amp.js +2 -2
  45. package/dist/defs/amr.d.ts +22 -6
  46. package/dist/defs/amr.js +23 -23
  47. package/dist/defs/antigravity.d.ts +15 -4
  48. package/dist/defs/antigravity.js +9 -8
  49. package/dist/defs/auggie.d.ts +4 -1
  50. package/dist/defs/auggie.js +1 -1
  51. package/dist/defs/claude.d.ts +12 -2
  52. package/dist/defs/claude.js +5 -5
  53. package/dist/defs/cline.d.ts +4 -1
  54. package/dist/defs/cline.js +1 -1
  55. package/dist/defs/codebuddy.d.ts +8 -1
  56. package/dist/defs/codebuddy.js +2 -2
  57. package/dist/defs/codex.d.ts +18 -4
  58. package/dist/defs/codex.js +7 -7
  59. package/dist/defs/copilot.d.ts +8 -1
  60. package/dist/defs/copilot.js +2 -2
  61. package/dist/defs/crush.d.ts +8 -1
  62. package/dist/defs/crush.js +1 -1
  63. package/dist/defs/cursor-agent.d.ts +14 -3
  64. package/dist/defs/cursor-agent.js +5 -5
  65. package/dist/defs/deepseek.d.ts +8 -1
  66. package/dist/defs/deepseek.js +2 -2
  67. package/dist/defs/devin.d.ts +12 -2
  68. package/dist/defs/devin.js +3 -3
  69. package/dist/defs/droid.d.ts +8 -1
  70. package/dist/defs/droid.js +1 -1
  71. package/dist/defs/gemini.d.ts +8 -1
  72. package/dist/defs/gemini.js +1 -1
  73. package/dist/defs/goose.d.ts +4 -1
  74. package/dist/defs/goose.js +1 -1
  75. package/dist/defs/grok-build.d.ts +11 -2
  76. package/dist/defs/grok-build.js +3 -3
  77. package/dist/defs/hermes.d.ts +4 -1
  78. package/dist/defs/hermes.js +2 -2
  79. package/dist/defs/kilo.d.ts +4 -1
  80. package/dist/defs/kilo.js +2 -2
  81. package/dist/defs/kimi.d.ts +4 -1
  82. package/dist/defs/kimi.js +2 -2
  83. package/dist/defs/kiro.d.ts +4 -1
  84. package/dist/defs/kiro.js +2 -2
  85. package/dist/defs/mimo.d.ts +8 -1
  86. package/dist/defs/mimo.js +2 -2
  87. package/dist/defs/opencode.d.ts +9 -2
  88. package/dist/defs/opencode.js +2 -2
  89. package/dist/defs/pi.d.ts +12 -2
  90. package/dist/defs/pi.js +9 -9
  91. package/dist/defs/qoder.d.ts +8 -1
  92. package/dist/defs/qoder.js +2 -2
  93. package/dist/defs/qwen.d.ts +8 -1
  94. package/dist/defs/qwen.js +2 -2
  95. package/dist/defs/reasonix.d.ts +4 -1
  96. package/dist/defs/reasonix.js +3 -3
  97. package/dist/defs/shared.d.ts +4 -2
  98. package/dist/defs/shared.js +2 -2
  99. package/dist/defs/trae-cli.d.ts +12 -2
  100. package/dist/defs/trae-cli.js +3 -3
  101. package/dist/defs/vibe.d.ts +4 -1
  102. package/dist/defs/vibe.js +2 -2
  103. package/dist/detection.d.ts +19 -4
  104. package/dist/detection.js +33 -33
  105. package/dist/diagnostics.d.ts +14 -3
  106. package/dist/diagnostics.js +5 -5
  107. package/dist/env.d.ts +16 -4
  108. package/dist/env.js +7 -7
  109. package/dist/executables.d.ts +24 -8
  110. package/dist/executables.js +13 -13
  111. package/dist/index.d.ts +228 -4
  112. package/dist/index.js +230 -6
  113. package/dist/invocation.d.ts +6 -1
  114. package/dist/invocation.js +2 -2
  115. package/dist/json-event-stream.d.ts +7 -2
  116. package/dist/json-event-stream.js +3 -3
  117. package/dist/launch.d.ts +12 -2
  118. package/dist/launch.js +3 -3
  119. package/dist/log-file.d.ts +4 -1
  120. package/dist/log-file.js +2 -2
  121. package/dist/mcp.d.ts +6 -3
  122. package/dist/mcp.js +1 -1
  123. package/dist/metadata.d.ts +7 -3
  124. package/dist/metadata.js +1 -1
  125. package/dist/mmd-routes.d.ts +22 -6
  126. package/dist/mmd-routes.js +14 -14
  127. package/dist/model-catalog-cache.d.ts +50 -0
  128. package/dist/model-catalog-cache.js +61 -0
  129. package/dist/model-loading-state.d.ts +10 -0
  130. package/dist/model-loading-state.js +23 -0
  131. package/dist/model-registry.d.ts +28 -7
  132. package/dist/model-registry.js +11 -8
  133. package/dist/models.d.ts +31 -6
  134. package/dist/models.js +6 -6
  135. package/dist/opencode-log.d.ts +12 -4
  136. package/dist/opencode-log.js +8 -8
  137. package/dist/paths.d.ts +6 -2
  138. package/dist/paths.js +3 -3
  139. package/dist/pi-models.d.ts +3 -1
  140. package/dist/pi-models.js +2 -2
  141. package/dist/prompt-budget.d.ts +17 -4
  142. package/dist/prompt-budget.js +3 -3
  143. package/dist/prompt-file.d.ts +5 -1
  144. package/dist/prompt-file.js +1 -1
  145. package/dist/providers/aihubmix.d.ts +34 -9
  146. package/dist/providers/aihubmix.js +26 -26
  147. package/dist/providers/anthropic-messages.d.ts +77 -20
  148. package/dist/providers/anthropic-messages.js +53 -50
  149. package/dist/providers/azure-chat.d.ts +21 -7
  150. package/dist/providers/azure-chat.js +18 -16
  151. package/dist/providers/connection-guard.d.ts +45 -31
  152. package/dist/providers/connection-guard.js +40 -143
  153. package/dist/providers/connection-test.d.ts +1 -1
  154. package/dist/providers/connection-test.js +13 -30
  155. package/dist/providers/elevenlabs.d.ts +7 -2
  156. package/dist/providers/elevenlabs.js +4 -4
  157. package/dist/providers/google-messages.d.ts +44 -10
  158. package/dist/providers/google-messages.js +36 -34
  159. package/dist/providers/google-schema.d.ts +26 -0
  160. package/dist/providers/google-schema.js +253 -0
  161. package/dist/providers/google.d.ts +21 -6
  162. package/dist/providers/google.js +10 -10
  163. package/dist/providers/index.d.ts +3 -2
  164. package/dist/providers/index.js +3 -2
  165. package/dist/providers/model-catalog.d.ts +1 -1
  166. package/dist/providers/model-catalog.js +14 -27
  167. package/dist/providers/oauth-callback-server.d.ts +1 -1
  168. package/dist/providers/oauth-callback-server.js +3 -2
  169. package/dist/providers/oauth-credentials.d.ts +7 -1
  170. package/dist/providers/oauth-credentials.js +5 -5
  171. package/dist/providers/oauth-provider.d.ts +2 -2
  172. package/dist/providers/oauth-provider.js +20 -25
  173. package/dist/providers/oauth-tokens.d.ts +26 -6
  174. package/dist/providers/oauth-tokens.js +9 -9
  175. package/dist/providers/ollama-chat.d.ts +66 -31
  176. package/dist/providers/ollama-chat.js +64 -61
  177. package/dist/providers/openai-chat.d.ts +77 -22
  178. package/dist/providers/openai-chat.js +71 -67
  179. package/dist/providers/pkce.d.ts +33 -20
  180. package/dist/providers/pkce.js +75 -146
  181. package/dist/providers/sse-decode.d.ts +3 -1
  182. package/dist/providers/sse-decode.js +1 -1
  183. package/dist/providers/token-params.d.ts +16 -5
  184. package/dist/providers/token-params.js +6 -6
  185. package/dist/providers/tool-turn-types.d.ts +89 -0
  186. package/dist/providers/tool-turn-types.js +2 -0
  187. package/dist/providers/tool-turn.d.ts +10 -0
  188. package/dist/providers/tool-turn.js +252 -0
  189. package/dist/qoder-stream.d.ts +7 -3
  190. package/dist/qoder-stream.js +3 -3
  191. package/dist/registry.d.ts +6 -2
  192. package/dist/registry.js +4 -4
  193. package/dist/resolution.d.ts +5 -1
  194. package/dist/resolution.js +3 -3
  195. package/dist/role-marker-guard.d.ts +9 -5
  196. package/dist/role-marker-guard.js +5 -5
  197. package/dist/terminal-launch.d.ts +6 -1
  198. package/dist/terminal-launch.js +2 -2
  199. package/dist/types.d.ts +22 -6
  200. package/package.json +43 -4
@@ -1,3 +1,4 @@
1
+ import { redactSecrets } from '@jini-ai/core';
1
2
  /**
2
3
  * @module providers/anthropic-messages
3
4
  *
@@ -74,7 +75,7 @@
74
75
  * for the integration-level regression proof.
75
76
  */
76
77
  import { createRoleMarkerGuard } from '../role-marker-guard.js';
77
- import { defaultDnsLookup, pinnedFetch, redactSecrets, validateBaseUrlResolved } from './connection-guard.js';
78
+ import { defaultDnsLookup, pinnedFetch, validateBaseUrlResolved } from './connection-guard.js';
78
79
  import { decodeSseStream } from './sse-decode.js';
79
80
  import { createTurnEndGuard } from './turn-end-guard.js';
80
81
  const DEFAULT_ANTHROPIC_BASE_URL = 'https://api.anthropic.com';
@@ -83,11 +84,12 @@ const DEFAULT_MAX_TOOL_TURNS = 8;
83
84
  function isRecord(value) {
84
85
  return typeof value === 'object' && value !== null && !Array.isArray(value);
85
86
  }
86
- export function anthropicRequestUrl(baseUrl) {
87
+ export function anthropicRequestUrl({ baseUrl }) {
87
88
  const base = (baseUrl ?? DEFAULT_ANTHROPIC_BASE_URL).replace(/\/+$/, '');
88
89
  return `${base}/v1/messages`;
89
90
  }
90
- export function anthropicHeaders(options) {
91
+ export function anthropicHeaders(requiredArgs, optionalArgs = {}) {
92
+ const options = { ...optionalArgs, ...requiredArgs };
91
93
  return {
92
94
  'content-type': 'application/json',
93
95
  'x-api-key': options.apiKey,
@@ -95,7 +97,7 @@ export function anthropicHeaders(options) {
95
97
  ...(options.extraHeaders ?? {}),
96
98
  };
97
99
  }
98
- export function anthropicRequestBody(options, messages) {
100
+ export function anthropicRequestBody({ options, messages }) {
99
101
  return {
100
102
  model: options.model,
101
103
  max_tokens: options.maxTokens,
@@ -106,7 +108,7 @@ export function anthropicRequestBody(options, messages) {
106
108
  ...(options.tools && options.tools.length > 0 ? { tools: options.tools } : {}),
107
109
  };
108
110
  }
109
- export function extractAnthropicErrorDetail(rawText) {
111
+ export function extractAnthropicErrorDetail({ rawText }) {
110
112
  try {
111
113
  const parsed = JSON.parse(rawText);
112
114
  if (isRecord(parsed) && isRecord(parsed.error) && typeof parsed.error.message === 'string') {
@@ -144,7 +146,7 @@ const MAX_IMAGE_BASE64_CHARS = Math.ceil((10 * 1024 * 1024 * 4) / 3);
144
146
  * @complexity O(1) — reads `data.length`, never decodes or parses the base64 payload.
145
147
  * @overallScore 100
146
148
  */
147
- export function invalidToolResultContentBlockReason(block) {
149
+ export function invalidToolResultContentBlockReason({ block }) {
148
150
  if (block.type === 'text')
149
151
  return null;
150
152
  if (block.source.type === 'url')
@@ -175,11 +177,11 @@ export function invalidToolResultContentBlockReason(block) {
175
177
  * `invalidToolResultContentBlockReason`).
176
178
  * @overallScore 100
177
179
  */
178
- export function guardToolResult(result) {
180
+ export function guardToolResult({ result }) {
179
181
  if (typeof result.content === 'string')
180
182
  return result;
181
183
  for (const block of result.content) {
182
- const reason = invalidToolResultContentBlockReason(block);
184
+ const reason = invalidToolResultContentBlockReason({ block: block });
183
185
  if (reason) {
184
186
  return { content: `[rejected tool result] ${reason}`, isError: true };
185
187
  }
@@ -187,7 +189,7 @@ export function guardToolResult(result) {
187
189
  return result;
188
190
  }
189
191
  /** Parses one SSE frame's data as a JSON object, or `null` for a malformed/empty keep-alive frame or a non-object payload — both are tolerated by the caller as "nothing to do this frame". */
190
- export function parseAnthropicSseData(raw) {
192
+ export function parseAnthropicSseData({ raw }) {
191
193
  let data;
192
194
  try {
193
195
  data = JSON.parse(raw);
@@ -197,7 +199,7 @@ export function parseAnthropicSseData(raw) {
197
199
  }
198
200
  return isRecord(data) ? data : null;
199
201
  }
200
- export function handleContentBlockStart(state, data) {
202
+ export function handleContentBlockStart({ state, data }) {
201
203
  if (!isRecord(data.content_block) || typeof data.index !== 'number')
202
204
  return;
203
205
  const block = data.content_block;
@@ -216,7 +218,7 @@ export function handleContentBlockStart(state, data) {
216
218
  * `'break'` once the guard flags contamination (the caller ends the turn immediately), otherwise
217
219
  * `'continue'`.
218
220
  */
219
- export function handleAnthropicTextDelta(state, index, text, onEvent) {
221
+ export function handleAnthropicTextDelta({ state, index, text, onEvent }) {
220
222
  const blockState = state.blocks.get(index);
221
223
  if (blockState)
222
224
  blockState.text += text;
@@ -225,7 +227,7 @@ export function handleAnthropicTextDelta(state, index, text, onEvent) {
225
227
  // delta in the same request can never reach this point already contaminated. (`feedText` itself
226
228
  // is still safe to call unconditionally regardless — it early-returns `''` once contaminated,
227
229
  // per its own doc.)
228
- const safe = state.guard.feedText(text);
230
+ const safe = state.guard.feedText({ text: text });
229
231
  if (safe.length > 0) {
230
232
  state.fullText += safe;
231
233
  onEvent({ type: 'text_delta', delta: safe });
@@ -237,26 +239,26 @@ export function handleAnthropicTextDelta(state, index, text, onEvent) {
237
239
  onEvent(warn);
238
240
  return 'break';
239
241
  }
240
- export function handleAnthropicInputJsonDelta(state, index, partialJson) {
242
+ export function handleAnthropicInputJsonDelta({ state, index, partialJson }) {
241
243
  const blockState = state.blocks.get(index);
242
244
  if (!blockState)
243
245
  return;
244
246
  blockState.inputJson += partialJson;
245
247
  }
246
- export function handleContentBlockDelta(state, data, onEvent) {
248
+ export function handleContentBlockDelta({ state, data, onEvent }) {
247
249
  if (!isRecord(data.delta) || typeof data.index !== 'number')
248
250
  return 'continue';
249
251
  const { delta, index } = data;
250
252
  if (delta.type === 'text_delta' && typeof delta.text === 'string') {
251
- return handleAnthropicTextDelta(state, index, delta.text, onEvent);
253
+ return handleAnthropicTextDelta({ state: state, index: index, text: delta.text, onEvent: onEvent });
252
254
  }
253
255
  if (delta.type === 'input_json_delta' && typeof delta.partial_json === 'string') {
254
- handleAnthropicInputJsonDelta(state, index, delta.partial_json);
256
+ handleAnthropicInputJsonDelta({ state: state, index: index, partialJson: delta.partial_json });
255
257
  }
256
258
  return 'continue';
257
259
  }
258
260
  /** Parses one tool_use block's accumulated `input_json_delta` text, falling back to `{}` for empty or malformed JSON — mirrors OpenAI's identical fallback for `tool_calls[].function.arguments` in `openai-chat.ts`. */
259
- export function parseAccumulatedToolInputJson(inputJson) {
261
+ export function parseAccumulatedToolInputJson({ inputJson }) {
260
262
  if (!inputJson.trim())
261
263
  return {};
262
264
  try {
@@ -266,18 +268,18 @@ export function parseAccumulatedToolInputJson(inputJson) {
266
268
  return {};
267
269
  }
268
270
  }
269
- export function handleContentBlockStop(state, data, onEvent) {
271
+ export function handleContentBlockStop({ state, data, onEvent }) {
270
272
  if (typeof data.index !== 'number')
271
273
  return;
272
274
  const blockState = state.blocks.get(data.index);
273
275
  if (blockState?.type === 'tool_use' && blockState.toolId && blockState.toolName) {
274
- const call = { id: blockState.toolId, name: blockState.toolName, input: parseAccumulatedToolInputJson(blockState.inputJson) };
276
+ const call = { id: blockState.toolId, name: blockState.toolName, input: parseAccumulatedToolInputJson({ inputJson: blockState.inputJson }) };
275
277
  state.toolCalls.push(call);
276
278
  onEvent({ type: 'tool_use', id: call.id, name: call.name, input: call.input });
277
279
  }
278
280
  state.blocks.delete(data.index);
279
281
  }
280
- export function handleMessageDelta(state, data, onEvent) {
282
+ export function handleMessageDelta({ state, data, onEvent }) {
281
283
  if (isRecord(data.delta) && typeof data.delta.stop_reason === 'string') {
282
284
  state.stopReason = data.delta.stop_reason;
283
285
  }
@@ -286,13 +288,13 @@ export function handleMessageDelta(state, data, onEvent) {
286
288
  onEvent({ type: 'usage', usage: data.usage });
287
289
  }
288
290
  }
289
- export function handleAnthropicErrorFrame(data, onEvent, apiKey) {
291
+ export function handleAnthropicErrorFrame({ data, onEvent, apiKey }) {
290
292
  const errorDetail = isRecord(data.error) && typeof data.error.message === 'string' ? data.error.message : 'upstream error';
291
293
  const errorType = isRecord(data.error) && typeof data.error.type === 'string' ? data.error.type : undefined;
292
- onEvent({ type: 'error', message: redactSecrets(errorDetail, [apiKey]), ...(errorType ? { code: errorType } : {}) });
294
+ onEvent({ type: 'error', message: redactSecrets({ input: errorDetail }, { exactSecrets: [apiKey] }), ...(errorType ? { code: errorType } : {}) });
293
295
  }
294
296
  /** The SSE event's dispatch key: `data.type` when present (every real Anthropic frame carries one), else the record's `event:` line, else `''` — a key that intentionally matches no entry in {@link ANTHROPIC_FRAME_HANDLERS} (`message_start`/`message_stop`/`ping`/future event types fall through as no-ops, same as before this frame was a lookup table). */
295
- export function anthropicFrameKind(data, frameEvent) {
297
+ export function anthropicFrameKind({ data, frameEvent }) {
296
298
  if (typeof data.type === 'string')
297
299
  return data.type;
298
300
  return frameEvent ?? '';
@@ -300,20 +302,20 @@ export function anthropicFrameKind(data, frameEvent) {
300
302
  /** One entry per SSE event kind this adapter reacts to. Replaces a sequential if-chain over `kind` — every branch below is now a single object-key lookup instead of N nesting-penalized `if` statements (see `runSingleAnthropicRequest`'s doc for the resulting flat loop). */
301
303
  const ANTHROPIC_FRAME_HANDLERS = {
302
304
  content_block_start: (state, data) => {
303
- handleContentBlockStart(state, data);
305
+ handleContentBlockStart({ state: state, data: data });
304
306
  return { action: 'continue' };
305
307
  },
306
- content_block_delta: (state, data, onEvent) => handleContentBlockDelta(state, data, onEvent) === 'break' ? { action: 'end', reason: 'contaminated' } : { action: 'continue' },
308
+ content_block_delta: (state, data, onEvent) => handleContentBlockDelta({ state: state, data: data, onEvent: onEvent }) === 'break' ? { action: 'end', reason: 'contaminated' } : { action: 'continue' },
307
309
  content_block_stop: (state, data, onEvent) => {
308
- handleContentBlockStop(state, data, onEvent);
310
+ handleContentBlockStop({ state: state, data: data, onEvent: onEvent });
309
311
  return { action: 'continue' };
310
312
  },
311
313
  message_delta: (state, data, onEvent) => {
312
- handleMessageDelta(state, data, onEvent);
314
+ handleMessageDelta({ state: state, data: data, onEvent: onEvent });
313
315
  return { action: 'continue' };
314
316
  },
315
317
  error: (_state, data, onEvent, apiKey) => {
316
- handleAnthropicErrorFrame(data, onEvent, apiKey);
318
+ handleAnthropicErrorFrame({ data: data, onEvent: onEvent, apiKey: apiKey });
317
319
  return { action: 'end', reason: 'error' };
318
320
  },
319
321
  };
@@ -328,7 +330,7 @@ async function openAnthropicResponseStream(options, messages, emitEnd) {
328
330
  const { onEvent } = options;
329
331
  // DNS-resolving, not merely textual — see the identical note in `openai-chat.ts`. A literal
330
332
  // private IP was already rejected; a hostname resolving to one was not.
331
- const baseUrlCheck = await validateBaseUrlResolved(options.baseUrl ?? DEFAULT_ANTHROPIC_BASE_URL, options.dnsLookup ?? defaultDnsLookup);
333
+ const baseUrlCheck = await validateBaseUrlResolved({ baseUrl: options.baseUrl ?? DEFAULT_ANTHROPIC_BASE_URL, lookup: options.dnsLookup ?? defaultDnsLookup });
332
334
  if (baseUrlCheck.error) {
333
335
  onEvent({ type: 'error', message: baseUrlCheck.error });
334
336
  emitEnd('error');
@@ -336,17 +338,17 @@ async function openAnthropicResponseStream(options, messages, emitEnd) {
336
338
  }
337
339
  let response;
338
340
  try {
339
- response = await (options.fetchImpl ?? pinnedFetch)(anthropicRequestUrl(options.baseUrl), {
340
- method: 'POST',
341
- headers: anthropicHeaders(options),
342
- body: JSON.stringify(anthropicRequestBody(options, messages)),
343
- redirect: 'error',
344
- ...(options.signal ? { signal: options.signal } : {}),
345
- }, baseUrlCheck.pinnedAddress);
341
+ response = await (options.fetchImpl ?? pinnedFetch)({ url: anthropicRequestUrl({ baseUrl: options.baseUrl }), init: {
342
+ method: 'POST',
343
+ headers: (({ apiKey, model, messages, maxTokens, onEvent, ...optionalArgs }) => anthropicHeaders({ apiKey, model, messages, maxTokens, onEvent }, optionalArgs))(options),
344
+ body: JSON.stringify(anthropicRequestBody({ options: options, messages: messages })),
345
+ redirect: 'error',
346
+ ...(options.signal ? { signal: options.signal } : {}),
347
+ }, pinnedAddress: baseUrlCheck.pinnedAddress });
346
348
  }
347
349
  catch (error) {
348
350
  const message = error instanceof Error ? error.message : String(error);
349
- onEvent({ type: 'error', message: redactSecrets(message, [options.apiKey]) });
351
+ onEvent({ type: 'error', message: redactSecrets({ input: message }, { exactSecrets: [options.apiKey] }) });
350
352
  emitEnd('error');
351
353
  return null;
352
354
  }
@@ -354,7 +356,7 @@ async function openAnthropicResponseStream(options, messages, emitEnd) {
354
356
  const rawText = await response.text();
355
357
  onEvent({
356
358
  type: 'error',
357
- message: redactSecrets(extractAnthropicErrorDetail(rawText), [options.apiKey]),
359
+ message: redactSecrets({ input: extractAnthropicErrorDetail({ rawText: rawText }) }, { exactSecrets: [options.apiKey] }),
358
360
  code: String(response.status),
359
361
  });
360
362
  emitEnd('error');
@@ -376,24 +378,24 @@ async function runSingleAnthropicRequest(options, messages, emitEnd) {
376
378
  }
377
379
  onEvent({ type: 'status', label: 'requesting' });
378
380
  const state = {
379
- guard: createRoleMarkerGuard('anthropic-turn'),
381
+ guard: createRoleMarkerGuard({ messageId: 'anthropic-turn' }),
380
382
  blocks: new Map(),
381
383
  toolCalls: [],
382
384
  fullText: '',
383
385
  stopReason: null,
384
386
  usage: null,
385
387
  };
386
- for await (const frame of decodeSseStream(body)) {
388
+ for await (const frame of decodeSseStream({ source: body })) {
387
389
  // No `isEnded()` re-check at the top of this loop: every `emitEnd(...)` call site below is
388
390
  // immediately followed by `break`, so `ended` can never be true when a new iteration starts
389
391
  // — traced across all six call sites in this function and its handlers (the four pre-stream
390
392
  // early returns inside `openAnthropicResponseStream` plus the two in-loop
391
393
  // contamination/error branches below). Verified, not assumed; see
392
394
  // `__tests__/anthropic-messages.test.ts`'s duplicate-end-event regression case.
393
- const data = parseAnthropicSseData(frame.data);
395
+ const data = parseAnthropicSseData({ raw: frame.data });
394
396
  if (!data)
395
397
  continue; // malformed/empty keep-alive frame, or a non-object payload
396
- const handler = ANTHROPIC_FRAME_HANDLERS[anthropicFrameKind(data, frame.event)];
398
+ const handler = ANTHROPIC_FRAME_HANDLERS[anthropicFrameKind({ data: data, frameEvent: frame.event })];
397
399
  if (!handler)
398
400
  continue; // `message_start`, `message_stop`, `ping`, or a future event type — nothing to do
399
401
  const outcome = handler(state, data, onEvent, options.apiKey);
@@ -422,25 +424,25 @@ async function runSingleAnthropicRequest(options, messages, emitEnd) {
422
424
  * "stop" conditions (no/empty tool_use, no executor, turn ceiling reached) are exercised as plain
423
425
  * value-in/value-out assertions rather than through the full request/event-emission machinery.
424
426
  */
425
- export function anthropicLoopExitReason(outcome, toolTurns, maxToolTurns) {
427
+ export function anthropicLoopExitReason({ outcome, toolTurns, maxToolTurns }) {
426
428
  if (outcome.stopReason !== 'tool_use' || outcome.toolCalls.length === 0)
427
429
  return 'stop';
428
430
  if (toolTurns >= maxToolTurns)
429
431
  return 'max_tool_turns';
430
432
  return null;
431
433
  }
432
- export function buildAnthropicAssistantContent(text, toolCalls) {
434
+ export function buildAnthropicAssistantContent({ text, toolCalls }) {
433
435
  return [
434
436
  ...(text ? [{ type: 'text', text }] : []),
435
437
  ...toolCalls.map((call) => ({ type: 'tool_use', id: call.id, name: call.name, input: call.input })),
436
438
  ];
437
439
  }
438
440
  /** Runs every pending tool call in order and reduces the results into the `tool_result` blocks the next request's `user` message carries. Emits one `tool_result` event per call as it goes. */
439
- export async function executeAnthropicToolCalls(executeTool, calls, onEvent) {
441
+ export async function executeAnthropicToolCalls({ executeTool, calls, onEvent }) {
440
442
  const toolResultBlocks = [];
441
443
  for (const call of calls) {
442
444
  const rawResult = await executeTool(call);
443
- const result = guardToolResult(rawResult);
445
+ const result = guardToolResult({ result: rawResult });
444
446
  onEvent({ type: 'tool_result', toolUseId: call.id, content: result.content, isError: result.isError ?? false });
445
447
  toolResultBlocks.push({
446
448
  type: 'tool_result',
@@ -451,7 +453,8 @@ export async function executeAnthropicToolCalls(executeTool, calls, onEvent) {
451
453
  }
452
454
  return toolResultBlocks;
453
455
  }
454
- export async function runAnthropicToolTurn(options) {
456
+ export async function runAnthropicToolTurn(requiredArgs, optionalArgs = {}) {
457
+ const options = { ...optionalArgs, ...requiredArgs };
455
458
  const maxToolTurns = options.maxToolTurns ?? DEFAULT_MAX_TOOL_TURNS;
456
459
  const executeTool = options.executeTool;
457
460
  const endGuard = createTurnEndGuard(options.onEvent, (reason) => ({ type: 'end', reason }));
@@ -464,7 +467,7 @@ export async function runAnthropicToolTurn(options) {
464
467
  lastStopReason = outcome.stopReason;
465
468
  if (endGuard.hasEnded())
466
469
  break;
467
- const exitReason = anthropicLoopExitReason(outcome, toolTurns, maxToolTurns);
470
+ const exitReason = anthropicLoopExitReason({ outcome: outcome, toolTurns: toolTurns, maxToolTurns: maxToolTurns });
468
471
  if (exitReason) {
469
472
  emitEnd(exitReason);
470
473
  break;
@@ -477,8 +480,8 @@ export async function runAnthropicToolTurn(options) {
477
480
  break;
478
481
  }
479
482
  toolTurns += 1;
480
- const assistantContent = buildAnthropicAssistantContent(outcome.text, outcome.toolCalls);
481
- const toolResultBlocks = await executeAnthropicToolCalls(executeTool, outcome.toolCalls, options.onEvent);
483
+ const assistantContent = buildAnthropicAssistantContent({ text: outcome.text, toolCalls: outcome.toolCalls });
484
+ const toolResultBlocks = await executeAnthropicToolCalls({ executeTool: executeTool, calls: outcome.toolCalls, onEvent: options.onEvent });
482
485
  messages = [
483
486
  ...messages,
484
487
  { role: 'assistant', content: assistantContent },
@@ -17,14 +17,14 @@
17
17
  * to `'2024-10-21'`, the default this repo's real OD predecessor used for
18
18
  * its own azure chat-completions proxy route (`apps/daemon/src/routes/
19
19
  * chat.ts`'s `[proxy:azure]` handler, confirmed by reading that file
20
- * directly in a sibling checkout — see `source-map.md`'s dated entry).
20
+ * directly in a sibling checkout — see `archived provenance ledger`'s dated entry).
21
21
  * That source additionally branches on whether `baseUrl` already contains
22
22
  * a versioned `/openai/v1` path (a newer Azure OpenAI API preview); that
23
23
  * branch is deliberately not carried forward here — this adapter targets
24
24
  * the one documented, stable URL/body shape per this task's approved scope
25
25
  * ("byte-identical to OpenAI, only URL/auth differ"). Its 400-retry
26
26
  * behavior IS carried forward (see "Token-limit fix" below) — round-4
27
- * external audit (`AUD-R4-001`) found the first port had dropped it,
27
+ * external audit found the first port had dropped it,
28
28
  * which fails every request against a GPT-5/o-series Azure deployment.
29
29
  *
30
30
  * **Auth**: `api-key: {apiKey}` header — NOT `Authorization: Bearer`,
@@ -70,6 +70,7 @@
70
70
  * follow-up (never interleaved — a real 400 otherwise), same per-image attribution label naming
71
71
  * the tool call it answers. See that module's doc for the full rationale; it is not repeated here
72
72
  * beyond this pointer to avoid the two copies drifting.
73
+ * See docs/decisions/DR-001-provider-native-tool-lifecycle.md.
73
74
  */
74
75
  import { type DnsLookupFn, type PinnedFetch } from './connection-guard.js';
75
76
  import { type OpenAiCompatibleRequestOutcome } from './openai-chat.js';
@@ -188,22 +189,35 @@ export interface AzureTurnResult {
188
189
  * instead proceed to execute the pending tool calls — mirrors
189
190
  * `anthropic-messages.ts#anthropicLoopExitReason`'s pure decision/effect split.
190
191
  */
191
- export declare function azureLoopExitReason(outcome: OpenAiCompatibleRequestOutcome, toolTurns: number, maxToolTurns: number): AzureTurnEndReason | null;
192
+ export declare function azureLoopExitReason({ outcome, toolTurns, maxToolTurns }: {
193
+ outcome: OpenAiCompatibleRequestOutcome;
194
+ toolTurns: number;
195
+ maxToolTurns: number;
196
+ }): AzureTurnEndReason | null;
192
197
  /** Builds the assistant continuation's `tool_calls` field — same shape as `openai-chat.ts#buildOpenAiAssistantToolCallMessage`'s `tool_calls`, since Azure's wire body is byte-identical to plain OpenAI's (see module doc). */
193
- export declare function buildAzureAssistantToolCalls(toolCalls: readonly AzureToolCall[]): AzureToolCallParam[];
198
+ export declare function buildAzureAssistantToolCalls({ toolCalls }: {
199
+ toolCalls: readonly AzureToolCall[];
200
+ }): AzureToolCallParam[];
194
201
  export interface AzureToolExecutionOutcome {
195
202
  readonly toolResultMessages: AzureMessageParam[];
196
203
  readonly followUpParts: AzureContentPart[];
197
204
  }
198
205
  /** Azure counterpart of `openai-chat.ts#executeOpenAiToolCalls` — same split (text-only `tool` message plus one labeled-image follow-up per batch) and same per-batch assembly discipline; see that function's doc and module doc's "synthetic follow-up message" note for why. */
199
- export declare function executeAzureToolCalls(executeTool: AzureToolExecutor, calls: readonly AzureToolCall[], onEvent: (event: AzureTurnEvent) => void): Promise<AzureToolExecutionOutcome>;
206
+ export declare function executeAzureToolCalls({ executeTool, calls, onEvent }: {
207
+ executeTool: AzureToolExecutor;
208
+ calls: readonly AzureToolCall[];
209
+ onEvent: (event: AzureTurnEvent) => void;
210
+ }): Promise<AzureToolExecutionOutcome>;
200
211
  /** Azure counterpart of `openai-chat.ts#buildOpenAiToolExchangeMessages` — appends the batch's `tool` messages plus, when present, the single labeled-image follow-up message. */
201
- export declare function buildAzureToolExchangeMessages(toolResultMessages: readonly AzureMessageParam[], followUpParts: readonly AzureContentPart[]): AzureMessageParam[];
212
+ export declare function buildAzureToolExchangeMessages({ toolResultMessages, followUpParts }: {
213
+ toolResultMessages: readonly AzureMessageParam[];
214
+ followUpParts: readonly AzureContentPart[];
215
+ }): AzureMessageParam[];
202
216
  /**
203
217
  * Runs a full Azure OpenAI Chat Completions turn, including the
204
218
  * tool-execution loop when `options.executeTool` is supplied and the model
205
219
  * requests a function call. See `anthropic-messages.ts#runAnthropicToolTurn`'s
206
220
  * doc for the shared event-stream/`ended`-flag contract this mirrors exactly.
207
221
  */
208
- export declare function runAzureToolTurn(options: AzureTurnOptions): Promise<AzureTurnResult>;
222
+ export declare function runAzureToolTurn(requiredArgs: Pick<AzureTurnOptions, "apiKey" | "baseUrl" | "model" | "messages" | "onEvent">, optionalArgs?: Omit<AzureTurnOptions, "apiKey" | "baseUrl" | "model" | "messages" | "onEvent">): Promise<AzureTurnResult>;
209
223
  //# sourceMappingURL=azure-chat.d.ts.map
@@ -17,14 +17,14 @@
17
17
  * to `'2024-10-21'`, the default this repo's real OD predecessor used for
18
18
  * its own azure chat-completions proxy route (`apps/daemon/src/routes/
19
19
  * chat.ts`'s `[proxy:azure]` handler, confirmed by reading that file
20
- * directly in a sibling checkout — see `source-map.md`'s dated entry).
20
+ * directly in a sibling checkout — see `archived provenance ledger`'s dated entry).
21
21
  * That source additionally branches on whether `baseUrl` already contains
22
22
  * a versioned `/openai/v1` path (a newer Azure OpenAI API preview); that
23
23
  * branch is deliberately not carried forward here — this adapter targets
24
24
  * the one documented, stable URL/body shape per this task's approved scope
25
25
  * ("byte-identical to OpenAI, only URL/auth differ"). Its 400-retry
26
26
  * behavior IS carried forward (see "Token-limit fix" below) — round-4
27
- * external audit (`AUD-R4-001`) found the first port had dropped it,
27
+ * external audit found the first port had dropped it,
28
28
  * which fails every request against a GPT-5/o-series Azure deployment.
29
29
  *
30
30
  * **Auth**: `api-key: {apiKey}` header — NOT `Authorization: Bearer`,
@@ -70,6 +70,7 @@
70
70
  * follow-up (never interleaved — a real 400 otherwise), same per-image attribution label naming
71
71
  * the tool call it answers. See that module's doc for the full rationale; it is not repeated here
72
72
  * beyond this pointer to avoid the two copies drifting.
73
+ * See docs/decisions/DR-001-provider-native-tool-lifecycle.md.
73
74
  */
74
75
  import { defaultDnsLookup, validateBaseUrlResolved } from './connection-guard.js';
75
76
  import { runOpenAiCompatibleRequest } from './openai-chat.js';
@@ -107,19 +108,19 @@ function azureRequestBody(options, messages, tokenParam) {
107
108
  }
108
109
  /** Validates `options.baseUrl`, then delegates to `openai-chat.ts#runOpenAiCompatibleRequest` with Azure's own URL/header/body builders. */
109
110
  async function runSingleAzureRequest(options, messages, emitEnd, hasEnded) {
110
- const baseUrlCheck = await validateBaseUrlResolved(options.baseUrl, options.dnsLookup ?? defaultDnsLookup);
111
+ const baseUrlCheck = await validateBaseUrlResolved({ baseUrl: options.baseUrl, lookup: options.dnsLookup ?? defaultDnsLookup });
111
112
  if (baseUrlCheck.error) {
112
113
  options.onEvent({ type: 'error', message: baseUrlCheck.error });
113
114
  emitEnd('error');
114
115
  return { finishReason: null, toolCalls: [], text: '' };
115
116
  }
116
117
  const maxTokens = effectiveAzureMaxTokens(options);
117
- return runOpenAiCompatibleRequest({
118
+ return (({ url, headers, body, redactSecretsList, guardMessageId, providerLabel, onEvent, emitEnd, hasEnded, ...optionalArgs }) => runOpenAiCompatibleRequest({ url, headers, body, redactSecretsList, guardMessageId, providerLabel, onEvent, emitEnd, hasEnded }, optionalArgs))({
118
119
  url: azureRequestUrl(options.baseUrl, options.model, options.apiVersion),
119
120
  headers: azureHeaders(options),
120
- body: azureRequestBody(options, messages, buildLegacyMaxTokensParam(maxTokens)),
121
- retryableBody: (status, rawErrorText) => status === 400 && isUnsupportedMaxTokensError(rawErrorText)
122
- ? azureRequestBody(options, messages, buildMaxCompletionTokensParam(maxTokens))
121
+ body: azureRequestBody(options, messages, buildLegacyMaxTokensParam({ maxTokens: maxTokens })),
122
+ retryableBody: (status, rawErrorText) => status === 400 && isUnsupportedMaxTokensError({ detail: rawErrorText })
123
+ ? azureRequestBody(options, messages, buildMaxCompletionTokensParam({ maxTokens: maxTokens }))
123
124
  : null,
124
125
  ...(baseUrlCheck.pinnedAddress ? { pinnedAddress: baseUrlCheck.pinnedAddress } : {}),
125
126
  ...(options.signal ? { signal: options.signal } : {}),
@@ -182,7 +183,7 @@ function splitAzureToolResultContent(content) {
182
183
  * instead proceed to execute the pending tool calls — mirrors
183
184
  * `anthropic-messages.ts#anthropicLoopExitReason`'s pure decision/effect split.
184
185
  */
185
- export function azureLoopExitReason(outcome, toolTurns, maxToolTurns) {
186
+ export function azureLoopExitReason({ outcome, toolTurns, maxToolTurns }) {
186
187
  if (outcome.finishReason !== 'tool_calls' || outcome.toolCalls.length === 0)
187
188
  return 'stop';
188
189
  if (toolTurns >= maxToolTurns)
@@ -190,11 +191,11 @@ export function azureLoopExitReason(outcome, toolTurns, maxToolTurns) {
190
191
  return null;
191
192
  }
192
193
  /** Builds the assistant continuation's `tool_calls` field — same shape as `openai-chat.ts#buildOpenAiAssistantToolCallMessage`'s `tool_calls`, since Azure's wire body is byte-identical to plain OpenAI's (see module doc). */
193
- export function buildAzureAssistantToolCalls(toolCalls) {
194
+ export function buildAzureAssistantToolCalls({ toolCalls }) {
194
195
  return toolCalls.map((call) => ({ id: call.id, type: 'function', function: { name: call.name, arguments: JSON.stringify(call.input) } }));
195
196
  }
196
197
  /** Azure counterpart of `openai-chat.ts#executeOpenAiToolCalls` — same split (text-only `tool` message plus one labeled-image follow-up per batch) and same per-batch assembly discipline; see that function's doc and module doc's "synthetic follow-up message" note for why. */
197
- export async function executeAzureToolCalls(executeTool, calls, onEvent) {
198
+ export async function executeAzureToolCalls({ executeTool, calls, onEvent }) {
198
199
  const toolResultMessages = [];
199
200
  const followUpParts = [];
200
201
  for (const call of calls) {
@@ -212,7 +213,7 @@ export async function executeAzureToolCalls(executeTool, calls, onEvent) {
212
213
  return { toolResultMessages, followUpParts };
213
214
  }
214
215
  /** Azure counterpart of `openai-chat.ts#buildOpenAiToolExchangeMessages` — appends the batch's `tool` messages plus, when present, the single labeled-image follow-up message. */
215
- export function buildAzureToolExchangeMessages(toolResultMessages, followUpParts) {
216
+ export function buildAzureToolExchangeMessages({ toolResultMessages, followUpParts }) {
216
217
  return [...toolResultMessages, ...(followUpParts.length > 0 ? [{ role: 'user', content: followUpParts }] : [])];
217
218
  }
218
219
  /**
@@ -221,7 +222,8 @@ export function buildAzureToolExchangeMessages(toolResultMessages, followUpParts
221
222
  * requests a function call. See `anthropic-messages.ts#runAnthropicToolTurn`'s
222
223
  * doc for the shared event-stream/`ended`-flag contract this mirrors exactly.
223
224
  */
224
- export async function runAzureToolTurn(options) {
225
+ export async function runAzureToolTurn(requiredArgs, optionalArgs = {}) {
226
+ const options = { ...optionalArgs, ...requiredArgs };
225
227
  const maxToolTurns = options.maxToolTurns ?? DEFAULT_MAX_TOOL_TURNS;
226
228
  const executeTool = options.executeTool;
227
229
  const endGuard = createTurnEndGuard(options.onEvent, (reason) => ({ type: 'end', reason }));
@@ -234,7 +236,7 @@ export async function runAzureToolTurn(options) {
234
236
  lastFinishReason = outcome.finishReason;
235
237
  if (endGuard.hasEnded())
236
238
  break;
237
- const exitReason = azureLoopExitReason(outcome, toolTurns, maxToolTurns);
239
+ const exitReason = azureLoopExitReason({ outcome: outcome, toolTurns: toolTurns, maxToolTurns: maxToolTurns });
238
240
  if (exitReason) {
239
241
  emitEnd(exitReason);
240
242
  break;
@@ -244,12 +246,12 @@ export async function runAzureToolTurn(options) {
244
246
  break;
245
247
  }
246
248
  toolTurns += 1;
247
- const assistantToolCalls = buildAzureAssistantToolCalls(outcome.toolCalls);
248
- const { toolResultMessages, followUpParts } = await executeAzureToolCalls(executeTool, outcome.toolCalls, options.onEvent);
249
+ const assistantToolCalls = buildAzureAssistantToolCalls({ toolCalls: outcome.toolCalls });
250
+ const { toolResultMessages, followUpParts } = await executeAzureToolCalls({ executeTool: executeTool, calls: outcome.toolCalls, onEvent: options.onEvent });
249
251
  messages = [
250
252
  ...messages,
251
253
  { role: 'assistant', content: outcome.text || null, tool_calls: assistantToolCalls },
252
- ...buildAzureToolExchangeMessages(toolResultMessages, followUpParts),
254
+ ...buildAzureToolExchangeMessages({ toolResultMessages: toolResultMessages, followUpParts: followUpParts }),
253
255
  ];
254
256
  }
255
257
  return { finishReason: lastFinishReason, toolTurns };