@framers/agentos 0.9.26 → 0.9.28
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/dist/api/generateText.d.ts +18 -0
- package/dist/api/generateText.d.ts.map +1 -1
- package/dist/api/generateText.js +280 -207
- package/dist/api/generateText.js.map +1 -1
- package/dist/api/runtime/tool-emulation/activation.d.ts +7 -0
- package/dist/api/runtime/tool-emulation/activation.d.ts.map +1 -0
- package/dist/api/runtime/tool-emulation/activation.js +17 -0
- package/dist/api/runtime/tool-emulation/activation.js.map +1 -0
- package/dist/api/runtime/tool-emulation/index.d.ts +5 -0
- package/dist/api/runtime/tool-emulation/index.d.ts.map +1 -0
- package/dist/api/runtime/tool-emulation/index.js +5 -0
- package/dist/api/runtime/tool-emulation/index.js.map +1 -0
- package/dist/api/runtime/tool-emulation/loop.d.ts +38 -0
- package/dist/api/runtime/tool-emulation/loop.d.ts.map +1 -0
- package/dist/api/runtime/tool-emulation/loop.js +53 -0
- package/dist/api/runtime/tool-emulation/loop.js.map +1 -0
- package/dist/api/runtime/tool-emulation/parser.d.ts +23 -0
- package/dist/api/runtime/tool-emulation/parser.d.ts.map +1 -0
- package/dist/api/runtime/tool-emulation/parser.js +38 -0
- package/dist/api/runtime/tool-emulation/parser.js.map +1 -0
- package/dist/api/runtime/tool-emulation/renderer.d.ts +8 -0
- package/dist/api/runtime/tool-emulation/renderer.d.ts.map +1 -0
- package/dist/api/runtime/tool-emulation/renderer.js +26 -0
- package/dist/api/runtime/tool-emulation/renderer.js.map +1 -0
- package/dist/api/streamText.d.ts.map +1 -1
- package/dist/api/streamText.js +324 -256
- package/dist/api/streamText.js.map +1 -1
- package/dist/cognition/rag/vector_stores/PostgresVectorStore.js +3 -3
- package/dist/cognition/rag/vector_stores/PostgresVectorStore.js.map +1 -1
- package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/AnthropicProvider.js +18 -4
- package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
- package/package.json +1 -1
package/dist/api/streamText.js
CHANGED
|
@@ -13,6 +13,7 @@ import { attachUsageAttributes, toTurnMetricUsage } from './observability.js';
|
|
|
13
13
|
import { fireLlmUsageObserver } from './observers.js';
|
|
14
14
|
import { hostPolicyToRouteParams, mergeRequiredCapabilities } from './runtime/hostPolicy.js';
|
|
15
15
|
import { adaptTools } from './runtime/toolAdapter.js';
|
|
16
|
+
import { runEmulatedToolLoop } from './runtime/tool-emulation/index.js';
|
|
16
17
|
import { buildFallbackChain, createPlan, isRetryableError, resolveChainOfThought, } from './generateText.js';
|
|
17
18
|
import { resolveDynamicToolCalls } from './runtime/dynamicToolCalling.js';
|
|
18
19
|
import { StreamingReconstructor } from '../core/llm/streaming/StreamingReconstructor.js';
|
|
@@ -229,287 +230,354 @@ export function streamText(opts) {
|
|
|
229
230
|
rootSpan?.setAttribute('agentos.api.plan_steps', resolvedPlan.steps.length);
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
233
|
+
// --- Prompt-based tool-calling shim (toolMode) ---
|
|
234
|
+
// For models without native tool-use, buffer the tool roundtrips and emit
|
|
235
|
+
// the final answer as a single text part through the stream contract (spec
|
|
236
|
+
// decision #1 — replay from buffer, no extra model call). 'prompt' forces
|
|
237
|
+
// it up front; 'auto' tries native streaming first and falls back here on
|
|
238
|
+
// the provider's tool-unsupported error (see the catch around the loop).
|
|
239
|
+
const toolMode = opts.toolMode ?? 'auto';
|
|
240
|
+
const toolUnsupportedErr = (e) => e instanceof Error &&
|
|
241
|
+
/support tool use|does not support (tools|function)|no endpoints found that support/i.test(e.message);
|
|
242
|
+
async function* runShimStream() {
|
|
243
|
+
const loopResult = await runEmulatedToolLoop({
|
|
244
|
+
tools: Array.from(toolMap.values()),
|
|
245
|
+
messages: messages.map((m) => ({
|
|
246
|
+
role: String(m.role),
|
|
247
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? ''),
|
|
248
|
+
})),
|
|
249
|
+
maxRoundtrips: opts.maxSteps ?? 5,
|
|
250
|
+
callModel: async (msgs) => {
|
|
251
|
+
// provider is guaranteed non-undefined by the `if (!provider) throw`
|
|
252
|
+
// guard above; the closure just loses TS's flow-narrowing.
|
|
253
|
+
const r = await provider.generateCompletion(resolved.modelId, msgs, {
|
|
254
|
+
temperature: opts.temperature,
|
|
255
|
+
maxTokens: opts.maxTokens,
|
|
256
|
+
});
|
|
257
|
+
const cc = r.choices?.[0]?.message?.content;
|
|
258
|
+
return {
|
|
259
|
+
text: typeof cc === 'string' ? cc : (cc?.text ?? ''),
|
|
260
|
+
usage: { totalTokens: r.usage?.totalTokens ?? 0 },
|
|
245
261
|
};
|
|
246
|
-
const modified = await opts.onBeforeGeneration(hookCtx);
|
|
247
|
-
if (modified) {
|
|
248
|
-
effectiveMessages = modified.messages;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
catch (hookErr) {
|
|
252
|
-
console.warn('[agentos] onBeforeGeneration hook error:', hookErr);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
const stepSpan = startAgentOSSpan('agentos.api.stream_text.step', {
|
|
256
|
-
attributes: {
|
|
257
|
-
'llm.provider': resolved.providerId,
|
|
258
|
-
'llm.model': resolved.modelId,
|
|
259
|
-
'agentos.api.step': step + 1,
|
|
260
|
-
'agentos.api.tool_count': tools.length,
|
|
261
262
|
},
|
|
262
263
|
});
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
// cache hits.
|
|
303
|
-
const cacheRead = chunk.usage.cacheReadInputTokens;
|
|
304
|
-
const cacheCreate = chunk.usage.cacheCreationInputTokens;
|
|
305
|
-
if (typeof cacheRead === 'number') {
|
|
306
|
-
usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + cacheRead;
|
|
307
|
-
}
|
|
308
|
-
if (typeof cacheCreate === 'number') {
|
|
309
|
-
usage.cacheCreationTokens = (usage.cacheCreationTokens ?? 0) + cacheCreate;
|
|
264
|
+
usage.totalTokens = (usage.totalTokens ?? 0) + loopResult.totalTokens;
|
|
265
|
+
finalText = loopResult.text;
|
|
266
|
+
const shimToolCalls = loopResult.toolCalls.map((c) => ({
|
|
267
|
+
name: c.name,
|
|
268
|
+
args: c.args,
|
|
269
|
+
...(c.error ? { error: c.error } : {}),
|
|
270
|
+
}));
|
|
271
|
+
if (loopResult.text) {
|
|
272
|
+
const part = { type: 'text', text: loopResult.text };
|
|
273
|
+
parts.push(part);
|
|
274
|
+
yield part;
|
|
275
|
+
}
|
|
276
|
+
resolveText(finalText);
|
|
277
|
+
resolveUsage(usage);
|
|
278
|
+
resolveToolCalls(shimToolCalls);
|
|
279
|
+
}
|
|
280
|
+
if (tools.length > 0 && toolMode === 'prompt') {
|
|
281
|
+
yield* runShimStream();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
let streamedAnyText = false;
|
|
285
|
+
try {
|
|
286
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
287
|
+
// --- onBeforeGeneration hook ---
|
|
288
|
+
let effectiveMessages = messages;
|
|
289
|
+
if (opts.onBeforeGeneration) {
|
|
290
|
+
try {
|
|
291
|
+
const hookCtx = {
|
|
292
|
+
messages: [...messages],
|
|
293
|
+
system: opts.system,
|
|
294
|
+
tools: Array.from(toolMap.values()),
|
|
295
|
+
model: resolved.modelId,
|
|
296
|
+
provider: resolved.providerId,
|
|
297
|
+
step,
|
|
298
|
+
prompt: opts.prompt,
|
|
299
|
+
};
|
|
300
|
+
const modified = await opts.onBeforeGeneration(hookCtx);
|
|
301
|
+
if (modified) {
|
|
302
|
+
effectiveMessages = modified.messages;
|
|
310
303
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
totalTokens: chunk.usage.totalTokens,
|
|
315
|
-
costUSD: chunk.usage.costUSD,
|
|
316
|
-
});
|
|
304
|
+
}
|
|
305
|
+
catch (hookErr) {
|
|
306
|
+
console.warn('[agentos] onBeforeGeneration hook error:', hookErr);
|
|
317
307
|
}
|
|
318
308
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
let streamedToolCalls = resolveDynamicToolCalls(finalChunk?.choices?.[0]?.message?.tool_calls ??
|
|
326
|
-
reconstructor
|
|
327
|
-
.getToolCalls()
|
|
328
|
-
.filter((toolCall) => toolCall.id && toolCall.name)
|
|
329
|
-
.map((toolCall) => ({
|
|
330
|
-
id: toolCall.id,
|
|
331
|
-
type: 'function',
|
|
332
|
-
function: {
|
|
333
|
-
name: toolCall.name,
|
|
334
|
-
arguments: toolCall.rawArguments || JSON.stringify(toolCall.arguments ?? {}),
|
|
309
|
+
const stepSpan = startAgentOSSpan('agentos.api.stream_text.step', {
|
|
310
|
+
attributes: {
|
|
311
|
+
'llm.provider': resolved.providerId,
|
|
312
|
+
'llm.model': resolved.modelId,
|
|
313
|
+
'agentos.api.step': step + 1,
|
|
314
|
+
'agentos.api.tool_count': tools.length,
|
|
335
315
|
},
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (opts.onAfterGeneration) {
|
|
316
|
+
});
|
|
317
|
+
const stream = provider.generateCompletionStream(resolved.modelId, effectiveMessages, {
|
|
318
|
+
tools: toolSchemas,
|
|
319
|
+
temperature: opts.temperature,
|
|
320
|
+
maxTokens: opts.maxTokens,
|
|
321
|
+
});
|
|
322
|
+
const reconstructor = new StreamingReconstructor();
|
|
344
323
|
try {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
324
|
+
for await (const chunk of stream) {
|
|
325
|
+
reconstructor.push(chunk);
|
|
326
|
+
const textDelta = chunk.responseTextDelta ?? '';
|
|
327
|
+
if (textDelta) {
|
|
328
|
+
const part = { type: 'text', text: textDelta };
|
|
329
|
+
parts.push(part);
|
|
330
|
+
yield part;
|
|
331
|
+
streamedAnyText = true;
|
|
332
|
+
}
|
|
333
|
+
if (chunk.error) {
|
|
334
|
+
const error = new Error(chunk.error.message);
|
|
335
|
+
const part = { type: 'error', error };
|
|
336
|
+
parts.push(part);
|
|
337
|
+
yield part;
|
|
338
|
+
metricStatus = 'error';
|
|
339
|
+
resolveText(finalText);
|
|
340
|
+
resolveUsage(usage);
|
|
341
|
+
resolveToolCalls(allToolCalls);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (chunk.isFinal && chunk.usage) {
|
|
345
|
+
usage.promptTokens += chunk.usage.promptTokens ?? 0;
|
|
346
|
+
usage.completionTokens += chunk.usage.completionTokens ?? 0;
|
|
347
|
+
usage.totalTokens += chunk.usage.totalTokens ?? 0;
|
|
348
|
+
if (typeof chunk.usage.costUSD === 'number') {
|
|
349
|
+
usage.costUSD = (usage.costUSD ?? 0) + chunk.usage.costUSD;
|
|
350
|
+
}
|
|
351
|
+
// Prompt-cache metrics from the provider layer. Provider
|
|
352
|
+
// surfaces these as cacheReadInputTokens /
|
|
353
|
+
// cacheCreationInputTokens on the final chunk's usage
|
|
354
|
+
// (mirrors Anthropic's cache_read_input_tokens /
|
|
355
|
+
// cache_creation_input_tokens); forward them onto the
|
|
356
|
+
// aggregated TokenUsage so downstream cost trackers see
|
|
357
|
+
// cache hits.
|
|
358
|
+
const cacheRead = chunk.usage.cacheReadInputTokens;
|
|
359
|
+
const cacheCreate = chunk.usage.cacheCreationInputTokens;
|
|
360
|
+
if (typeof cacheRead === 'number') {
|
|
361
|
+
usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + cacheRead;
|
|
362
|
+
}
|
|
363
|
+
if (typeof cacheCreate === 'number') {
|
|
364
|
+
usage.cacheCreationTokens = (usage.cacheCreationTokens ?? 0) + cacheCreate;
|
|
365
|
+
}
|
|
366
|
+
attachUsageAttributes(stepSpan, {
|
|
367
|
+
promptTokens: chunk.usage.promptTokens,
|
|
368
|
+
completionTokens: chunk.usage.completionTokens,
|
|
369
|
+
totalTokens: chunk.usage.totalTokens,
|
|
370
|
+
costUSD: chunk.usage.costUSD,
|
|
371
|
+
});
|
|
368
372
|
}
|
|
369
373
|
}
|
|
370
374
|
}
|
|
371
|
-
|
|
372
|
-
|
|
375
|
+
finally {
|
|
376
|
+
stepSpan?.end();
|
|
373
377
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const fnName = toolCall.function?.name ?? '';
|
|
396
|
-
const rawArgs = toolCall.function?.arguments ?? '{}';
|
|
397
|
-
const toolCallId = toolCall.id ?? '';
|
|
398
|
-
const toolCallRecord = {
|
|
399
|
-
name: fnName,
|
|
400
|
-
args: rawArgs,
|
|
401
|
-
};
|
|
402
|
-
let parsedArgs;
|
|
403
|
-
try {
|
|
404
|
-
parsedArgs = typeof rawArgs === 'string' ? JSON.parse(rawArgs) : rawArgs;
|
|
405
|
-
toolCallRecord.args = parsedArgs;
|
|
406
|
-
}
|
|
407
|
-
catch {
|
|
408
|
-
toolCallRecord.error = `Tool "${fnName}" arguments were not valid JSON.`;
|
|
409
|
-
const resultPart = {
|
|
410
|
-
type: 'tool-result',
|
|
411
|
-
toolName: fnName,
|
|
412
|
-
result: { error: toolCallRecord.error },
|
|
413
|
-
};
|
|
414
|
-
parts.push(resultPart);
|
|
415
|
-
yield resultPart;
|
|
416
|
-
messages.push({
|
|
417
|
-
role: 'tool',
|
|
418
|
-
tool_call_id: toolCallId,
|
|
419
|
-
content: JSON.stringify({ error: toolCallRecord.error }),
|
|
420
|
-
});
|
|
421
|
-
allToolCalls.push(toolCallRecord);
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
const requestPart = { type: 'tool-call', toolName: fnName, args: parsedArgs };
|
|
425
|
-
parts.push(requestPart);
|
|
426
|
-
yield requestPart;
|
|
427
|
-
const tool = toolMap.get(fnName);
|
|
428
|
-
if (!tool) {
|
|
429
|
-
toolCallRecord.error = `Tool "${fnName}" not found.`;
|
|
430
|
-
const resultPart = {
|
|
431
|
-
type: 'tool-result',
|
|
432
|
-
toolName: fnName,
|
|
433
|
-
result: { error: toolCallRecord.error },
|
|
434
|
-
};
|
|
435
|
-
parts.push(resultPart);
|
|
436
|
-
yield resultPart;
|
|
437
|
-
messages.push({
|
|
438
|
-
role: 'tool',
|
|
439
|
-
tool_call_id: toolCallId,
|
|
440
|
-
content: JSON.stringify({ error: toolCallRecord.error }),
|
|
441
|
-
});
|
|
442
|
-
allToolCalls.push(toolCallRecord);
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
// --- onBeforeToolExecution hook ---
|
|
446
|
-
if (opts.onBeforeToolExecution) {
|
|
378
|
+
const stepText = reconstructor.getFullText();
|
|
379
|
+
const finalChunk = reconstructor.getFinalChunk();
|
|
380
|
+
let streamedToolCalls = resolveDynamicToolCalls(finalChunk?.choices?.[0]?.message?.tool_calls ??
|
|
381
|
+
reconstructor
|
|
382
|
+
.getToolCalls()
|
|
383
|
+
.filter((toolCall) => toolCall.id && toolCall.name)
|
|
384
|
+
.map((toolCall) => ({
|
|
385
|
+
id: toolCall.id,
|
|
386
|
+
type: 'function',
|
|
387
|
+
function: {
|
|
388
|
+
name: toolCall.name,
|
|
389
|
+
arguments: toolCall.rawArguments || JSON.stringify(toolCall.arguments ?? {}),
|
|
390
|
+
},
|
|
391
|
+
})), {
|
|
392
|
+
text: stepText,
|
|
393
|
+
step,
|
|
394
|
+
toolsAvailable: tools.length > 0,
|
|
395
|
+
});
|
|
396
|
+
// --- onAfterGeneration hook ---
|
|
397
|
+
let effectiveStepText = stepText;
|
|
398
|
+
if (opts.onAfterGeneration) {
|
|
447
399
|
try {
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
400
|
+
const stepUsage = {
|
|
401
|
+
promptTokens: usage.promptTokens,
|
|
402
|
+
completionTokens: usage.completionTokens,
|
|
403
|
+
totalTokens: usage.totalTokens,
|
|
404
|
+
costUSD: usage.costUSD,
|
|
405
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
406
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
407
|
+
};
|
|
408
|
+
const toolCallRecords = (streamedToolCalls ?? []).map((tc) => ({
|
|
409
|
+
name: tc.function?.name ?? '',
|
|
410
|
+
args: tc.function?.arguments ?? '{}',
|
|
411
|
+
}));
|
|
412
|
+
const hookResult = {
|
|
413
|
+
text: stepText,
|
|
414
|
+
toolCalls: toolCallRecords,
|
|
415
|
+
usage: stepUsage,
|
|
452
416
|
step,
|
|
453
417
|
};
|
|
454
|
-
const
|
|
455
|
-
if (
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
result: { skipped: true },
|
|
461
|
-
};
|
|
462
|
-
parts.push(resultPart);
|
|
463
|
-
yield resultPart;
|
|
464
|
-
messages.push({
|
|
465
|
-
role: 'tool',
|
|
466
|
-
tool_call_id: toolCallId,
|
|
467
|
-
content: JSON.stringify({ skipped: true }),
|
|
468
|
-
});
|
|
469
|
-
allToolCalls.push(toolCallRecord);
|
|
470
|
-
continue;
|
|
418
|
+
const modified = await opts.onAfterGeneration(hookResult);
|
|
419
|
+
if (modified) {
|
|
420
|
+
effectiveStepText = modified.text;
|
|
421
|
+
if (modified.toolCalls.length === 0 && streamedToolCalls && streamedToolCalls.length > 0) {
|
|
422
|
+
streamedToolCalls = [];
|
|
423
|
+
}
|
|
471
424
|
}
|
|
472
|
-
parsedArgs = hookResult.args;
|
|
473
425
|
}
|
|
474
426
|
catch (hookErr) {
|
|
475
|
-
console.warn('[agentos]
|
|
427
|
+
console.warn('[agentos] onAfterGeneration hook error:', hookErr);
|
|
476
428
|
}
|
|
477
429
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
tool_call_id: toolCallId,
|
|
492
|
-
content: JSON.stringify(result.output ?? { error: result.error ?? 'Tool execution failed.' }),
|
|
493
|
-
});
|
|
430
|
+
// Always track the latest step's text so finalText is available even
|
|
431
|
+
// when maxSteps is exhausted with outstanding tool calls.
|
|
432
|
+
if (effectiveStepText) {
|
|
433
|
+
finalText = effectiveStepText;
|
|
434
|
+
}
|
|
435
|
+
if (!streamedToolCalls || streamedToolCalls.length === 0) {
|
|
436
|
+
rootSpan?.setAttribute('agentos.api.finish_reason', 'stop');
|
|
437
|
+
rootSpan?.setAttribute('agentos.api.tool_calls', allToolCalls.length);
|
|
438
|
+
attachUsageAttributes(rootSpan, usage);
|
|
439
|
+
resolveText(finalText);
|
|
440
|
+
resolveUsage(usage);
|
|
441
|
+
resolveToolCalls(allToolCalls);
|
|
442
|
+
return;
|
|
494
443
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
444
|
+
messages.push({
|
|
445
|
+
role: 'assistant',
|
|
446
|
+
content: effectiveStepText || null,
|
|
447
|
+
tool_calls: streamedToolCalls,
|
|
448
|
+
});
|
|
449
|
+
for (const toolCall of streamedToolCalls) {
|
|
450
|
+
const fnName = toolCall.function?.name ?? '';
|
|
451
|
+
const rawArgs = toolCall.function?.arguments ?? '{}';
|
|
452
|
+
const toolCallId = toolCall.id ?? '';
|
|
453
|
+
const toolCallRecord = {
|
|
454
|
+
name: fnName,
|
|
455
|
+
args: rawArgs,
|
|
501
456
|
};
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
457
|
+
let parsedArgs;
|
|
458
|
+
try {
|
|
459
|
+
parsedArgs = typeof rawArgs === 'string' ? JSON.parse(rawArgs) : rawArgs;
|
|
460
|
+
toolCallRecord.args = parsedArgs;
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
toolCallRecord.error = `Tool "${fnName}" arguments were not valid JSON.`;
|
|
464
|
+
const resultPart = {
|
|
465
|
+
type: 'tool-result',
|
|
466
|
+
toolName: fnName,
|
|
467
|
+
result: { error: toolCallRecord.error },
|
|
468
|
+
};
|
|
469
|
+
parts.push(resultPart);
|
|
470
|
+
yield resultPart;
|
|
471
|
+
messages.push({
|
|
472
|
+
role: 'tool',
|
|
473
|
+
tool_call_id: toolCallId,
|
|
474
|
+
content: JSON.stringify({ error: toolCallRecord.error }),
|
|
475
|
+
});
|
|
476
|
+
allToolCalls.push(toolCallRecord);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const requestPart = { type: 'tool-call', toolName: fnName, args: parsedArgs };
|
|
480
|
+
parts.push(requestPart);
|
|
481
|
+
yield requestPart;
|
|
482
|
+
const tool = toolMap.get(fnName);
|
|
483
|
+
if (!tool) {
|
|
484
|
+
toolCallRecord.error = `Tool "${fnName}" not found.`;
|
|
485
|
+
const resultPart = {
|
|
486
|
+
type: 'tool-result',
|
|
487
|
+
toolName: fnName,
|
|
488
|
+
result: { error: toolCallRecord.error },
|
|
489
|
+
};
|
|
490
|
+
parts.push(resultPart);
|
|
491
|
+
yield resultPart;
|
|
492
|
+
messages.push({
|
|
493
|
+
role: 'tool',
|
|
494
|
+
tool_call_id: toolCallId,
|
|
495
|
+
content: JSON.stringify({ error: toolCallRecord.error }),
|
|
496
|
+
});
|
|
497
|
+
allToolCalls.push(toolCallRecord);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
// --- onBeforeToolExecution hook ---
|
|
501
|
+
if (opts.onBeforeToolExecution) {
|
|
502
|
+
try {
|
|
503
|
+
const hookInfo = {
|
|
504
|
+
name: fnName,
|
|
505
|
+
args: parsedArgs,
|
|
506
|
+
id: toolCallId || '',
|
|
507
|
+
step,
|
|
508
|
+
};
|
|
509
|
+
const hookResult = await opts.onBeforeToolExecution(hookInfo);
|
|
510
|
+
if (hookResult === null) {
|
|
511
|
+
toolCallRecord.error = 'Skipped by onBeforeToolExecution hook';
|
|
512
|
+
const resultPart = {
|
|
513
|
+
type: 'tool-result',
|
|
514
|
+
toolName: fnName,
|
|
515
|
+
result: { skipped: true },
|
|
516
|
+
};
|
|
517
|
+
parts.push(resultPart);
|
|
518
|
+
yield resultPart;
|
|
519
|
+
messages.push({
|
|
520
|
+
role: 'tool',
|
|
521
|
+
tool_call_id: toolCallId,
|
|
522
|
+
content: JSON.stringify({ skipped: true }),
|
|
523
|
+
});
|
|
524
|
+
allToolCalls.push(toolCallRecord);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
parsedArgs = hookResult.args;
|
|
528
|
+
}
|
|
529
|
+
catch (hookErr) {
|
|
530
|
+
console.warn('[agentos] onBeforeToolExecution hook error:', hookErr);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
const result = await tool.execute(parsedArgs, buildHelperToolExecutionContext('streamText', helperToolRunId, step, toolCallId || undefined));
|
|
535
|
+
toolCallRecord.result = result.output;
|
|
536
|
+
toolCallRecord.error = result.success ? undefined : result.error;
|
|
537
|
+
const resultPart = {
|
|
538
|
+
type: 'tool-result',
|
|
539
|
+
toolName: fnName,
|
|
540
|
+
result: result.output ?? { error: result.error },
|
|
541
|
+
};
|
|
542
|
+
parts.push(resultPart);
|
|
543
|
+
yield resultPart;
|
|
544
|
+
messages.push({
|
|
545
|
+
role: 'tool',
|
|
546
|
+
tool_call_id: toolCallId,
|
|
547
|
+
content: JSON.stringify(result.output ?? { error: result.error ?? 'Tool execution failed.' }),
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
catch (err) {
|
|
551
|
+
toolCallRecord.error = err?.message ?? String(err);
|
|
552
|
+
const resultPart = {
|
|
553
|
+
type: 'tool-result',
|
|
554
|
+
toolName: fnName,
|
|
555
|
+
result: { error: toolCallRecord.error },
|
|
556
|
+
};
|
|
557
|
+
parts.push(resultPart);
|
|
558
|
+
yield resultPart;
|
|
559
|
+
messages.push({
|
|
560
|
+
role: 'tool',
|
|
561
|
+
tool_call_id: toolCallId,
|
|
562
|
+
content: JSON.stringify({ error: toolCallRecord.error }),
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
allToolCalls.push(toolCallRecord);
|
|
509
566
|
}
|
|
510
|
-
allToolCalls.push(toolCallRecord);
|
|
511
567
|
}
|
|
512
568
|
}
|
|
569
|
+
catch (loopErr) {
|
|
570
|
+
// 'auto' reactive fallback: if the provider rejected native tool-use
|
|
571
|
+
// before any text streamed, re-run the turn through the prompt shim.
|
|
572
|
+
if (!streamedAnyText &&
|
|
573
|
+
tools.length > 0 &&
|
|
574
|
+
toolMode === 'auto' &&
|
|
575
|
+
toolUnsupportedErr(loopErr)) {
|
|
576
|
+
yield* runShimStream();
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
throw loopErr;
|
|
580
|
+
}
|
|
513
581
|
resolveText(finalText);
|
|
514
582
|
resolveUsage(usage);
|
|
515
583
|
resolveToolCalls(allToolCalls);
|