@framers/agentos 0.12.24 → 0.13.2
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/README.md +11 -11
- package/dist/api/AgentOS.d.ts +15 -0
- package/dist/api/AgentOS.d.ts.map +1 -1
- package/dist/api/AgentOS.js +82 -0
- package/dist/api/AgentOS.js.map +1 -1
- package/dist/api/agent.d.ts +7 -3
- package/dist/api/agent.d.ts.map +1 -1
- package/dist/api/agent.js.map +1 -1
- package/dist/api/embedText.d.ts.map +1 -1
- package/dist/api/embedText.js +1 -8
- package/dist/api/embedText.js.map +1 -1
- package/dist/api/gmi.d.ts.map +1 -1
- package/dist/api/gmi.js +38 -19
- package/dist/api/gmi.js.map +1 -1
- package/dist/api/runtime/AgentOSOrchestrator.d.ts.map +1 -1
- package/dist/api/runtime/AgentOSOrchestrator.js +23 -0
- package/dist/api/runtime/AgentOSOrchestrator.js.map +1 -1
- package/dist/api/runtime/agentCognitiveMemory.d.ts +12 -9
- package/dist/api/runtime/agentCognitiveMemory.d.ts.map +1 -1
- package/dist/api/runtime/agentCognitiveMemory.js +53 -17
- package/dist/api/runtime/agentCognitiveMemory.js.map +1 -1
- package/dist/api/runtime/completionGateway.d.ts.map +1 -1
- package/dist/api/runtime/completionGateway.js +7 -0
- package/dist/api/runtime/completionGateway.js.map +1 -1
- package/dist/api/runtime/gmiResults.d.ts +63 -6
- package/dist/api/runtime/gmiResults.d.ts.map +1 -1
- package/dist/api/runtime/gmiResults.js +83 -13
- package/dist/api/runtime/gmiResults.js.map +1 -1
- package/dist/api/runtime/gmiSession.d.ts +31 -6
- package/dist/api/runtime/gmiSession.d.ts.map +1 -1
- package/dist/api/runtime/gmiSession.js +144 -44
- package/dist/api/runtime/gmiSession.js.map +1 -1
- package/dist/api/runtime/gmiTranscript.d.ts +7 -6
- package/dist/api/runtime/gmiTranscript.d.ts.map +1 -1
- package/dist/api/runtime/gmiTranscript.js +6 -5
- package/dist/api/runtime/gmiTranscript.js.map +1 -1
- package/dist/api/runtime/provider-defaults.d.ts +9 -0
- package/dist/api/runtime/provider-defaults.d.ts.map +1 -1
- package/dist/api/runtime/provider-defaults.js +9 -0
- package/dist/api/runtime/provider-defaults.js.map +1 -1
- package/dist/api/types/AgentOSInput.d.ts +5 -0
- package/dist/api/types/AgentOSInput.d.ts.map +1 -1
- package/dist/api/types/OrchestratorConfig.d.ts +3 -0
- package/dist/api/types/OrchestratorConfig.d.ts.map +1 -1
- package/dist/cognition/emergent/executor/QuickJSExecutor.d.ts +8 -0
- package/dist/cognition/emergent/executor/QuickJSExecutor.d.ts.map +1 -1
- package/dist/cognition/emergent/executor/QuickJSExecutor.js +193 -36
- package/dist/cognition/emergent/executor/QuickJSExecutor.js.map +1 -1
- package/dist/cognition/emergent/executor/guest-prelude.d.ts +4 -0
- package/dist/cognition/emergent/executor/guest-prelude.d.ts.map +1 -1
- package/dist/cognition/emergent/executor/guest-prelude.js +21 -1
- package/dist/cognition/emergent/executor/guest-prelude.js.map +1 -1
- package/dist/cognition/emergent/executor/guest-surface.d.ts.map +1 -1
- package/dist/cognition/emergent/executor/guest-surface.js +7 -2
- package/dist/cognition/emergent/executor/guest-surface.js.map +1 -1
- package/dist/cognition/memory/CognitiveMemoryManager.d.ts +28 -21
- package/dist/cognition/memory/CognitiveMemoryManager.d.ts.map +1 -1
- package/dist/cognition/memory/CognitiveMemoryManager.js +76 -26
- package/dist/cognition/memory/CognitiveMemoryManager.js.map +1 -1
- package/dist/cognition/memory/retrieval/store/MemoryStore.d.ts +10 -2
- package/dist/cognition/memory/retrieval/store/MemoryStore.d.ts.map +1 -1
- package/dist/cognition/memory/retrieval/store/MemoryStore.js +11 -2
- package/dist/cognition/memory/retrieval/store/MemoryStore.js.map +1 -1
- package/dist/cognition/substrate/ConversationHistoryManager.d.ts +3 -1
- package/dist/cognition/substrate/ConversationHistoryManager.d.ts.map +1 -1
- package/dist/cognition/substrate/ConversationHistoryManager.js +9 -2
- package/dist/cognition/substrate/ConversationHistoryManager.js.map +1 -1
- package/dist/cognition/substrate/GMI.d.ts +33 -2
- package/dist/cognition/substrate/GMI.d.ts.map +1 -1
- package/dist/cognition/substrate/GMI.js +225 -71
- package/dist/cognition/substrate/GMI.js.map +1 -1
- package/dist/cognition/substrate/IGMI.d.ts +14 -3
- package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
- package/dist/cognition/substrate/IGMI.js +2 -1
- package/dist/cognition/substrate/IGMI.js.map +1 -1
- package/dist/cognition/substrate/SentimentTracker.d.ts +8 -0
- package/dist/cognition/substrate/SentimentTracker.d.ts.map +1 -1
- package/dist/cognition/substrate/SentimentTracker.js +20 -0
- package/dist/cognition/substrate/SentimentTracker.js.map +1 -1
- package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OpenAIProvider.js +23 -4
- package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
- package/dist/core/utils/errors.d.ts +4 -0
- package/dist/core/utils/errors.d.ts.map +1 -1
- package/dist/core/utils/errors.js +8 -0
- package/dist/core/utils/errors.js.map +1 -1
- package/dist/extensions/ExtensionRegistry.d.ts.map +1 -1
- package/dist/extensions/ExtensionRegistry.js +4 -1
- package/dist/extensions/ExtensionRegistry.js.map +1 -1
- package/dist/io/speech/SpeechRuntime.d.ts.map +1 -1
- package/dist/io/speech/SpeechRuntime.js +5 -0
- package/dist/io/speech/SpeechRuntime.js.map +1 -1
- package/dist/io/speech/providers/MiniMaxTextToSpeechProvider.d.ts +34 -3
- package/dist/io/speech/providers/MiniMaxTextToSpeechProvider.d.ts.map +1 -1
- package/dist/io/speech/providers/MiniMaxTextToSpeechProvider.js +120 -11
- package/dist/io/speech/providers/MiniMaxTextToSpeechProvider.js.map +1 -1
- package/dist/safety/runtime/SpendMeter.d.ts +135 -0
- package/dist/safety/runtime/SpendMeter.d.ts.map +1 -0
- package/dist/safety/runtime/SpendMeter.js +61 -0
- package/dist/safety/runtime/SpendMeter.js.map +1 -0
- package/dist/safety/runtime/SqlSpendMeter.d.ts +78 -0
- package/dist/safety/runtime/SqlSpendMeter.d.ts.map +1 -0
- package/dist/safety/runtime/SqlSpendMeter.js +268 -0
- package/dist/safety/runtime/SqlSpendMeter.js.map +1 -0
- package/dist/safety/runtime/index.d.ts +4 -0
- package/dist/safety/runtime/index.d.ts.map +1 -1
- package/dist/safety/runtime/index.js +2 -0
- package/dist/safety/runtime/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -54,6 +54,26 @@ function cacheDiagnosticsSeed(value) {
|
|
|
54
54
|
const id = typeof value === 'object' ? value.previousMessageId : undefined;
|
|
55
55
|
return { previousMessageId: typeof id === 'string' && id.length > 0 ? id : null };
|
|
56
56
|
}
|
|
57
|
+
/** `value` when it is a usage report as a provider gives one: an object with a numeric token count. */
|
|
58
|
+
function asUsageReport(value) {
|
|
59
|
+
if (!value || typeof value !== 'object')
|
|
60
|
+
return undefined;
|
|
61
|
+
const { promptTokens, completionTokens, totalTokens } = value;
|
|
62
|
+
return [promptTokens, completionTokens, totalTokens].some((count) => typeof count === 'number' && Number.isFinite(count))
|
|
63
|
+
? value
|
|
64
|
+
: undefined;
|
|
65
|
+
}
|
|
66
|
+
/** A message's content as a string two equal contents share: the text itself, or the JSON of its parts. */
|
|
67
|
+
function contentKey(content) {
|
|
68
|
+
if (typeof content === 'string')
|
|
69
|
+
return content;
|
|
70
|
+
try {
|
|
71
|
+
return JSON.stringify(content) ?? '';
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
57
77
|
/** Adds one provider usage report to the turn's total. */
|
|
58
78
|
function addUsage(total, usage) {
|
|
59
79
|
total.promptTokens += usage.promptTokens || 0;
|
|
@@ -86,6 +106,16 @@ export class GMI {
|
|
|
86
106
|
*/
|
|
87
107
|
this.turnSequence = 0;
|
|
88
108
|
this.stateOwnerTurn = 0;
|
|
109
|
+
/**
|
|
110
|
+
* What the GMI needs to forget a turn once the history no longer holds it
|
|
111
|
+
* (see {@link GMI.forgetTurnsOutside}): the turn each trace entry was recorded
|
|
112
|
+
* in (entries recorded outside a turn have none), and each user turn's input
|
|
113
|
+
* as a comparison key, by turn id, oldest first.
|
|
114
|
+
*/
|
|
115
|
+
this.traceEntryTurn = new WeakMap();
|
|
116
|
+
this.turnInputs = new Map();
|
|
117
|
+
/** Settles once the latest replaceHistory() or clearHistory() has also cleaned the sentiment history. */
|
|
118
|
+
this.historyForgetting = Promise.resolve();
|
|
89
119
|
// (Self-reflection state is owned by MetapromptExecutor)
|
|
90
120
|
// Cognitive Memory Bridge
|
|
91
121
|
this.memoryBridge = null;
|
|
@@ -355,6 +385,57 @@ export class GMI {
|
|
|
355
385
|
details: details ? JSON.parse(JSON.stringify(details)) : {},
|
|
356
386
|
};
|
|
357
387
|
this.reasoningTrace.entries.push(entry);
|
|
388
|
+
if (this.reasoningTrace.turnId)
|
|
389
|
+
this.traceEntryTurn.set(entry, this.reasoningTrace.turnId);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Drops what the GMI recorded about the turns `history` no longer holds, so
|
|
393
|
+
* that a replaced or cleared fact reaches no later model call through the
|
|
394
|
+
* records kept beside the history: the reflection metaprompt sends recent
|
|
395
|
+
* trace entries to the model as evidence.
|
|
396
|
+
*
|
|
397
|
+
* A recorded turn is kept when `history` has a user message with that turn's
|
|
398
|
+
* input, each message keeping one turn, the newest first. For every other
|
|
399
|
+
* turn, the details of its trace entries are emptied (the first characters of
|
|
400
|
+
* its input, its tool calls and results; the entries stay, with their type and
|
|
401
|
+
* message), and the sentiment tracker drops the input excerpts of its events
|
|
402
|
+
* and sentiment trends. The tracker's part runs asynchronously and has
|
|
403
|
+
* finished before the next turn starts.
|
|
404
|
+
*
|
|
405
|
+
* @param history - The conversation history after the replacement.
|
|
406
|
+
*/
|
|
407
|
+
forgetTurnsOutside(history) {
|
|
408
|
+
const unclaimed = new Map();
|
|
409
|
+
for (const message of history) {
|
|
410
|
+
if (message.role !== 'user')
|
|
411
|
+
continue;
|
|
412
|
+
const key = contentKey(message.content);
|
|
413
|
+
unclaimed.set(key, (unclaimed.get(key) ?? 0) + 1);
|
|
414
|
+
}
|
|
415
|
+
const kept = new Set();
|
|
416
|
+
for (const [turnId, key] of [...this.turnInputs].reverse()) {
|
|
417
|
+
const left = unclaimed.get(key) ?? 0;
|
|
418
|
+
if (left === 0) {
|
|
419
|
+
this.turnInputs.delete(turnId);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
unclaimed.set(key, left - 1);
|
|
423
|
+
kept.add(turnId);
|
|
424
|
+
}
|
|
425
|
+
for (const entry of this.reasoningTrace.entries) {
|
|
426
|
+
const turnId = this.traceEntryTurn.get(entry);
|
|
427
|
+
if (turnId === undefined || kept.has(turnId))
|
|
428
|
+
continue;
|
|
429
|
+
entry.details = {};
|
|
430
|
+
this.traceEntryTurn.delete(entry);
|
|
431
|
+
}
|
|
432
|
+
this.historyForgetting = this.historyForgetting
|
|
433
|
+
.then(() => this.sentimentTracker?.forgetTurnsExcept(kept))
|
|
434
|
+
.catch((error) => {
|
|
435
|
+
this.addTraceEntry(ReasoningEntryType.WARNING, 'Could not drop the sentiment history of turns removed from the conversation history.', {
|
|
436
|
+
error: error instanceof Error ? error.message : String(error),
|
|
437
|
+
});
|
|
438
|
+
});
|
|
358
439
|
}
|
|
359
440
|
stringifyTurnContent(content) {
|
|
360
441
|
if (typeof content === 'string') {
|
|
@@ -462,16 +543,22 @@ export class GMI {
|
|
|
462
543
|
* Makes `messages` the whole conversation history. An empty array is
|
|
463
544
|
* authoritative: it empties the history (unlike
|
|
464
545
|
* `metadata.conversationHistoryForPrompt`, which a turn ignores when empty).
|
|
465
|
-
* The sentiment tracker and the metaprompts read the same history.
|
|
546
|
+
* The sentiment tracker and the metaprompts read the same history. What the
|
|
547
|
+
* GMI recorded about turns the new history no longer holds is dropped too:
|
|
548
|
+
* the details of their reasoning-trace entries and the input excerpts of
|
|
549
|
+
* their sentiment records (a turn stays while a user message with its input
|
|
550
|
+
* is in the history).
|
|
466
551
|
*/
|
|
467
552
|
replaceHistory(messages) {
|
|
468
553
|
this.conversationHistoryManager.clear();
|
|
469
554
|
if (messages.length > 0)
|
|
470
555
|
this.conversationHistoryManager.hydrate(messages);
|
|
556
|
+
this.forgetTurnsOutside(this.conversationHistoryManager.history);
|
|
471
557
|
}
|
|
472
|
-
/** Empties the conversation history. */
|
|
558
|
+
/** Empties the conversation history, and drops what the GMI recorded about its turns, as {@link GMI.replaceHistory} does. */
|
|
473
559
|
clearHistory() {
|
|
474
560
|
this.conversationHistoryManager.clear();
|
|
561
|
+
this.forgetTurnsOutside([]);
|
|
475
562
|
}
|
|
476
563
|
hydrateTurnContext(context) {
|
|
477
564
|
if (typeof context.sessionId === 'string' && context.sessionId.trim()) {
|
|
@@ -666,6 +753,9 @@ export class GMI {
|
|
|
666
753
|
const aggregatedUsage = { totalTokens: 0, promptTokens: 0, completionTokens: 0, breakdown: [] };
|
|
667
754
|
let lastErrorForOutput = undefined;
|
|
668
755
|
try {
|
|
756
|
+
// A history replaced or cleared just before this turn has finished dropping
|
|
757
|
+
// the sentiment records of the turns it removed.
|
|
758
|
+
await this.historyForgetting;
|
|
669
759
|
if (turnInput.userContextOverride) {
|
|
670
760
|
const mergedPreferences = turnInput.userContextOverride.preferences &&
|
|
671
761
|
typeof turnInput.userContextOverride.preferences === 'object'
|
|
@@ -700,6 +790,19 @@ export class GMI {
|
|
|
700
790
|
// assistant replies and tool results the loop below adds.
|
|
701
791
|
const historyAtTurnStart = [...this.conversationHistoryManager.history];
|
|
702
792
|
const currentUserMessage = historyAtTurnStart.find((message) => message.role === 'user' && !messagesBeforeInput.has(message));
|
|
793
|
+
// The turn's input, so a later replaceHistory() or clearHistory() can tell
|
|
794
|
+
// whether the history still holds this turn. Bounded like the trace:
|
|
795
|
+
// every turn on the trace has at least one entry there.
|
|
796
|
+
if (currentUserMessage) {
|
|
797
|
+
this.turnInputs.delete(turnId);
|
|
798
|
+
this.turnInputs.set(turnId, contentKey(currentUserMessage.content));
|
|
799
|
+
while (this.turnInputs.size > this.traceLimits.maxEntries) {
|
|
800
|
+
const oldest = this.turnInputs.keys().next().value;
|
|
801
|
+
if (oldest === undefined)
|
|
802
|
+
break;
|
|
803
|
+
this.turnInputs.delete(oldest);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
703
806
|
const currentTurnText = currentUserMessage?.content
|
|
704
807
|
? (typeof currentUserMessage.content === 'string'
|
|
705
808
|
? currentUserMessage.content
|
|
@@ -900,34 +1003,51 @@ export class GMI {
|
|
|
900
1003
|
toolsForLLM = discoveredTools;
|
|
901
1004
|
}
|
|
902
1005
|
}
|
|
903
|
-
// Every completion option the persona and the turn set (D4b)
|
|
904
|
-
// the
|
|
1006
|
+
// Every completion option the persona and the turn set (D4b), the turn's
|
|
1007
|
+
// over the persona's; one neither sets is not sent, so the provider's
|
|
1008
|
+
// default applies. Tools, user and streaming are the GMI's.
|
|
905
1009
|
const turnOptions = (turnInput.metadata?.options ?? {});
|
|
906
1010
|
const personaOptions = (this.activePersona.defaultModelCompletionOptions ?? {});
|
|
1011
|
+
// The provider's end-user field: the host's choice when the turn names one
|
|
1012
|
+
// (null sends none), else the turn's user.
|
|
1013
|
+
const providerUserId = turnInput.metadata && 'providerUserId' in turnInput.metadata
|
|
1014
|
+
? turnInput.metadata.providerUserId
|
|
1015
|
+
: this.currentUserContext.userId;
|
|
907
1016
|
const llmOptions = {
|
|
908
1017
|
...pickCompletionOptions(personaOptions),
|
|
909
1018
|
...pickCompletionOptions(turnOptions),
|
|
910
1019
|
...(stepCacheDiagnostics ? { cacheDiagnostics: { ...stepCacheDiagnostics } } : {}),
|
|
911
|
-
|
|
912
|
-
|
|
1020
|
+
// The turn's own abort signal: aborting it ends the model call in progress
|
|
1021
|
+
// with the provider's abort chunk, and every later call of the turn at once.
|
|
1022
|
+
...(turnOptions.abortSignal ? { abortSignal: turnOptions.abortSignal } : {}),
|
|
913
1023
|
tools: toolsForLLM.length > 0 ? toolsForLLM.map(t => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.inputSchema } })) : undefined,
|
|
914
|
-
|
|
915
|
-
|
|
1024
|
+
// Only with tools: OpenAI rejects a tool_choice on a request that offers none
|
|
1025
|
+
// (HTTP 400, "'tool_choice' is only allowed when 'tools' are specified").
|
|
1026
|
+
toolChoice: toolsForLLM.length > 0
|
|
1027
|
+
? (turnOptions.toolChoice ?? personaOptions.toolChoice ?? "auto")
|
|
1028
|
+
: undefined,
|
|
1029
|
+
...(providerUserId ? { userId: providerUserId } : {}),
|
|
916
1030
|
stream: true,
|
|
917
1031
|
};
|
|
918
1032
|
// A schema travels to the gateway, which lowers it per hop (D9); it is not a provider option.
|
|
919
1033
|
const responseSchema = turnOptions.responseSchema;
|
|
920
1034
|
const schemaName = turnOptions.schemaName;
|
|
921
|
-
// The gateway's route: the model asked for, the user
|
|
922
|
-
// task hint
|
|
923
|
-
//
|
|
1035
|
+
// The gateway's route: the model asked for, the last user message (its text
|
|
1036
|
+
// is the router's task hint: the text parts of a multimodal message, and on
|
|
1037
|
+
// a continuation the message being answered, never the continuation's own
|
|
1038
|
+
// internal text), the tools, and the call options each fallback hop derives
|
|
1039
|
+
// its overrides from. Every hop that could not start is traced.
|
|
924
1040
|
let lastHopError;
|
|
925
|
-
|
|
1041
|
+
let lastUserMessage;
|
|
1042
|
+
for (let i = historyAtTurnStart.length - 1; i >= 0 && !lastUserMessage; i--) {
|
|
1043
|
+
if (historyAtTurnStart[i].role === 'user')
|
|
1044
|
+
lastUserMessage = historyAtTurnStart[i];
|
|
1045
|
+
}
|
|
926
1046
|
const route = gateway
|
|
927
1047
|
? {
|
|
928
1048
|
modelId: modelIdToUse,
|
|
929
1049
|
providerId: providerIdForModel,
|
|
930
|
-
messages:
|
|
1050
|
+
messages: lastUserMessage ? [lastUserMessage] : [],
|
|
931
1051
|
tools: llmOptions.tools,
|
|
932
1052
|
callOptions: { maxTokens: llmOptions.maxTokens, effort: llmOptions.effort, cache: llmOptions.cache },
|
|
933
1053
|
onFallback: ({ from, to, hop, error }) => this.addTraceEntry(ReasoningEntryType.WARNING, `Model fallback from '${from}' to '${to}' (hop ${hop}): ${error.message}`),
|
|
@@ -942,7 +1062,17 @@ export class GMI {
|
|
|
942
1062
|
// prompt is built, so the prompt is budgeted for the serving model (D1).
|
|
943
1063
|
let resolution = null;
|
|
944
1064
|
if (gateway && route) {
|
|
945
|
-
|
|
1065
|
+
try {
|
|
1066
|
+
resolution = this.turnResolution ?? (await gateway.resolve(route));
|
|
1067
|
+
}
|
|
1068
|
+
catch (resolveError) {
|
|
1069
|
+
// resolve() throws only when the primary cannot be set up (no
|
|
1070
|
+
// credentials, a provider or model that does not resolve): a
|
|
1071
|
+
// configuration error, not a failure of the turn's processing.
|
|
1072
|
+
if (resolveError instanceof GMIError)
|
|
1073
|
+
throw resolveError;
|
|
1074
|
+
throw new GMIError(resolveError instanceof Error ? resolveError.message : String(resolveError), GMIErrorCode.CONFIGURATION_ERROR, { turnId, providerId: providerIdForModel, modelId: modelIdToUse });
|
|
1075
|
+
}
|
|
946
1076
|
if (!resolution) {
|
|
947
1077
|
throw new GMIError(`No model provider could serve the turn${lastHopError ? `: ${lastHopError.message}` : '.'}`, GMIErrorCode.LLM_PROVIDER_UNAVAILABLE, { turnId });
|
|
948
1078
|
}
|
|
@@ -1032,65 +1162,89 @@ export class GMI {
|
|
|
1032
1162
|
}
|
|
1033
1163
|
this.addTraceEntry(ReasoningEntryType.LLM_CALL_START, `Streaming from ${modelTargetInfo.modelId}${resolution ? ` (hop ${resolution.hop})` : ''}. Tools: ${toolsForLLM.length}.`);
|
|
1034
1164
|
let textDeltaEmitted = false;
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
: tc.
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1165
|
+
// The usage on the error chunk that ended the step, when it carried one.
|
|
1166
|
+
let errorChunkUsage;
|
|
1167
|
+
try {
|
|
1168
|
+
for await (const chunk of attempt) {
|
|
1169
|
+
if (chunk.error) {
|
|
1170
|
+
errorChunkUsage = asUsageReport(chunk.usage);
|
|
1171
|
+
throw new GMIError(`LLM stream error: ${chunk.error.message}`, GMIErrorCode.LLM_PROVIDER_ERROR, chunk.error.details);
|
|
1172
|
+
}
|
|
1173
|
+
if (chunk.responseTextDelta) {
|
|
1174
|
+
currentIterationTextResponse += chunk.responseTextDelta;
|
|
1175
|
+
aggregatedResponseText += chunk.responseTextDelta; // Aggregate for final output
|
|
1176
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TEXT_DELTA, chunk.responseTextDelta, { usage: chunk.usage });
|
|
1177
|
+
textDeltaEmitted = true;
|
|
1178
|
+
}
|
|
1179
|
+
// Handle fully formed tool_calls if present in the chunk's message
|
|
1180
|
+
const choice = chunk.choices?.[0];
|
|
1181
|
+
// Capture extended-thinking blocks from the final chunk so they ride
|
|
1182
|
+
// the assistant turn into history (replayed verbatim next tool turn).
|
|
1183
|
+
if (choice?.message?.thinkingBlocks?.length) {
|
|
1184
|
+
currentIterationThinkingBlocks = choice.message.thinkingBlocks;
|
|
1185
|
+
}
|
|
1186
|
+
// The gateway lifts a streamed schema tool call into `structuredOutput` (D9).
|
|
1187
|
+
const structured = chunk.structuredOutput;
|
|
1188
|
+
if (structured !== undefined)
|
|
1189
|
+
stepStructuredOutput = structured;
|
|
1190
|
+
if (choice?.message?.tool_calls && choice.message.tool_calls.length > 0) {
|
|
1191
|
+
currentIterationToolCallRequests = choice.message.tool_calls.map((tc) => ({
|
|
1192
|
+
id: tc.id || `toolcall-${uuidv4()}`, // Ensure ID
|
|
1193
|
+
name: tc.function.name,
|
|
1194
|
+
arguments: typeof tc.function.arguments === 'string'
|
|
1195
|
+
? JSON.parse(tc.function.arguments)
|
|
1196
|
+
: tc.function.arguments,
|
|
1197
|
+
...(tc.thoughtSignature ? { thoughtSignature: tc.thoughtSignature } : {}),
|
|
1198
|
+
}));
|
|
1199
|
+
aggregatedToolCalls.push(...currentIterationToolCallRequests); // Aggregate for final output
|
|
1200
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TOOL_CALL_REQUEST, [...currentIterationToolCallRequests], {
|
|
1201
|
+
metadata: {
|
|
1202
|
+
executionMode: 'internal',
|
|
1203
|
+
requiresExternalToolResult: false,
|
|
1204
|
+
},
|
|
1205
|
+
});
|
|
1206
|
+
this.addTraceEntry(ReasoningEntryType.TOOL_CALL_REQUESTED, `LLM requested tool(s).`, { requests: currentIterationToolCallRequests });
|
|
1207
|
+
}
|
|
1208
|
+
// A provider that sends no deltas: its final content is the step's text, emitted once (D4d).
|
|
1209
|
+
if (chunk.isFinal && !textDeltaEmitted && typeof choice?.message?.content === 'string' && choice.message.content.length > 0) {
|
|
1210
|
+
currentIterationTextResponse = choice.message.content;
|
|
1211
|
+
aggregatedResponseText += choice.message.content;
|
|
1212
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TEXT_DELTA, choice.message.content, { usage: chunk.usage });
|
|
1213
|
+
textDeltaEmitted = true;
|
|
1214
|
+
}
|
|
1215
|
+
if (chunk.isFinal && choice?.finishReason) {
|
|
1216
|
+
stepFinishReason = choice.finishReason;
|
|
1217
|
+
this.addTraceEntry(ReasoningEntryType.LLM_CALL_COMPLETE, `LLM stream part finished. Reason: ${choice.finishReason}`, { usage: chunk.usage });
|
|
1218
|
+
}
|
|
1219
|
+
if (chunk.isFinal)
|
|
1220
|
+
stepFinalChunk = chunk;
|
|
1221
|
+
// Usage on every chunk that carries it, choice or not (D4c). Providers report
|
|
1222
|
+
// the request's running total, so the turn total takes the step's last value once.
|
|
1223
|
+
if (chunk.usage) {
|
|
1224
|
+
stepUsage = chunk.usage;
|
|
1225
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.USAGE_UPDATE, chunk.usage);
|
|
1226
|
+
}
|
|
1227
|
+
} // End LLM stream
|
|
1228
|
+
}
|
|
1229
|
+
catch (stepError) {
|
|
1230
|
+
// A step that fails once its attempt has started was still billed.
|
|
1231
|
+
// Providers report the request's running total, so the bill is the
|
|
1232
|
+
// latest report: the error chunk's own usage, else the usage the
|
|
1233
|
+
// error carries (a refused turn reports it), else the last usage the
|
|
1234
|
+
// step reported. It counts toward the turn once and is reported on a
|
|
1235
|
+
// USAGE_UPDATE of its own, marked as a failed attempt's, because no
|
|
1236
|
+
// STEP_FINISHED will carry it.
|
|
1237
|
+
const billed = errorChunkUsage ??
|
|
1238
|
+
asUsageReport(stepError?.details?.usage) ??
|
|
1239
|
+
stepUsage;
|
|
1240
|
+
if (billed) {
|
|
1241
|
+
addUsage(aggregatedUsage, billed);
|
|
1242
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.USAGE_UPDATE, billed, {
|
|
1243
|
+
metadata: { attemptFailed: true, hop: resolution?.hop ?? 0, providerId: modelTargetInfo.providerId, modelId: modelTargetInfo.modelId },
|
|
1071
1244
|
});
|
|
1072
|
-
this.addTraceEntry(ReasoningEntryType.TOOL_CALL_REQUESTED, `LLM requested tool(s).`, { requests: currentIterationToolCallRequests });
|
|
1073
|
-
}
|
|
1074
|
-
// A provider that sends no deltas: its final content is the step's text, emitted once (D4d).
|
|
1075
|
-
if (chunk.isFinal && !textDeltaEmitted && typeof choice?.message?.content === 'string' && choice.message.content.length > 0) {
|
|
1076
|
-
currentIterationTextResponse = choice.message.content;
|
|
1077
|
-
aggregatedResponseText += choice.message.content;
|
|
1078
|
-
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TEXT_DELTA, choice.message.content, { usage: chunk.usage });
|
|
1079
|
-
textDeltaEmitted = true;
|
|
1080
|
-
}
|
|
1081
|
-
if (chunk.isFinal && choice?.finishReason) {
|
|
1082
|
-
stepFinishReason = choice.finishReason;
|
|
1083
|
-
this.addTraceEntry(ReasoningEntryType.LLM_CALL_COMPLETE, `LLM stream part finished. Reason: ${choice.finishReason}`, { usage: chunk.usage });
|
|
1084
1245
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
// Usage on every chunk that carries it, choice or not (D4c). Providers report
|
|
1088
|
-
// the request's running total, so the turn total takes the step's last value once.
|
|
1089
|
-
if (chunk.usage) {
|
|
1090
|
-
stepUsage = chunk.usage;
|
|
1091
|
-
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.USAGE_UPDATE, chunk.usage);
|
|
1092
|
-
}
|
|
1093
|
-
} // End LLM stream
|
|
1246
|
+
throw stepError;
|
|
1247
|
+
}
|
|
1094
1248
|
if (gateway && route && resolution && attemptOutcome) {
|
|
1095
1249
|
const outcome = await attemptOutcome;
|
|
1096
1250
|
if (outcome.kind === 'hopFailed') {
|