@jini-ai/agent-runtime 0.3.10 → 0.4.1

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 +108 -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 +94 -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 +258 -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
@@ -178,10 +178,17 @@ export interface OpenAiTurnResult {
178
178
  }
179
179
  /** Matches OD's own default (`apps/daemon/src/routes/chat.ts`'s openai/azure handlers both fall back to this when the caller doesn't supply one) — a real, explicit bound is always sent, never an unbounded request. */
180
180
  export declare const DEFAULT_OPENAI_MAX_TOKENS = 8192;
181
- export declare function openAiRequestUrl(baseUrl: string | undefined): string;
182
- export declare function openAiHeaders(options: OpenAiTurnOptions): Record<string, string>;
183
- export declare function openAiRequestBody(options: OpenAiTurnOptions, messages: readonly OpenAiMessageParam[]): Record<string, unknown>;
184
- export declare function extractOpenAiErrorDetail(rawText: string): string;
181
+ export declare function openAiRequestUrl({ baseUrl }: {
182
+ baseUrl: string | undefined;
183
+ }): string;
184
+ export declare function openAiHeaders(requiredArgs: Pick<OpenAiTurnOptions, "apiKey" | "model" | "messages" | "onEvent">, optionalArgs?: Omit<OpenAiTurnOptions, "apiKey" | "model" | "messages" | "onEvent">): Record<string, string>;
185
+ export declare function openAiRequestBody({ options, messages }: {
186
+ options: OpenAiTurnOptions;
187
+ messages: readonly OpenAiMessageParam[];
188
+ }): Record<string, unknown>;
189
+ export declare function extractOpenAiErrorDetail({ rawText }: {
190
+ rawText: string;
191
+ }): string;
185
192
  export interface PendingToolCall {
186
193
  id: string;
187
194
  name: string;
@@ -233,24 +240,48 @@ export interface OpenAiStreamState {
233
240
  usage: Record<string, unknown> | null;
234
241
  }
235
242
  /** 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". */
236
- export declare function parseOpenAiSseData(raw: string): Record<string, unknown> | null;
237
- export declare function applyOpenAiStreamUsage(state: OpenAiStreamState, data: Record<string, unknown>, onEvent: (event: OpenAiTurnEvent) => void): void;
243
+ export declare function parseOpenAiSseData({ raw }: {
244
+ raw: string;
245
+ }): Record<string, unknown> | null;
246
+ export declare function applyOpenAiStreamUsage({ state, data, onEvent }: {
247
+ state: OpenAiStreamState;
248
+ data: Record<string, unknown>;
249
+ onEvent: (event: OpenAiTurnEvent) => void;
250
+ }): void;
238
251
  /** The chunk's first (and, for Chat Completions, only) choice — `null` when the chunk carries no choice at all (e.g. a usage-only trailer chunk). */
239
- export declare function firstOpenAiChoice(data: Record<string, unknown>): Record<string, unknown> | null;
252
+ export declare function firstOpenAiChoice({ data }: {
253
+ data: Record<string, unknown>;
254
+ }): Record<string, unknown> | null;
240
255
  /**
241
256
  * Feeds one text delta through the role-marker guard and emits the safe portion. Returns
242
257
  * `'break'` once the guard flags contamination (the caller ends the turn immediately), otherwise
243
258
  * `'continue'` — same contract as `anthropic-messages.ts#handleAnthropicTextDelta`.
244
259
  */
245
- export declare function handleOpenAiTextContentDelta(state: OpenAiStreamState, content: string, onEvent: (event: OpenAiTurnEvent) => void): 'continue' | 'break';
260
+ export declare function handleOpenAiTextContentDelta({ state, content, onEvent }: {
261
+ state: OpenAiStreamState;
262
+ content: string;
263
+ onEvent: (event: OpenAiTurnEvent) => void;
264
+ }): 'continue' | 'break';
246
265
  /** Builds a fresh `PendingToolCall` from the streaming chunk that first mentions a given tool-call index — OpenAI sends `id`/`function.name` once, on that first chunk, then dribbles `function.arguments` in across subsequent chunks (accumulated separately by the caller). */
247
- export declare function newPendingOpenAiToolCall(rawCall: Record<string, unknown>, index: number): PendingToolCall;
266
+ export declare function newPendingOpenAiToolCall({ rawCall, index }: {
267
+ rawCall: Record<string, unknown>;
268
+ index: number;
269
+ }): PendingToolCall;
248
270
  /** Accumulates one `delta.tool_calls[]` entry from a streaming chunk into its running `PendingToolCall`. */
249
- export declare function accumulateOpenAiToolCallDelta(state: OpenAiStreamState, rawCall: unknown): void;
271
+ export declare function accumulateOpenAiToolCallDelta({ state, rawCall }: {
272
+ state: OpenAiStreamState;
273
+ rawCall: unknown;
274
+ }): void;
250
275
  /** Reduces one chunk's `choices[0]` — `finish_reason`, text content, and tool-call deltas — into `state`. Returns `'break'` when the text-delta guard detects contamination. */
251
- export declare function handleOpenAiChoiceDelta(state: OpenAiStreamState, choice: Record<string, unknown>, onEvent: (event: OpenAiTurnEvent) => void): 'continue' | 'break';
276
+ export declare function handleOpenAiChoiceDelta({ state, choice, onEvent }: {
277
+ state: OpenAiStreamState;
278
+ choice: Record<string, unknown>;
279
+ onEvent: (event: OpenAiTurnEvent) => void;
280
+ }): 'continue' | 'break';
252
281
  /** Parses one accumulated tool call's `argsJson`, falling back to `{}` for empty or malformed JSON — mirrors Anthropic's identical fallback for `input_json_delta` accumulation in `anthropic-messages.ts`. */
253
- export declare function resolveOpenAiToolCalls(pending: ReadonlyMap<number, PendingToolCall>): OpenAiToolCall[];
282
+ export declare function resolveOpenAiToolCalls({ pending }: {
283
+ pending: ReadonlyMap<number, PendingToolCall>;
284
+ }): OpenAiToolCall[];
254
285
  export type OpenAiFrameResult = 'continue' | 'done' | 'contaminated';
255
286
  /**
256
287
  * Reduces one decoded SSE frame into `state`, reporting what the caller's loop should do next:
@@ -259,7 +290,11 @@ export type OpenAiFrameResult = 'continue' | 'done' | 'contaminated';
259
290
  * turns what would be four sequential, nesting-penalized `if`s into a single function call plus
260
291
  * one outcome check — see `runOpenAiCompatibleRequest`'s loop below.
261
292
  */
262
- export declare function processOpenAiStreamFrame(state: OpenAiStreamState, frame: DecodedSseEvent, onEvent: (event: OpenAiTurnEvent) => void): OpenAiFrameResult;
293
+ export declare function processOpenAiStreamFrame({ state, frame, onEvent }: {
294
+ state: OpenAiStreamState;
295
+ frame: DecodedSseEvent;
296
+ onEvent: (event: OpenAiTurnEvent) => void;
297
+ }): OpenAiFrameResult;
263
298
  /**
264
299
  * Runs exactly one OpenAI-compatible (Chat Completions JSON wire format)
265
300
  * streaming HTTP request and reduces its SSE events into a single outcome.
@@ -275,7 +310,7 @@ export declare function processOpenAiStreamFrame(state: OpenAiStreamState, frame
275
310
  * `anthropic-messages.ts#runSingleAnthropicRequest`'s `emitEnd` contract —
276
311
  * see that function's doc.
277
312
  */
278
- export declare function runOpenAiCompatibleRequest(init: OpenAiCompatibleRequestInit): Promise<OpenAiCompatibleRequestOutcome>;
313
+ export declare function runOpenAiCompatibleRequest(requiredArgs: Pick<OpenAiCompatibleRequestInit, "url" | "headers" | "body" | "redactSecretsList" | "guardMessageId" | "providerLabel" | "onEvent" | "emitEnd" | "hasEnded">, optionalArgs?: Omit<OpenAiCompatibleRequestInit, "url" | "headers" | "body" | "redactSecretsList" | "guardMessageId" | "providerLabel" | "onEvent" | "emitEnd" | "hasEnded">): Promise<OpenAiCompatibleRequestOutcome>;
279
314
  /**
280
315
  * Validates one content part against OpenAI's real format/size constraints. Returns the violation
281
316
  * reason, or `null` when the part is legal to send.
@@ -288,7 +323,9 @@ export declare function runOpenAiCompatibleRequest(init: OpenAiCompatibleRequest
288
323
  *
289
324
  * @complexity O(1) — reads `data.length`, never decodes or parses the base64 payload.
290
325
  */
291
- export declare function invalidOpenAiContentPartReason(part: OpenAiContentPart): string | null;
326
+ export declare function invalidOpenAiContentPartReason({ part }: {
327
+ part: OpenAiContentPart;
328
+ }): string | null;
292
329
  export interface SanitizedOpenAiToolResult {
293
330
  readonly content: string | readonly OpenAiContentPart[];
294
331
  readonly isError: boolean;
@@ -306,7 +343,9 @@ export interface SanitizedOpenAiToolResult {
306
343
  *
307
344
  * @complexity O(n) in the number of content parts; O(1) per part (see `invalidOpenAiContentPartReason`).
308
345
  */
309
- export declare function sanitizeOpenAiToolResult(result: OpenAiToolResult): SanitizedOpenAiToolResult;
346
+ export declare function sanitizeOpenAiToolResult({ result }: {
347
+ result: OpenAiToolResult;
348
+ }): SanitizedOpenAiToolResult;
310
349
  export interface SplitOpenAiToolResultContent {
311
350
  readonly toolMessageContent: string | readonly OpenAiTextPart[];
312
351
  readonly imageParts: readonly OpenAiImageUrlPart[];
@@ -323,13 +362,19 @@ export interface SplitOpenAiToolResultContent {
323
362
  *
324
363
  * @complexity O(n) in the number of content parts.
325
364
  */
326
- export declare function splitOpenAiToolResultContent(content: string | readonly OpenAiContentPart[]): SplitOpenAiToolResultContent;
365
+ export declare function splitOpenAiToolResultContent({ content }: {
366
+ content: string | readonly OpenAiContentPart[];
367
+ }): SplitOpenAiToolResultContent;
327
368
  /**
328
369
  * Decides why the tool loop should stop after one request, or returns `null` when it should
329
370
  * instead proceed to execute the pending tool calls — mirrors
330
371
  * `anthropic-messages.ts#anthropicLoopExitReason`'s pure decision/effect split.
331
372
  */
332
- export declare function openAiLoopExitReason(outcome: OpenAiCompatibleRequestOutcome, toolTurns: number, maxToolTurns: number): OpenAiTurnEndReason | null;
373
+ export declare function openAiLoopExitReason({ outcome, toolTurns, maxToolTurns }: {
374
+ outcome: OpenAiCompatibleRequestOutcome;
375
+ toolTurns: number;
376
+ maxToolTurns: number;
377
+ }): OpenAiTurnEndReason | null;
333
378
  export interface OpenAiToolExecutionOutcome {
334
379
  readonly toolResultMessages: OpenAiMessageParam[];
335
380
  readonly followUpParts: OpenAiContentPart[];
@@ -340,16 +385,26 @@ export interface OpenAiToolExecutionOutcome {
340
385
  * module doc's "Tool messages cannot carry an image" section for why the split exists and why the
341
386
  * follow-up is assembled once per batch rather than per call.
342
387
  */
343
- export declare function executeOpenAiToolCalls(executeTool: OpenAiToolExecutor, calls: readonly OpenAiToolCall[], onEvent: (event: OpenAiTurnEvent) => void): Promise<OpenAiToolExecutionOutcome>;
388
+ export declare function executeOpenAiToolCalls({ executeTool, calls, onEvent }: {
389
+ executeTool: OpenAiToolExecutor;
390
+ calls: readonly OpenAiToolCall[];
391
+ onEvent: (event: OpenAiTurnEvent) => void;
392
+ }): Promise<OpenAiToolExecutionOutcome>;
344
393
  /** Builds the assistant turn that records the model's pending tool calls in `messages` history — `content` falls back to `null` (never `''`) per the wire schema. */
345
- export declare function buildOpenAiAssistantToolCallMessage(text: string, toolCalls: readonly OpenAiToolCallParam[]): OpenAiMessageParam;
394
+ export declare function buildOpenAiAssistantToolCallMessage({ text, toolCalls }: {
395
+ text: string;
396
+ toolCalls: readonly OpenAiToolCallParam[];
397
+ }): OpenAiMessageParam;
346
398
  /** Appends the batch's `tool` messages plus, when present, the single labeled-image follow-up message — see module doc's "Tool messages cannot carry an image" section for why the follow-up is at most one message per batch. */
347
- export declare function buildOpenAiToolExchangeMessages(toolResultMessages: readonly OpenAiMessageParam[], followUpParts: readonly OpenAiContentPart[]): OpenAiMessageParam[];
399
+ export declare function buildOpenAiToolExchangeMessages({ toolResultMessages, followUpParts }: {
400
+ toolResultMessages: readonly OpenAiMessageParam[];
401
+ followUpParts: readonly OpenAiContentPart[];
402
+ }): OpenAiMessageParam[];
348
403
  /**
349
404
  * Runs a full OpenAI Chat Completions turn, including the tool-execution
350
405
  * loop when `options.executeTool` is supplied and the model requests a
351
406
  * function call. See `anthropic-messages.ts#runAnthropicToolTurn`'s doc for
352
407
  * the shared event-stream/`ended`-flag contract this mirrors exactly.
353
408
  */
354
- export declare function runOpenAiToolTurn(options: OpenAiTurnOptions): Promise<OpenAiTurnResult>;
409
+ export declare function runOpenAiToolTurn(requiredArgs: Pick<OpenAiTurnOptions, "apiKey" | "model" | "messages" | "onEvent">, optionalArgs?: Omit<OpenAiTurnOptions, "apiKey" | "model" | "messages" | "onEvent">): Promise<OpenAiTurnResult>;
355
410
  //# sourceMappingURL=openai-chat.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { redactSecrets } from '@jini-ai/core';
1
2
  /**
2
3
  * @module providers/openai-chat
3
4
  *
@@ -67,7 +68,7 @@
67
68
  * reasoning.
68
69
  */
69
70
  import { createRoleMarkerGuard } from '../role-marker-guard.js';
70
- import { defaultDnsLookup, pinnedFetch, redactSecrets, validateBaseUrlResolved } from './connection-guard.js';
71
+ import { defaultDnsLookup, pinnedFetch, validateBaseUrlResolved } from './connection-guard.js';
71
72
  import { decodeSseStream } from './sse-decode.js';
72
73
  import { buildOpenAIChatTokenParam } from './token-params.js';
73
74
  import { createTurnEndGuard } from './turn-end-guard.js';
@@ -79,30 +80,31 @@ export const DEFAULT_OPENAI_MAX_TOKENS = 8192;
79
80
  function isRecord(value) {
80
81
  return typeof value === 'object' && value !== null && !Array.isArray(value);
81
82
  }
82
- export function openAiRequestUrl(baseUrl) {
83
+ export function openAiRequestUrl({ baseUrl }) {
83
84
  const base = (baseUrl ?? DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, '');
84
85
  return /\/v\d+(\/|$)/.test(base) ? `${base}/chat/completions` : `${base}/v1/chat/completions`;
85
86
  }
86
- export function openAiHeaders(options) {
87
+ export function openAiHeaders(requiredArgs, optionalArgs = {}) {
88
+ const options = { ...optionalArgs, ...requiredArgs };
87
89
  return {
88
90
  'content-type': 'application/json',
89
91
  authorization: `Bearer ${options.apiKey}`,
90
92
  ...(options.extraHeaders ?? {}),
91
93
  };
92
94
  }
93
- export function openAiRequestBody(options, messages) {
95
+ export function openAiRequestBody({ options, messages }) {
94
96
  const effectiveMaxTokens = typeof options.maxTokens === 'number' && options.maxTokens > 0 ? options.maxTokens : DEFAULT_OPENAI_MAX_TOKENS;
95
97
  return {
96
98
  model: options.model,
97
99
  stream: true,
98
100
  stream_options: { include_usage: true },
99
101
  messages,
100
- ...buildOpenAIChatTokenParam(options.model, effectiveMaxTokens),
102
+ ...buildOpenAIChatTokenParam({ model: options.model, maxTokens: effectiveMaxTokens }),
101
103
  ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
102
104
  ...(options.tools && options.tools.length > 0 ? { tools: options.tools } : {}),
103
105
  };
104
106
  }
105
- export function extractOpenAiErrorDetail(rawText) {
107
+ export function extractOpenAiErrorDetail({ rawText }) {
106
108
  try {
107
109
  const parsed = JSON.parse(rawText);
108
110
  if (isRecord(parsed) && isRecord(parsed.error) && typeof parsed.error.message === 'string') {
@@ -115,7 +117,7 @@ export function extractOpenAiErrorDetail(rawText) {
115
117
  return rawText.trim().slice(0, 500);
116
118
  }
117
119
  /** 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". */
118
- export function parseOpenAiSseData(raw) {
120
+ export function parseOpenAiSseData({ raw }) {
119
121
  let data;
120
122
  try {
121
123
  data = JSON.parse(raw);
@@ -125,14 +127,14 @@ export function parseOpenAiSseData(raw) {
125
127
  }
126
128
  return isRecord(data) ? data : null;
127
129
  }
128
- export function applyOpenAiStreamUsage(state, data, onEvent) {
130
+ export function applyOpenAiStreamUsage({ state, data, onEvent }) {
129
131
  if (!isRecord(data.usage))
130
132
  return;
131
133
  state.usage = data.usage;
132
134
  onEvent({ type: 'usage', usage: data.usage });
133
135
  }
134
136
  /** The chunk's first (and, for Chat Completions, only) choice — `null` when the chunk carries no choice at all (e.g. a usage-only trailer chunk). */
135
- export function firstOpenAiChoice(data) {
137
+ export function firstOpenAiChoice({ data }) {
136
138
  const choices = Array.isArray(data.choices) ? data.choices : [];
137
139
  const choice = choices[0];
138
140
  return isRecord(choice) ? choice : null;
@@ -142,8 +144,8 @@ export function firstOpenAiChoice(data) {
142
144
  * `'break'` once the guard flags contamination (the caller ends the turn immediately), otherwise
143
145
  * `'continue'` — same contract as `anthropic-messages.ts#handleAnthropicTextDelta`.
144
146
  */
145
- export function handleOpenAiTextContentDelta(state, content, onEvent) {
146
- const safe = state.guard.feedText(content);
147
+ export function handleOpenAiTextContentDelta({ state, content, onEvent }) {
148
+ const safe = state.guard.feedText({ text: content });
147
149
  if (safe.length > 0) {
148
150
  state.fullText += safe;
149
151
  onEvent({ type: 'text_delta', delta: safe });
@@ -156,19 +158,19 @@ export function handleOpenAiTextContentDelta(state, content, onEvent) {
156
158
  return 'break';
157
159
  }
158
160
  /** Builds a fresh `PendingToolCall` from the streaming chunk that first mentions a given tool-call index — OpenAI sends `id`/`function.name` once, on that first chunk, then dribbles `function.arguments` in across subsequent chunks (accumulated separately by the caller). */
159
- export function newPendingOpenAiToolCall(rawCall, index) {
161
+ export function newPendingOpenAiToolCall({ rawCall, index }) {
160
162
  const id = typeof rawCall.id === 'string' ? rawCall.id : `call_${index}`;
161
163
  const fn = isRecord(rawCall.function) ? rawCall.function : {};
162
164
  const name = typeof fn.name === 'string' ? fn.name : '';
163
165
  return { id, name, argsJson: '' };
164
166
  }
165
167
  /** Accumulates one `delta.tool_calls[]` entry from a streaming chunk into its running `PendingToolCall`. */
166
- export function accumulateOpenAiToolCallDelta(state, rawCall) {
168
+ export function accumulateOpenAiToolCallDelta({ state, rawCall }) {
167
169
  if (!isRecord(rawCall) || typeof rawCall.index !== 'number')
168
170
  return;
169
171
  let pending = state.toolCalls.get(rawCall.index);
170
172
  if (!pending) {
171
- pending = newPendingOpenAiToolCall(rawCall, rawCall.index);
173
+ pending = newPendingOpenAiToolCall({ rawCall: rawCall, index: rawCall.index });
172
174
  state.toolCalls.set(rawCall.index, pending);
173
175
  }
174
176
  const fn = isRecord(rawCall.function) ? rawCall.function : null;
@@ -177,7 +179,7 @@ export function accumulateOpenAiToolCallDelta(state, rawCall) {
177
179
  }
178
180
  }
179
181
  /** Reduces one chunk's `choices[0]` — `finish_reason`, text content, and tool-call deltas — into `state`. Returns `'break'` when the text-delta guard detects contamination. */
180
- export function handleOpenAiChoiceDelta(state, choice, onEvent) {
182
+ export function handleOpenAiChoiceDelta({ state, choice, onEvent }) {
181
183
  if (typeof choice.finish_reason === 'string') {
182
184
  state.finishReason = choice.finish_reason;
183
185
  }
@@ -185,18 +187,18 @@ export function handleOpenAiChoiceDelta(state, choice, onEvent) {
185
187
  if (!delta)
186
188
  return 'continue';
187
189
  if (typeof delta.content === 'string' && delta.content.length > 0) {
188
- if (handleOpenAiTextContentDelta(state, delta.content, onEvent) === 'break')
190
+ if (handleOpenAiTextContentDelta({ state: state, content: delta.content, onEvent: onEvent }) === 'break')
189
191
  return 'break';
190
192
  }
191
193
  if (Array.isArray(delta.tool_calls)) {
192
194
  for (const rawCall of delta.tool_calls) {
193
- accumulateOpenAiToolCallDelta(state, rawCall);
195
+ accumulateOpenAiToolCallDelta({ state: state, rawCall: rawCall });
194
196
  }
195
197
  }
196
198
  return 'continue';
197
199
  }
198
200
  /** Parses one accumulated tool call's `argsJson`, falling back to `{}` for empty or malformed JSON — mirrors Anthropic's identical fallback for `input_json_delta` accumulation in `anthropic-messages.ts`. */
199
- export function resolveOpenAiToolCalls(pending) {
201
+ export function resolveOpenAiToolCalls({ pending }) {
200
202
  return Array.from(pending.values()).map((call) => {
201
203
  let input = {};
202
204
  if (call.argsJson.trim()) {
@@ -226,26 +228,26 @@ async function requestOpenAiCompatibleStream(init) {
226
228
  const { onEvent, emitEnd } = init;
227
229
  let response;
228
230
  try {
229
- response = await (init.fetchImpl ?? pinnedFetch)(init.url, {
230
- method: 'POST',
231
- headers: init.headers,
232
- body: JSON.stringify(init.body),
233
- // The caller's SSRF check (`validateBaseUrlResolved`) only ever sees the
234
- // ORIGINAL url. Following a redirect would let a public, guard-passing
235
- // endpoint hand back a `302 -> http://169.254.169.254/...` and reach the
236
- // address the guard exists to refuse — with the provider auth headers
237
- // still attached. `model-catalog.ts`'s own fetch already refuses
238
- // redirects for the same reason; a redirecting chat-completions endpoint
239
- // is not a thing any supported provider does. `pinnedFetch` never
240
- // follows one regardless (see its doc); `redirect: 'error'` here is
241
- // self-documentation, not the mechanism.
242
- redirect: 'error',
243
- ...(init.signal ? { signal: init.signal } : {}),
244
- }, init.pinnedAddress);
231
+ response = await (init.fetchImpl ?? pinnedFetch)({ url: init.url, init: {
232
+ method: 'POST',
233
+ headers: init.headers,
234
+ body: JSON.stringify(init.body),
235
+ // The caller's SSRF check (`validateBaseUrlResolved`) only ever sees the
236
+ // ORIGINAL url. Following a redirect would let a public, guard-passing
237
+ // endpoint hand back a `302 -> http://169.254.169.254/...` and reach the
238
+ // address the guard exists to refuse — with the provider auth headers
239
+ // still attached. `model-catalog.ts`'s own fetch already refuses
240
+ // redirects for the same reason; a redirecting chat-completions endpoint
241
+ // is not a thing any supported provider does. `pinnedFetch` never
242
+ // follows one regardless (see its doc); `redirect: 'error'` here is
243
+ // self-documentation, not the mechanism.
244
+ redirect: 'error',
245
+ ...(init.signal ? { signal: init.signal } : {}),
246
+ }, pinnedAddress: init.pinnedAddress });
245
247
  }
246
248
  catch (error) {
247
249
  const message = error instanceof Error ? error.message : String(error);
248
- onEvent({ type: 'error', message: redactSecrets(message, init.redactSecretsList) });
250
+ onEvent({ type: 'error', message: redactSecrets({ input: message }, { exactSecrets: init.redactSecretsList }) });
249
251
  emitEnd('error');
250
252
  return { type: 'ended' };
251
253
  }
@@ -258,7 +260,7 @@ async function requestOpenAiCompatibleStream(init) {
258
260
  }
259
261
  onEvent({
260
262
  type: 'error',
261
- message: redactSecrets(extractOpenAiErrorDetail(rawText), init.redactSecretsList),
263
+ message: redactSecrets({ input: extractOpenAiErrorDetail({ rawText: rawText }) }, { exactSecrets: init.redactSecretsList }),
262
264
  code: String(response.status),
263
265
  });
264
266
  emitEnd('error');
@@ -278,17 +280,17 @@ async function requestOpenAiCompatibleStream(init) {
278
280
  * turns what would be four sequential, nesting-penalized `if`s into a single function call plus
279
281
  * one outcome check — see `runOpenAiCompatibleRequest`'s loop below.
280
282
  */
281
- export function processOpenAiStreamFrame(state, frame, onEvent) {
283
+ export function processOpenAiStreamFrame({ state, frame, onEvent }) {
282
284
  if (frame.data === DONE_SENTINEL)
283
285
  return 'done';
284
- const data = parseOpenAiSseData(frame.data);
286
+ const data = parseOpenAiSseData({ raw: frame.data });
285
287
  if (!data)
286
288
  return 'continue'; // malformed/empty keep-alive frame, or a non-object payload
287
- applyOpenAiStreamUsage(state, data, onEvent);
288
- const choice = firstOpenAiChoice(data);
289
+ applyOpenAiStreamUsage({ state: state, data: data, onEvent: onEvent });
290
+ const choice = firstOpenAiChoice({ data: data });
289
291
  if (!choice)
290
292
  return 'continue';
291
- return handleOpenAiChoiceDelta(state, choice, onEvent) === 'break' ? 'contaminated' : 'continue';
293
+ return handleOpenAiChoiceDelta({ state: state, choice: choice, onEvent: onEvent }) === 'break' ? 'contaminated' : 'continue';
292
294
  }
293
295
  /**
294
296
  * Runs exactly one OpenAI-compatible (Chat Completions JSON wire format)
@@ -305,24 +307,25 @@ export function processOpenAiStreamFrame(state, frame, onEvent) {
305
307
  * `anthropic-messages.ts#runSingleAnthropicRequest`'s `emitEnd` contract —
306
308
  * see that function's doc.
307
309
  */
308
- export async function runOpenAiCompatibleRequest(init) {
310
+ export async function runOpenAiCompatibleRequest(requiredArgs, optionalArgs = {}) {
311
+ const init = { ...optionalArgs, ...requiredArgs };
309
312
  const { onEvent, hasEnded } = init;
310
313
  const fetchOutcome = await requestOpenAiCompatibleStream(init);
311
314
  if (fetchOutcome.type === 'ended') {
312
315
  return { finishReason: null, toolCalls: [], text: '' };
313
316
  }
314
317
  if (fetchOutcome.type === 'retry') {
315
- return runOpenAiCompatibleRequest(fetchOutcome.retryInit);
318
+ return (({ url, headers, body, redactSecretsList, guardMessageId, providerLabel, onEvent, emitEnd, hasEnded, ...optionalArgs }) => runOpenAiCompatibleRequest({ url, headers, body, redactSecretsList, guardMessageId, providerLabel, onEvent, emitEnd, hasEnded }, optionalArgs))(fetchOutcome.retryInit);
316
319
  }
317
320
  onEvent({ type: 'status', label: 'requesting' });
318
321
  const state = {
319
- guard: createRoleMarkerGuard(init.guardMessageId),
322
+ guard: createRoleMarkerGuard({ messageId: init.guardMessageId }),
320
323
  toolCalls: new Map(),
321
324
  fullText: '',
322
325
  finishReason: null,
323
326
  usage: null,
324
327
  };
325
- for await (const frame of decodeSseStream(fetchOutcome.body)) {
328
+ for await (const frame of decodeSseStream({ source: fetchOutcome.body })) {
326
329
  // No `hasEnded()` re-check at the top of this loop: the only in-loop call to `emitEnd`
327
330
  // (contamination, below) is immediately followed by `break`, and every other call site is a
328
331
  // pre-loop early `return` — traced across all five call sites in this function and
@@ -330,7 +333,7 @@ export async function runOpenAiCompatibleRequest(init) {
330
333
  // `anthropic-messages.ts#runSingleAnthropicRequest`. `hasEnded()` is still consulted once,
331
334
  // after this loop, to decide whether pending tool_use events should still be emitted (a
332
335
  // contaminating delta can arrive on a *later* chunk than the one that set `finish_reason`).
333
- const result = processOpenAiStreamFrame(state, frame, onEvent);
336
+ const result = processOpenAiStreamFrame({ state: state, frame: frame, onEvent: onEvent });
334
337
  if (result === 'done')
335
338
  break;
336
339
  if (result === 'contaminated') {
@@ -338,7 +341,7 @@ export async function runOpenAiCompatibleRequest(init) {
338
341
  break;
339
342
  }
340
343
  }
341
- const resolvedToolCalls = resolveOpenAiToolCalls(state.toolCalls);
344
+ const resolvedToolCalls = resolveOpenAiToolCalls({ pending: state.toolCalls });
342
345
  if (state.finishReason === 'tool_calls' && !hasEnded()) {
343
346
  emitPendingOpenAiToolUseEvents(resolvedToolCalls, onEvent);
344
347
  }
@@ -350,16 +353,16 @@ async function runSingleOpenAiRequest(options, messages, emitEnd, hasEnded) {
350
353
  // so `https://internal.example.com -> 10.0.0.5` passed it and this runner connected to private
351
354
  // infrastructure on behalf of whoever supplied `baseUrl`. Matches what the Azure, Google and
352
355
  // Ollama runners in this directory already did.
353
- const baseUrlCheck = await validateBaseUrlResolved(options.baseUrl ?? DEFAULT_OPENAI_BASE_URL, options.dnsLookup ?? defaultDnsLookup);
356
+ const baseUrlCheck = await validateBaseUrlResolved({ baseUrl: options.baseUrl ?? DEFAULT_OPENAI_BASE_URL, lookup: options.dnsLookup ?? defaultDnsLookup });
354
357
  if (baseUrlCheck.error) {
355
358
  options.onEvent({ type: 'error', message: baseUrlCheck.error });
356
359
  emitEnd('error');
357
360
  return { finishReason: null, toolCalls: [], text: '' };
358
361
  }
359
- return runOpenAiCompatibleRequest({
360
- url: openAiRequestUrl(options.baseUrl),
361
- headers: openAiHeaders(options),
362
- body: openAiRequestBody(options, messages),
362
+ return (({ url, headers, body, redactSecretsList, guardMessageId, providerLabel, onEvent, emitEnd, hasEnded, ...optionalArgs }) => runOpenAiCompatibleRequest({ url, headers, body, redactSecretsList, guardMessageId, providerLabel, onEvent, emitEnd, hasEnded }, optionalArgs))({
363
+ url: openAiRequestUrl({ baseUrl: options.baseUrl }),
364
+ headers: (({ apiKey, model, messages, onEvent, ...optionalArgs }) => openAiHeaders({ apiKey, model, messages, onEvent }, optionalArgs))(options),
365
+ body: openAiRequestBody({ options: options, messages: messages }),
363
366
  ...(baseUrlCheck.pinnedAddress ? { pinnedAddress: baseUrlCheck.pinnedAddress } : {}),
364
367
  ...(options.signal ? { signal: options.signal } : {}),
365
368
  ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),
@@ -399,7 +402,7 @@ const OPENAI_DATA_URI_PATTERN = /^data:([^;,]+)(?:;charset=[^;,]+)?;base64,(.+)$
399
402
  *
400
403
  * @complexity O(1) — reads `data.length`, never decodes or parses the base64 payload.
401
404
  */
402
- export function invalidOpenAiContentPartReason(part) {
405
+ export function invalidOpenAiContentPartReason({ part }) {
403
406
  if (part.type === 'text')
404
407
  return null;
405
408
  const dataUriMatch = OPENAI_DATA_URI_PATTERN.exec(part.image_url.url);
@@ -427,14 +430,14 @@ export function invalidOpenAiContentPartReason(part) {
427
430
  *
428
431
  * @complexity O(n) in the number of content parts; O(1) per part (see `invalidOpenAiContentPartReason`).
429
432
  */
430
- export function sanitizeOpenAiToolResult(result) {
433
+ export function sanitizeOpenAiToolResult({ result }) {
431
434
  if (typeof result.content === 'string')
432
435
  return { content: result.content, isError: false };
433
436
  if (result.content.length > MAX_IMAGES_PER_OPENAI_TOOL_RESULT) {
434
437
  return { content: `tool result rejected: exceeds the ${MAX_IMAGES_PER_OPENAI_TOOL_RESULT}-image-per-request guard (${result.content.length} parts)`, isError: true };
435
438
  }
436
439
  for (const part of result.content) {
437
- const reason = invalidOpenAiContentPartReason(part);
440
+ const reason = invalidOpenAiContentPartReason({ part: part });
438
441
  if (reason)
439
442
  return { content: `tool result rejected: ${reason}`, isError: true };
440
443
  }
@@ -452,7 +455,7 @@ export function sanitizeOpenAiToolResult(result) {
452
455
  *
453
456
  * @complexity O(n) in the number of content parts.
454
457
  */
455
- export function splitOpenAiToolResultContent(content) {
458
+ export function splitOpenAiToolResultContent({ content }) {
456
459
  if (typeof content === 'string')
457
460
  return { toolMessageContent: content, imageParts: [] };
458
461
  const textParts = content.filter((part) => part.type === 'text');
@@ -465,7 +468,7 @@ export function splitOpenAiToolResultContent(content) {
465
468
  * instead proceed to execute the pending tool calls — mirrors
466
469
  * `anthropic-messages.ts#anthropicLoopExitReason`'s pure decision/effect split.
467
470
  */
468
- export function openAiLoopExitReason(outcome, toolTurns, maxToolTurns) {
471
+ export function openAiLoopExitReason({ outcome, toolTurns, maxToolTurns }) {
469
472
  if (outcome.finishReason !== 'tool_calls' || outcome.toolCalls.length === 0)
470
473
  return 'stop';
471
474
  if (toolTurns >= maxToolTurns)
@@ -478,14 +481,14 @@ export function openAiLoopExitReason(outcome, toolTurns, maxToolTurns) {
478
481
  * module doc's "Tool messages cannot carry an image" section for why the split exists and why the
479
482
  * follow-up is assembled once per batch rather than per call.
480
483
  */
481
- export async function executeOpenAiToolCalls(executeTool, calls, onEvent) {
484
+ export async function executeOpenAiToolCalls({ executeTool, calls, onEvent }) {
482
485
  const toolResultMessages = [];
483
486
  const followUpParts = [];
484
487
  for (const call of calls) {
485
488
  const result = await executeTool(call);
486
- const sanitized = sanitizeOpenAiToolResult(result);
489
+ const sanitized = sanitizeOpenAiToolResult({ result: result });
487
490
  onEvent({ type: 'tool_result', toolUseId: call.id, content: sanitized.content, isError: sanitized.isError });
488
- const split = splitOpenAiToolResultContent(sanitized.content);
491
+ const split = splitOpenAiToolResultContent({ content: sanitized.content });
489
492
  toolResultMessages.push({ role: 'tool', content: split.toolMessageContent, tool_call_id: call.id });
490
493
  if (split.imageParts.length > 0) {
491
494
  // Attribution label — without it, the model cannot tell this image apart from a human
@@ -498,11 +501,11 @@ export async function executeOpenAiToolCalls(executeTool, calls, onEvent) {
498
501
  return { toolResultMessages, followUpParts };
499
502
  }
500
503
  /** Builds the assistant turn that records the model's pending tool calls in `messages` history — `content` falls back to `null` (never `''`) per the wire schema. */
501
- export function buildOpenAiAssistantToolCallMessage(text, toolCalls) {
504
+ export function buildOpenAiAssistantToolCallMessage({ text, toolCalls }) {
502
505
  return { role: 'assistant', content: text || null, tool_calls: toolCalls };
503
506
  }
504
507
  /** Appends the batch's `tool` messages plus, when present, the single labeled-image follow-up message — see module doc's "Tool messages cannot carry an image" section for why the follow-up is at most one message per batch. */
505
- export function buildOpenAiToolExchangeMessages(toolResultMessages, followUpParts) {
508
+ export function buildOpenAiToolExchangeMessages({ toolResultMessages, followUpParts }) {
506
509
  return [...toolResultMessages, ...(followUpParts.length > 0 ? [{ role: 'user', content: followUpParts }] : [])];
507
510
  }
508
511
  /**
@@ -511,7 +514,8 @@ export function buildOpenAiToolExchangeMessages(toolResultMessages, followUpPart
511
514
  * function call. See `anthropic-messages.ts#runAnthropicToolTurn`'s doc for
512
515
  * the shared event-stream/`ended`-flag contract this mirrors exactly.
513
516
  */
514
- export async function runOpenAiToolTurn(options) {
517
+ export async function runOpenAiToolTurn(requiredArgs, optionalArgs = {}) {
518
+ const options = { ...optionalArgs, ...requiredArgs };
515
519
  const maxToolTurns = options.maxToolTurns ?? DEFAULT_MAX_TOOL_TURNS;
516
520
  const executeTool = options.executeTool;
517
521
  const endGuard = createTurnEndGuard(options.onEvent, (reason) => ({ type: 'end', reason }));
@@ -524,7 +528,7 @@ export async function runOpenAiToolTurn(options) {
524
528
  lastFinishReason = outcome.finishReason;
525
529
  if (endGuard.hasEnded())
526
530
  break;
527
- const exitReason = openAiLoopExitReason(outcome, toolTurns, maxToolTurns);
531
+ const exitReason = openAiLoopExitReason({ outcome: outcome, toolTurns: toolTurns, maxToolTurns: maxToolTurns });
528
532
  if (exitReason) {
529
533
  emitEnd(exitReason);
530
534
  break;
@@ -539,11 +543,11 @@ export async function runOpenAiToolTurn(options) {
539
543
  type: 'function',
540
544
  function: { name: call.name, arguments: JSON.stringify(call.input) },
541
545
  }));
542
- const { toolResultMessages, followUpParts } = await executeOpenAiToolCalls(executeTool, outcome.toolCalls, options.onEvent);
546
+ const { toolResultMessages, followUpParts } = await executeOpenAiToolCalls({ executeTool: executeTool, calls: outcome.toolCalls, onEvent: options.onEvent });
543
547
  messages = [
544
548
  ...messages,
545
- buildOpenAiAssistantToolCallMessage(outcome.text, assistantToolCalls),
546
- ...buildOpenAiToolExchangeMessages(toolResultMessages, followUpParts),
549
+ buildOpenAiAssistantToolCallMessage({ text: outcome.text, toolCalls: assistantToolCalls }),
550
+ ...buildOpenAiToolExchangeMessages({ toolResultMessages: toolResultMessages, followUpParts: followUpParts }),
547
551
  ];
548
552
  }
549
553
  return { finishReason: lastFinishReason, toolTurns };