@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.
- package/CHANGELOG.md +103 -0
- package/README.md +34 -17
- package/dist/acp-model-probe.d.ts +6 -4
- package/dist/acp-model-probe.js +3 -3
- package/dist/agent-protocol/acp/account-failure.d.ts +4 -2
- package/dist/agent-protocol/acp/account-failure.js +1 -1
- package/dist/agent-protocol/acp/json.d.ts +6 -6
- package/dist/agent-protocol/acp/json.js +3 -3
- package/dist/agent-protocol/acp/models.d.ts +10 -5
- package/dist/agent-protocol/acp/models.js +32 -31
- package/dist/agent-protocol/acp/session-params.d.ts +3 -1
- package/dist/agent-protocol/acp/session-params.js +2 -1
- package/dist/agent-protocol/acp/session.d.ts +28 -22
- package/dist/agent-protocol/acp/session.js +181 -147
- package/dist/agent-protocol/acp/text-suppression.d.ts +1 -1
- package/dist/agent-protocol/acp/text-suppression.js +1 -1
- package/dist/agent-protocol/acp/types.d.ts +3 -2
- package/dist/agent-protocol/acp/updates.d.ts +11 -11
- package/dist/agent-protocol/acp/updates.js +2 -2
- package/dist/agent-protocol/core/json-line-stream.d.ts +24 -8
- package/dist/agent-protocol/core/json-line-stream.js +8 -8
- package/dist/agent-protocol/pi-rpc/events.d.ts +5 -1
- package/dist/agent-protocol/pi-rpc/events.js +36 -36
- package/dist/agent-protocol/pi-rpc/internal.d.ts +4 -1
- package/dist/agent-protocol/pi-rpc/models.d.ts +3 -1
- package/dist/agent-protocol/pi-rpc/models.js +1 -1
- package/dist/agent-protocol/pi-rpc/session.d.ts +1 -1
- package/dist/agent-protocol/pi-rpc/session.js +33 -32
- package/dist/amr-model-cache.d.ts +12 -4
- package/dist/amr-model-cache.js +17 -21
- package/dist/amr-profile-resolver.d.ts +3 -1
- package/dist/anthropic-live-models.js +0 -0
- package/dist/artifact-taxonomy.d.ts +6 -2
- package/dist/auth.d.ts +52 -15
- package/dist/auth.js +36 -36
- package/dist/claude-code-models.js +2 -2
- package/dist/claude-stream.d.ts +22 -3
- package/dist/claude-stream.js +24 -8
- package/dist/copilot-stream.d.ts +7 -3
- package/dist/copilot-stream.js +3 -3
- package/dist/defs/aider.d.ts +8 -1
- package/dist/defs/aider.js +2 -2
- package/dist/defs/amp.d.ts +8 -1
- package/dist/defs/amp.js +2 -2
- package/dist/defs/amr.d.ts +22 -6
- package/dist/defs/amr.js +23 -23
- package/dist/defs/antigravity.d.ts +15 -4
- package/dist/defs/antigravity.js +9 -8
- package/dist/defs/auggie.d.ts +4 -1
- package/dist/defs/auggie.js +1 -1
- package/dist/defs/claude.d.ts +12 -2
- package/dist/defs/claude.js +5 -5
- package/dist/defs/cline.d.ts +4 -1
- package/dist/defs/cline.js +1 -1
- package/dist/defs/codebuddy.d.ts +8 -1
- package/dist/defs/codebuddy.js +2 -2
- package/dist/defs/codex.d.ts +18 -4
- package/dist/defs/codex.js +7 -7
- package/dist/defs/copilot.d.ts +8 -1
- package/dist/defs/copilot.js +2 -2
- package/dist/defs/crush.d.ts +8 -1
- package/dist/defs/crush.js +1 -1
- package/dist/defs/cursor-agent.d.ts +14 -3
- package/dist/defs/cursor-agent.js +5 -5
- package/dist/defs/deepseek.d.ts +8 -1
- package/dist/defs/deepseek.js +2 -2
- package/dist/defs/devin.d.ts +12 -2
- package/dist/defs/devin.js +3 -3
- package/dist/defs/droid.d.ts +8 -1
- package/dist/defs/droid.js +1 -1
- package/dist/defs/gemini.d.ts +8 -1
- package/dist/defs/gemini.js +1 -1
- package/dist/defs/goose.d.ts +4 -1
- package/dist/defs/goose.js +1 -1
- package/dist/defs/grok-build.d.ts +11 -2
- package/dist/defs/grok-build.js +3 -3
- package/dist/defs/hermes.d.ts +4 -1
- package/dist/defs/hermes.js +2 -2
- package/dist/defs/kilo.d.ts +4 -1
- package/dist/defs/kilo.js +2 -2
- package/dist/defs/kimi.d.ts +4 -1
- package/dist/defs/kimi.js +2 -2
- package/dist/defs/kiro.d.ts +4 -1
- package/dist/defs/kiro.js +2 -2
- package/dist/defs/mimo.d.ts +8 -1
- package/dist/defs/mimo.js +2 -2
- package/dist/defs/opencode.d.ts +9 -2
- package/dist/defs/opencode.js +2 -2
- package/dist/defs/pi.d.ts +12 -2
- package/dist/defs/pi.js +9 -9
- package/dist/defs/qoder.d.ts +8 -1
- package/dist/defs/qoder.js +2 -2
- package/dist/defs/qwen.d.ts +8 -1
- package/dist/defs/qwen.js +2 -2
- package/dist/defs/reasonix.d.ts +4 -1
- package/dist/defs/reasonix.js +3 -3
- package/dist/defs/shared.d.ts +4 -2
- package/dist/defs/shared.js +2 -2
- package/dist/defs/trae-cli.d.ts +12 -2
- package/dist/defs/trae-cli.js +3 -3
- package/dist/defs/vibe.d.ts +4 -1
- package/dist/defs/vibe.js +2 -2
- package/dist/detection.d.ts +19 -4
- package/dist/detection.js +33 -33
- package/dist/diagnostics.d.ts +14 -3
- package/dist/diagnostics.js +5 -5
- package/dist/env.d.ts +16 -4
- package/dist/env.js +7 -7
- package/dist/executables.d.ts +24 -8
- package/dist/executables.js +13 -13
- package/dist/index.d.ts +228 -4
- package/dist/index.js +230 -6
- package/dist/invocation.d.ts +6 -1
- package/dist/invocation.js +2 -2
- package/dist/json-event-stream.d.ts +7 -2
- package/dist/json-event-stream.js +3 -3
- package/dist/launch.d.ts +12 -2
- package/dist/launch.js +3 -3
- package/dist/log-file.d.ts +4 -1
- package/dist/log-file.js +2 -2
- package/dist/mcp.d.ts +6 -3
- package/dist/mcp.js +1 -1
- package/dist/metadata.d.ts +7 -3
- package/dist/metadata.js +1 -1
- package/dist/mmd-routes.d.ts +22 -6
- package/dist/mmd-routes.js +14 -14
- package/dist/model-catalog-cache.d.ts +50 -0
- package/dist/model-catalog-cache.js +61 -0
- package/dist/model-loading-state.d.ts +10 -0
- package/dist/model-loading-state.js +23 -0
- package/dist/model-registry.d.ts +28 -7
- package/dist/model-registry.js +11 -8
- package/dist/models.d.ts +31 -6
- package/dist/models.js +6 -6
- package/dist/opencode-log.d.ts +12 -4
- package/dist/opencode-log.js +8 -8
- package/dist/paths.d.ts +6 -2
- package/dist/paths.js +3 -3
- package/dist/pi-models.d.ts +3 -1
- package/dist/pi-models.js +2 -2
- package/dist/prompt-budget.d.ts +17 -4
- package/dist/prompt-budget.js +3 -3
- package/dist/prompt-file.d.ts +5 -1
- package/dist/prompt-file.js +1 -1
- package/dist/providers/aihubmix.d.ts +34 -9
- package/dist/providers/aihubmix.js +26 -26
- package/dist/providers/anthropic-messages.d.ts +77 -20
- package/dist/providers/anthropic-messages.js +53 -50
- package/dist/providers/azure-chat.d.ts +21 -7
- package/dist/providers/azure-chat.js +18 -16
- package/dist/providers/connection-guard.d.ts +45 -31
- package/dist/providers/connection-guard.js +40 -143
- package/dist/providers/connection-test.d.ts +1 -1
- package/dist/providers/connection-test.js +13 -30
- package/dist/providers/elevenlabs.d.ts +7 -2
- package/dist/providers/elevenlabs.js +4 -4
- package/dist/providers/google-messages.d.ts +44 -10
- package/dist/providers/google-messages.js +36 -34
- package/dist/providers/google-schema.d.ts +26 -0
- package/dist/providers/google-schema.js +253 -0
- package/dist/providers/google.d.ts +21 -6
- package/dist/providers/google.js +10 -10
- package/dist/providers/index.d.ts +3 -2
- package/dist/providers/index.js +3 -2
- package/dist/providers/model-catalog.d.ts +1 -1
- package/dist/providers/model-catalog.js +14 -27
- package/dist/providers/oauth-callback-server.d.ts +1 -1
- package/dist/providers/oauth-callback-server.js +3 -2
- package/dist/providers/oauth-credentials.d.ts +7 -1
- package/dist/providers/oauth-credentials.js +5 -5
- package/dist/providers/oauth-provider.d.ts +2 -2
- package/dist/providers/oauth-provider.js +20 -25
- package/dist/providers/oauth-tokens.d.ts +26 -6
- package/dist/providers/oauth-tokens.js +9 -9
- package/dist/providers/ollama-chat.d.ts +66 -31
- package/dist/providers/ollama-chat.js +64 -61
- package/dist/providers/openai-chat.d.ts +77 -22
- package/dist/providers/openai-chat.js +71 -67
- package/dist/providers/pkce.d.ts +33 -20
- package/dist/providers/pkce.js +75 -146
- package/dist/providers/sse-decode.d.ts +3 -1
- package/dist/providers/sse-decode.js +1 -1
- package/dist/providers/token-params.d.ts +16 -5
- package/dist/providers/token-params.js +6 -6
- package/dist/providers/tool-turn-types.d.ts +89 -0
- package/dist/providers/tool-turn-types.js +2 -0
- package/dist/providers/tool-turn.d.ts +10 -0
- package/dist/providers/tool-turn.js +252 -0
- package/dist/qoder-stream.d.ts +7 -3
- package/dist/qoder-stream.js +3 -3
- package/dist/registry.d.ts +6 -2
- package/dist/registry.js +4 -4
- package/dist/resolution.d.ts +5 -1
- package/dist/resolution.js +3 -3
- package/dist/role-marker-guard.d.ts +9 -5
- package/dist/role-marker-guard.js +5 -5
- package/dist/terminal-launch.d.ts +6 -1
- package/dist/terminal-launch.js +2 -2
- package/dist/types.d.ts +22 -6
- 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,
|
|
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(
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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(
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 `
|
|
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
|
|
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(
|
|
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 };
|