@framers/agentos 0.12.3 → 0.12.5
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/agent.d.ts +7 -5
- package/dist/api/agent.d.ts.map +1 -1
- package/dist/api/agent.js.map +1 -1
- package/dist/api/generateObject.d.ts +5 -5
- package/dist/api/generateText.d.ts +173 -50
- package/dist/api/generateText.d.ts.map +1 -1
- package/dist/api/generateText.js +272 -81
- package/dist/api/generateText.js.map +1 -1
- package/dist/api/index.d.ts +6 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +5 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/runtime/GMIChunkTransformer.d.ts +1 -1
- package/dist/api/runtime/GMIChunkTransformer.d.ts.map +1 -1
- package/dist/api/runtime/GMIChunkTransformer.js +7 -2
- package/dist/api/runtime/GMIChunkTransformer.js.map +1 -1
- package/dist/api/runtime/completionGateway.d.ts +98 -0
- package/dist/api/runtime/completionGateway.d.ts.map +1 -0
- package/dist/api/runtime/completionGateway.js +347 -0
- package/dist/api/runtime/completionGateway.js.map +1 -0
- package/dist/api/runtime/contextWindowFit.d.ts +49 -0
- package/dist/api/runtime/contextWindowFit.d.ts.map +1 -0
- package/dist/api/runtime/contextWindowFit.js +109 -0
- package/dist/api/runtime/contextWindowFit.js.map +1 -0
- package/dist/api/runtime/gatewayProviderManager.d.ts +37 -0
- package/dist/api/runtime/gatewayProviderManager.d.ts.map +1 -0
- package/dist/api/runtime/gatewayProviderManager.js +49 -0
- package/dist/api/runtime/gatewayProviderManager.js.map +1 -0
- package/dist/api/streamText.d.ts.map +1 -1
- package/dist/api/streamText.js +90 -6
- package/dist/api/streamText.js.map +1 -1
- package/dist/cognition/emergent/ComposableToolBuilder.d.ts.map +1 -1
- package/dist/cognition/emergent/ComposableToolBuilder.js +16 -8
- package/dist/cognition/emergent/ComposableToolBuilder.js.map +1 -1
- package/dist/cognition/emergent/EffectsStore.d.ts +58 -0
- package/dist/cognition/emergent/EffectsStore.d.ts.map +1 -0
- package/dist/cognition/emergent/EffectsStore.js +96 -0
- package/dist/cognition/emergent/EffectsStore.js.map +1 -0
- package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts +22 -2
- package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentCapabilityEngine.js +128 -19
- package/dist/cognition/emergent/EmergentCapabilityEngine.js.map +1 -1
- package/dist/cognition/emergent/EmergentJudge.d.ts +1 -1
- package/dist/cognition/emergent/EmergentJudge.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentToolRegistry.d.ts +12 -3
- package/dist/cognition/emergent/EmergentToolRegistry.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentToolRegistry.js +51 -4
- package/dist/cognition/emergent/EmergentToolRegistry.js.map +1 -1
- package/dist/cognition/emergent/ForgeToolMetaTool.d.ts.map +1 -1
- package/dist/cognition/emergent/ForgeToolMetaTool.js +4 -1
- package/dist/cognition/emergent/ForgeToolMetaTool.js.map +1 -1
- package/dist/cognition/emergent/SandboxedToolForge.d.ts +55 -51
- package/dist/cognition/emergent/SandboxedToolForge.d.ts.map +1 -1
- package/dist/cognition/emergent/SandboxedToolForge.js +103 -109
- package/dist/cognition/emergent/SandboxedToolForge.js.map +1 -1
- package/dist/cognition/emergent/ToolPackage.d.ts +2 -2
- package/dist/cognition/emergent/ToolPackage.d.ts.map +1 -1
- package/dist/cognition/emergent/broker/CapabilityBroker.d.ts +58 -0
- package/dist/cognition/emergent/broker/CapabilityBroker.d.ts.map +1 -0
- package/dist/cognition/emergent/broker/CapabilityBroker.js +288 -0
- package/dist/cognition/emergent/broker/CapabilityBroker.js.map +1 -0
- package/dist/cognition/emergent/broker/fetch.d.ts +44 -0
- package/dist/cognition/emergent/broker/fetch.d.ts.map +1 -0
- package/dist/cognition/emergent/broker/fetch.js +197 -0
- package/dist/cognition/emergent/broker/fetch.js.map +1 -0
- package/dist/cognition/emergent/broker/fs-read.d.ts +63 -0
- package/dist/cognition/emergent/broker/fs-read.d.ts.map +1 -0
- package/dist/cognition/emergent/broker/fs-read.js +133 -0
- package/dist/cognition/emergent/broker/fs-read.js.map +1 -0
- package/dist/cognition/emergent/broker/refusal.d.ts +10 -0
- package/dist/cognition/emergent/broker/refusal.d.ts.map +1 -0
- package/dist/cognition/emergent/broker/refusal.js +13 -0
- package/dist/cognition/emergent/broker/refusal.js.map +1 -0
- package/dist/cognition/emergent/ceiling.d.ts +75 -0
- package/dist/cognition/emergent/ceiling.d.ts.map +1 -0
- package/dist/cognition/emergent/ceiling.js +166 -0
- package/dist/cognition/emergent/ceiling.js.map +1 -0
- package/dist/cognition/emergent/index.d.ts +2 -0
- package/dist/cognition/emergent/index.d.ts.map +1 -1
- package/dist/cognition/emergent/index.js +1 -0
- package/dist/cognition/emergent/index.js.map +1 -1
- package/dist/cognition/emergent/types.d.ts +75 -2
- package/dist/cognition/emergent/types.d.ts.map +1 -1
- package/dist/cognition/emergent/types.js.map +1 -1
- package/dist/cognition/substrate/GMI.d.ts +21 -0
- package/dist/cognition/substrate/GMI.d.ts.map +1 -1
- package/dist/cognition/substrate/GMI.js +286 -88
- package/dist/cognition/substrate/GMI.js.map +1 -1
- package/dist/cognition/substrate/IGMI.d.ts +48 -1
- package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
- package/dist/cognition/substrate/IGMI.js +4 -0
- package/dist/cognition/substrate/IGMI.js.map +1 -1
- package/dist/core/llm/providers/errors/ContextWindowExceededError.d.ts +22 -0
- package/dist/core/llm/providers/errors/ContextWindowExceededError.d.ts.map +1 -0
- package/dist/core/llm/providers/errors/ContextWindowExceededError.js +21 -0
- package/dist/core/llm/providers/errors/ContextWindowExceededError.js.map +1 -0
- package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js +34 -13
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
- package/dist/core/llm/routing/IModelRouter.d.ts +6 -0
- package/dist/core/llm/routing/IModelRouter.d.ts.map +1 -1
- package/dist/core/llm/routing/PolicyAwareRouter.d.ts +7 -0
- package/dist/core/llm/routing/PolicyAwareRouter.d.ts.map +1 -1
- package/dist/core/llm/routing/PolicyAwareRouter.js +12 -0
- package/dist/core/llm/routing/PolicyAwareRouter.js.map +1 -1
- package/dist/core/llm/routing/UncensoredModelCatalog.d.ts +37 -0
- package/dist/core/llm/routing/UncensoredModelCatalog.d.ts.map +1 -1
- package/dist/core/llm/routing/UncensoredModelCatalog.js +64 -74
- package/dist/core/llm/routing/UncensoredModelCatalog.js.map +1 -1
- package/dist/core/tools/ITool.d.ts +45 -0
- package/dist/core/tools/ITool.d.ts.map +1 -1
- package/dist/core/tools/ToolOrchestrator.d.ts.map +1 -1
- package/dist/core/tools/ToolOrchestrator.js +4 -8
- package/dist/core/tools/ToolOrchestrator.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -25,6 +25,31 @@ import { feedbackTraceMessage } from './userFeedback.js';
|
|
|
25
25
|
import { resolveReasoningTraceLimits } from './reasoningTraceLimits.js';
|
|
26
26
|
const DEFAULT_MAX_CONVERSATION_HISTORY_TURNS = 20;
|
|
27
27
|
const DEFAULT_SELF_REFLECTION_INTERVAL_TURNS = 5;
|
|
28
|
+
/** The completion options a persona's defaults and a turn's `metadata.options` may set (design D4b). */
|
|
29
|
+
const FORWARDED_COMPLETION_OPTION_KEYS = [
|
|
30
|
+
'temperature', 'maxTokens', 'topP', 'frequencyPenalty', 'presencePenalty', 'thinking', 'effort', 'cache',
|
|
31
|
+
'promptCacheKey', 'promptCacheRetention', 'serviceTier', 'requestTimeout', 'customModelParams',
|
|
32
|
+
'stopSequences', 'responseFormat', 'toolChoice',
|
|
33
|
+
];
|
|
34
|
+
/** The forwarded completion options `source` sets; unset keys are left out so a later layer can fill them. */
|
|
35
|
+
function pickCompletionOptions(source) {
|
|
36
|
+
const picked = {};
|
|
37
|
+
if (!source)
|
|
38
|
+
return picked;
|
|
39
|
+
for (const key of FORWARDED_COMPLETION_OPTION_KEYS) {
|
|
40
|
+
if (source[key] !== undefined)
|
|
41
|
+
picked[key] = source[key];
|
|
42
|
+
}
|
|
43
|
+
return picked;
|
|
44
|
+
}
|
|
45
|
+
/** Adds one provider usage report to the turn's total. */
|
|
46
|
+
function addUsage(total, usage) {
|
|
47
|
+
total.promptTokens += usage.promptTokens || 0;
|
|
48
|
+
total.completionTokens += usage.completionTokens || 0;
|
|
49
|
+
total.totalTokens = total.promptTokens + total.completionTokens;
|
|
50
|
+
if (usage.costUSD)
|
|
51
|
+
total.totalCostUSD = (total.totalCostUSD || 0) + usage.costUSD;
|
|
52
|
+
}
|
|
28
53
|
/**
|
|
29
54
|
* @class GMI
|
|
30
55
|
* @implements {IGMI}
|
|
@@ -421,6 +446,21 @@ export class GMI {
|
|
|
421
446
|
hydrateConversationHistory(conversationHistory) {
|
|
422
447
|
this.conversationHistoryManager.hydrate(conversationHistory);
|
|
423
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Makes `messages` the whole conversation history. An empty array is
|
|
451
|
+
* authoritative: it empties the history (unlike
|
|
452
|
+
* `metadata.conversationHistoryForPrompt`, which a turn ignores when empty).
|
|
453
|
+
* The sentiment tracker and the metaprompts read the same history.
|
|
454
|
+
*/
|
|
455
|
+
replaceHistory(messages) {
|
|
456
|
+
this.conversationHistoryManager.clear();
|
|
457
|
+
if (messages.length > 0)
|
|
458
|
+
this.conversationHistoryManager.hydrate(messages);
|
|
459
|
+
}
|
|
460
|
+
/** Empties the conversation history. */
|
|
461
|
+
clearHistory() {
|
|
462
|
+
this.conversationHistoryManager.clear();
|
|
463
|
+
}
|
|
424
464
|
hydrateTurnContext(context) {
|
|
425
465
|
if (typeof context.sessionId === 'string' && context.sessionId.trim()) {
|
|
426
466
|
this.reasoningTrace.sessionId = context.sessionId.trim();
|
|
@@ -561,6 +601,25 @@ export class GMI {
|
|
|
561
601
|
return 'google_function_calling';
|
|
562
602
|
return 'openai_functions';
|
|
563
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* The model target of a turn without a completion gateway: the runtime
|
|
606
|
+
* path's lookup through the provider manager, with the 8192-token window
|
|
607
|
+
* and no capabilities when the manager knows nothing about the model.
|
|
608
|
+
*/
|
|
609
|
+
async legacyModelTargetInfo(modelId, providerId) {
|
|
610
|
+
const modelDetails = await this.llmProviderManager.getModelInfo(modelId, providerId);
|
|
611
|
+
return {
|
|
612
|
+
modelId,
|
|
613
|
+
providerId: modelDetails?.providerId || providerId || this.llmProviderManager.getProviderForModel(modelId)?.providerId || 'unknown',
|
|
614
|
+
maxContextTokens: modelDetails?.contextWindowSize || 8192,
|
|
615
|
+
capabilities: modelDetails?.capabilities || [],
|
|
616
|
+
promptFormatType: this.determinePromptFormat(),
|
|
617
|
+
toolSupport: {
|
|
618
|
+
supported: modelDetails?.capabilities.includes('tool_use') || false,
|
|
619
|
+
format: this.determineToolFormat(modelDetails, providerId),
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
}
|
|
564
623
|
/** @inheritdoc */
|
|
565
624
|
async *processTurnStream(turnInput) {
|
|
566
625
|
const continuationAllowedStates = turnInput.metadata?.isToolContinuation === true
|
|
@@ -572,6 +631,10 @@ export class GMI {
|
|
|
572
631
|
}
|
|
573
632
|
this.state = GMIPrimeState.PROCESSING;
|
|
574
633
|
const turnNumber = ++this.turnSequence;
|
|
634
|
+
// A user turn starts at the primary hop; a tool continuation stays on the
|
|
635
|
+
// hop that served the step it continues.
|
|
636
|
+
if (turnInput.metadata?.isToolContinuation !== true)
|
|
637
|
+
this.turnResolution = undefined;
|
|
575
638
|
this.stateOwnerTurn = turnNumber;
|
|
576
639
|
// False once a newer turn has started; lifecycle writes below check it.
|
|
577
640
|
const ownsState = () => this.stateOwnerTurn === turnNumber;
|
|
@@ -787,41 +850,13 @@ export class GMI {
|
|
|
787
850
|
assembledMemoryContext: assembledMemoryContext ?? undefined,
|
|
788
851
|
// tools: this.activePersona.embeddedTools, // If ITool[] and PromptComponents.tools takes ITool[]
|
|
789
852
|
};
|
|
853
|
+
const gateway = this.config.completionGateway;
|
|
790
854
|
const preferredModelIdFromInput = turnInput.metadata?.options?.preferredModelId;
|
|
791
855
|
const modelIdToUse = preferredModelIdFromInput || this.activePersona.defaultModelId || this.config.defaultLlmModelId;
|
|
792
856
|
const providerIdForModel = this.activePersona.defaultProviderId || this.config.defaultLlmProviderId;
|
|
793
857
|
if (!modelIdToUse) {
|
|
794
858
|
throw new GMIError("Could not determine modelId for LLM call.", GMIErrorCode.CONFIGURATION_ERROR, { turnId });
|
|
795
859
|
}
|
|
796
|
-
const modelDetails = await this.llmProviderManager.getModelInfo(modelIdToUse, providerIdForModel);
|
|
797
|
-
const modelTargetInfo = {
|
|
798
|
-
modelId: modelIdToUse,
|
|
799
|
-
providerId: modelDetails?.providerId || providerIdForModel || this.llmProviderManager.getProviderForModel(modelIdToUse)?.providerId || 'unknown',
|
|
800
|
-
maxContextTokens: modelDetails?.contextWindowSize || 8192, // Default fallback
|
|
801
|
-
capabilities: modelDetails?.capabilities || [],
|
|
802
|
-
promptFormatType: this.determinePromptFormat(),
|
|
803
|
-
toolSupport: {
|
|
804
|
-
supported: modelDetails?.capabilities.includes('tool_use') || false,
|
|
805
|
-
format: this.determineToolFormat(modelDetails, providerIdForModel),
|
|
806
|
-
},
|
|
807
|
-
};
|
|
808
|
-
const promptEngineResult = await this.promptEngine.constructPrompt(promptComponents, modelTargetInfo, promptExecContext);
|
|
809
|
-
promptEngineResult.issues?.forEach(issue => this.addTraceEntry(ReasoningEntryType.WARNING, `Prompt Engine Issue: ${issue.message}`, issue));
|
|
810
|
-
// A failed template leaves the prompt empty, and every provider takes
|
|
811
|
-
// a non-empty ChatMessage[]; fail the turn rather than send nothing.
|
|
812
|
-
const promptMessages = promptEngineResult.prompt;
|
|
813
|
-
if (!Array.isArray(promptMessages) || promptMessages.length === 0) {
|
|
814
|
-
throw new GMIError(`Prompt construction produced no chat messages for model '${modelTargetInfo.modelId}' (template '${promptEngineResult.metadata?.templateUsed ?? 'unknown'}').`, GMIErrorCode.GMI_PROCESSING_ERROR, {
|
|
815
|
-
turnId,
|
|
816
|
-
templateUsed: promptEngineResult.metadata?.templateUsed,
|
|
817
|
-
issues: promptEngineResult.issues?.filter((issue) => issue.type === 'error'),
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
this.addTraceEntry(ReasoningEntryType.PROMPT_CONSTRUCTION_COMPLETE, `Prompt constructed for model ${modelTargetInfo.modelId}.`);
|
|
821
|
-
const provider = this.llmProviderManager.getProvider(modelTargetInfo.providerId);
|
|
822
|
-
if (!provider) {
|
|
823
|
-
throw new GMIError(`LLM Provider '${modelTargetInfo.providerId}' not found or not initialized.`, GMIErrorCode.LLM_PROVIDER_UNAVAILABLE);
|
|
824
|
-
}
|
|
825
860
|
const plannedToolFailureMode = turnInput.metadata?.executionPolicy?.toolFailureMode === 'fail_closed'
|
|
826
861
|
? 'fail_closed'
|
|
827
862
|
: 'fail_open';
|
|
@@ -846,77 +881,231 @@ export class GMI {
|
|
|
846
881
|
toolsForLLM = discoveredTools;
|
|
847
882
|
}
|
|
848
883
|
}
|
|
884
|
+
// Every completion option the persona and the turn set (D4b); temperature and
|
|
885
|
+
// the output budget keep their defaults; tools, user and streaming are the GMI's.
|
|
886
|
+
const turnOptions = (turnInput.metadata?.options ?? {});
|
|
887
|
+
const personaOptions = (this.activePersona.defaultModelCompletionOptions ?? {});
|
|
849
888
|
const llmOptions = {
|
|
850
|
-
|
|
851
|
-
|
|
889
|
+
...pickCompletionOptions(personaOptions),
|
|
890
|
+
...pickCompletionOptions(turnOptions),
|
|
891
|
+
temperature: turnOptions.temperature ?? personaOptions.temperature ?? 0.7,
|
|
892
|
+
maxTokens: turnOptions.maxTokens ?? personaOptions.maxTokens ?? 2048,
|
|
852
893
|
tools: toolsForLLM.length > 0 ? toolsForLLM.map(t => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.inputSchema } })) : undefined,
|
|
853
|
-
toolChoice:
|
|
894
|
+
toolChoice: turnOptions.toolChoice ?? personaOptions.toolChoice ?? (toolsForLLM.length > 0 ? "auto" : undefined),
|
|
854
895
|
userId: this.currentUserContext.userId,
|
|
855
|
-
stream: true,
|
|
856
|
-
responseFormat: turnInput.metadata?.options?.responseFormat,
|
|
896
|
+
stream: true,
|
|
857
897
|
};
|
|
858
|
-
|
|
898
|
+
// A schema travels to the gateway, which lowers it per hop (D9); it is not a provider option.
|
|
899
|
+
const responseSchema = turnOptions.responseSchema;
|
|
900
|
+
const schemaName = turnOptions.schemaName;
|
|
901
|
+
// The gateway's route: the model asked for, the user's text as the router's
|
|
902
|
+
// task hint, the tools, and the call options each fallback hop derives its
|
|
903
|
+
// overrides from. Every hop that could not start is traced.
|
|
904
|
+
let lastHopError;
|
|
905
|
+
const userText = this.stringifyTurnContent(turnInput.content) ?? '';
|
|
906
|
+
const route = gateway
|
|
907
|
+
? {
|
|
908
|
+
modelId: modelIdToUse,
|
|
909
|
+
providerId: providerIdForModel,
|
|
910
|
+
messages: userText ? [{ role: 'user', content: userText }] : [],
|
|
911
|
+
tools: llmOptions.tools,
|
|
912
|
+
callOptions: { maxTokens: llmOptions.maxTokens, effort: llmOptions.effort, cache: llmOptions.cache },
|
|
913
|
+
onFallback: ({ from, to, hop, error }) => this.addTraceEntry(ReasoningEntryType.WARNING, `Model fallback from '${from}' to '${to}' (hop ${hop}): ${error.message}`),
|
|
914
|
+
onHopFailure: ({ providerId, hop, error }) => {
|
|
915
|
+
lastHopError = error;
|
|
916
|
+
this.addTraceEntry(ReasoningEntryType.WARNING, `Provider '${providerId}' (hop ${hop}) could not start: ${error.message}`);
|
|
917
|
+
},
|
|
918
|
+
}
|
|
919
|
+
: undefined;
|
|
920
|
+
// The hop that serves this step: the one that served the turn's last
|
|
921
|
+
// step, else the route's first hop that can start. Resolved before the
|
|
922
|
+
// prompt is built, so the prompt is budgeted for the serving model (D1).
|
|
923
|
+
let resolution = null;
|
|
924
|
+
if (gateway && route) {
|
|
925
|
+
resolution = this.turnResolution ?? (await gateway.resolve(route));
|
|
926
|
+
if (!resolution) {
|
|
927
|
+
throw new GMIError(`No model provider could serve the turn${lastHopError ? `: ${lastHopError.message}` : '.'}`, GMIErrorCode.LLM_PROVIDER_UNAVAILABLE, { turnId });
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// What the serving attempt produced; read after the hop loop.
|
|
859
931
|
let currentIterationTextResponse = "";
|
|
860
932
|
let currentIterationToolCallRequests = [];
|
|
861
|
-
// Extended-thinking blocks emitted this
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
//
|
|
865
|
-
// turn is unchanged on the normal path.
|
|
933
|
+
// Extended-thinking blocks emitted this step (Anthropic with thinking
|
|
934
|
+
// enabled). Captured from the final chunk and stored on the assistant
|
|
935
|
+
// turn so the next tool turn replays them verbatim, which Anthropic
|
|
936
|
+
// requires. Empty for every non-thinking step.
|
|
866
937
|
let currentIterationThinkingBlocks = [];
|
|
867
|
-
let
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
938
|
+
let stepUsage;
|
|
939
|
+
let stepFinishReason = null;
|
|
940
|
+
let stepFinalChunk;
|
|
941
|
+
let stepStructuredOutput;
|
|
942
|
+
let modelTargetInfo;
|
|
943
|
+
// One attempt per hop. A failure before any output moves to the next
|
|
944
|
+
// hop and rebuilds the prompt for that hop's model (D3); after output
|
|
945
|
+
// there is no fallback for the step. Without a gateway the loop runs once.
|
|
946
|
+
hopLoop: for (;;) {
|
|
947
|
+
if (resolution) {
|
|
948
|
+
this.llmProviderManager.setResolution?.(resolution);
|
|
871
949
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
950
|
+
modelTargetInfo = resolution
|
|
951
|
+
? {
|
|
952
|
+
modelId: resolution.modelId,
|
|
953
|
+
providerId: resolution.providerId,
|
|
954
|
+
maxContextTokens: resolution.maxContextTokens,
|
|
955
|
+
capabilities: resolution.capabilities,
|
|
956
|
+
promptFormatType: this.determinePromptFormat(),
|
|
957
|
+
toolSupport: { supported: resolution.capabilities.includes('tool_use'), format: resolution.toolFormat },
|
|
958
|
+
}
|
|
959
|
+
: await this.legacyModelTargetInfo(modelIdToUse, providerIdForModel);
|
|
960
|
+
const promptEngineResult = await this.promptEngine.constructPrompt(promptComponents, modelTargetInfo, promptExecContext);
|
|
961
|
+
promptEngineResult.issues?.forEach(issue => this.addTraceEntry(ReasoningEntryType.WARNING, `Prompt Engine Issue: ${issue.message}`, issue));
|
|
962
|
+
// A failed template leaves the prompt empty, and every provider takes
|
|
963
|
+
// a non-empty ChatMessage[]; fail the turn rather than send nothing.
|
|
964
|
+
const promptMessages = promptEngineResult.prompt;
|
|
965
|
+
if (!Array.isArray(promptMessages) || promptMessages.length === 0) {
|
|
966
|
+
throw new GMIError(`Prompt construction produced no chat messages for model '${modelTargetInfo.modelId}' (template '${promptEngineResult.metadata?.templateUsed ?? 'unknown'}').`, GMIErrorCode.GMI_PROCESSING_ERROR, {
|
|
967
|
+
turnId,
|
|
968
|
+
templateUsed: promptEngineResult.metadata?.templateUsed,
|
|
969
|
+
issues: promptEngineResult.issues?.filter((issue) => issue.type === 'error'),
|
|
970
|
+
});
|
|
877
971
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
972
|
+
this.addTraceEntry(ReasoningEntryType.PROMPT_CONSTRUCTION_COMPLETE, `Prompt constructed for model ${modelTargetInfo.modelId}.`);
|
|
973
|
+
let attempt;
|
|
974
|
+
let attemptOutcome;
|
|
975
|
+
if (gateway && resolution) {
|
|
976
|
+
const gatewayAttempt = gateway.stream(resolution, promptMessages, llmOptions, responseSchema, schemaName);
|
|
977
|
+
attempt = gatewayAttempt;
|
|
978
|
+
attemptOutcome = gatewayAttempt.outcome;
|
|
884
979
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
: tc.function.arguments,
|
|
892
|
-
...(tc.thoughtSignature ? { thoughtSignature: tc.thoughtSignature } : {}),
|
|
893
|
-
}));
|
|
894
|
-
aggregatedToolCalls.push(...currentIterationToolCallRequests); // Aggregate for final output
|
|
895
|
-
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TOOL_CALL_REQUEST, [...currentIterationToolCallRequests], {
|
|
896
|
-
metadata: {
|
|
897
|
-
executionMode: 'internal',
|
|
898
|
-
requiresExternalToolResult: false,
|
|
899
|
-
},
|
|
900
|
-
});
|
|
901
|
-
this.addTraceEntry(ReasoningEntryType.TOOL_CALL_REQUESTED, `LLM requested tool(s).`, { requests: currentIterationToolCallRequests });
|
|
980
|
+
else {
|
|
981
|
+
const provider = this.llmProviderManager.getProvider(modelTargetInfo.providerId);
|
|
982
|
+
if (!provider) {
|
|
983
|
+
throw new GMIError(`LLM Provider '${modelTargetInfo.providerId}' not found or not initialized.`, GMIErrorCode.LLM_PROVIDER_UNAVAILABLE);
|
|
984
|
+
}
|
|
985
|
+
attempt = provider.generateCompletionStream(modelTargetInfo.modelId, promptMessages, llmOptions);
|
|
902
986
|
}
|
|
903
|
-
|
|
904
|
-
|
|
987
|
+
this.addTraceEntry(ReasoningEntryType.LLM_CALL_START, `Streaming from ${modelTargetInfo.modelId}${resolution ? ` (hop ${resolution.hop})` : ''}. Tools: ${toolsForLLM.length}.`);
|
|
988
|
+
let textDeltaEmitted = false;
|
|
989
|
+
for await (const chunk of attempt) {
|
|
990
|
+
if (chunk.error) {
|
|
991
|
+
throw new GMIError(`LLM stream error: ${chunk.error.message}`, GMIErrorCode.LLM_PROVIDER_ERROR, chunk.error.details);
|
|
992
|
+
}
|
|
993
|
+
if (chunk.responseTextDelta) {
|
|
994
|
+
currentIterationTextResponse += chunk.responseTextDelta;
|
|
995
|
+
aggregatedResponseText += chunk.responseTextDelta; // Aggregate for final output
|
|
996
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TEXT_DELTA, chunk.responseTextDelta, { usage: chunk.usage });
|
|
997
|
+
textDeltaEmitted = true;
|
|
998
|
+
}
|
|
999
|
+
// Handle fully formed tool_calls if present in the chunk's message
|
|
1000
|
+
const choice = chunk.choices?.[0];
|
|
1001
|
+
// Capture extended-thinking blocks from the final chunk so they ride
|
|
1002
|
+
// the assistant turn into history (replayed verbatim next tool turn).
|
|
1003
|
+
if (choice?.message?.thinkingBlocks?.length) {
|
|
1004
|
+
currentIterationThinkingBlocks = choice.message.thinkingBlocks;
|
|
1005
|
+
}
|
|
1006
|
+
// The gateway lifts a streamed schema tool call into `structuredOutput` (D9).
|
|
1007
|
+
const structured = chunk.structuredOutput;
|
|
1008
|
+
if (structured !== undefined)
|
|
1009
|
+
stepStructuredOutput = structured;
|
|
1010
|
+
if (choice?.message?.tool_calls && choice.message.tool_calls.length > 0) {
|
|
1011
|
+
currentIterationToolCallRequests = choice.message.tool_calls.map((tc) => ({
|
|
1012
|
+
id: tc.id || `toolcall-${uuidv4()}`, // Ensure ID
|
|
1013
|
+
name: tc.function.name,
|
|
1014
|
+
arguments: typeof tc.function.arguments === 'string'
|
|
1015
|
+
? JSON.parse(tc.function.arguments)
|
|
1016
|
+
: tc.function.arguments,
|
|
1017
|
+
...(tc.thoughtSignature ? { thoughtSignature: tc.thoughtSignature } : {}),
|
|
1018
|
+
}));
|
|
1019
|
+
aggregatedToolCalls.push(...currentIterationToolCallRequests); // Aggregate for final output
|
|
1020
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TOOL_CALL_REQUEST, [...currentIterationToolCallRequests], {
|
|
1021
|
+
metadata: {
|
|
1022
|
+
executionMode: 'internal',
|
|
1023
|
+
requiresExternalToolResult: false,
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
this.addTraceEntry(ReasoningEntryType.TOOL_CALL_REQUESTED, `LLM requested tool(s).`, { requests: currentIterationToolCallRequests });
|
|
1027
|
+
}
|
|
1028
|
+
// A provider that sends no deltas: its final content is the step's text, emitted once (D4d).
|
|
1029
|
+
if (chunk.isFinal && !textDeltaEmitted && typeof choice?.message?.content === 'string' && choice.message.content.length > 0) {
|
|
1030
|
+
currentIterationTextResponse = choice.message.content;
|
|
1031
|
+
aggregatedResponseText += choice.message.content;
|
|
1032
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TEXT_DELTA, choice.message.content, { usage: chunk.usage });
|
|
1033
|
+
textDeltaEmitted = true;
|
|
1034
|
+
}
|
|
1035
|
+
if (chunk.isFinal && choice?.finishReason) {
|
|
1036
|
+
stepFinishReason = choice.finishReason;
|
|
1037
|
+
this.addTraceEntry(ReasoningEntryType.LLM_CALL_COMPLETE, `LLM stream part finished. Reason: ${choice.finishReason}`, { usage: chunk.usage });
|
|
1038
|
+
}
|
|
1039
|
+
if (chunk.isFinal)
|
|
1040
|
+
stepFinalChunk = chunk;
|
|
1041
|
+
// Usage on every chunk that carries it, choice or not (D4c). Providers report
|
|
1042
|
+
// the request's running total, so the turn total takes the step's last value once.
|
|
905
1043
|
if (chunk.usage) {
|
|
1044
|
+
stepUsage = chunk.usage;
|
|
906
1045
|
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.USAGE_UPDATE, chunk.usage);
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1046
|
+
}
|
|
1047
|
+
} // End LLM stream
|
|
1048
|
+
if (gateway && route && resolution && attemptOutcome) {
|
|
1049
|
+
const outcome = await attemptOutcome;
|
|
1050
|
+
if (outcome.kind === 'hopFailed') {
|
|
1051
|
+
const failedHop = resolution;
|
|
1052
|
+
lastHopError = outcome.error;
|
|
1053
|
+
this.addTraceEntry(ReasoningEntryType.WARNING, `Provider '${failedHop.providerId}' (hop ${failedHop.hop}) failed before any output: ${outcome.error.message}`);
|
|
1054
|
+
// A failed attempt can still be billed (a refused turn reports its
|
|
1055
|
+
// usage). It counts toward the turn once, whether or not the turn
|
|
1056
|
+
// goes on, and is reported on its own USAGE_UPDATE: no STEP_FINISHED
|
|
1057
|
+
// carries it.
|
|
1058
|
+
if (outcome.usage) {
|
|
1059
|
+
addUsage(aggregatedUsage, outcome.usage);
|
|
1060
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.USAGE_UPDATE, outcome.usage, {
|
|
1061
|
+
metadata: { attemptFailed: true, hop: failedHop.hop, providerId: failedHop.providerId, modelId: failedHop.modelId },
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
if (!outcome.retryable) {
|
|
1065
|
+
throw new GMIError(`LLM provider error: ${outcome.error.message}`, GMIErrorCode.LLM_PROVIDER_ERROR, { turnId, providerId: failedHop.providerId, hop: failedHop.hop });
|
|
1066
|
+
}
|
|
1067
|
+
const next = await gateway.resolve(route, failedHop);
|
|
1068
|
+
if (!next) {
|
|
1069
|
+
throw new GMIError(`LLM provider error after ${failedHop.hop + 1} hop(s): ${lastHopError.message}`, GMIErrorCode.LLM_PROVIDER_ERROR, { turnId, providerId: failedHop.providerId, hop: failedHop.hop });
|
|
1070
|
+
}
|
|
1071
|
+
// Nothing of the failed attempt carries over to the next hop.
|
|
1072
|
+
currentIterationTextResponse = "";
|
|
1073
|
+
currentIterationToolCallRequests = [];
|
|
1074
|
+
currentIterationThinkingBlocks = [];
|
|
1075
|
+
stepUsage = undefined;
|
|
1076
|
+
stepFinishReason = null;
|
|
1077
|
+
stepFinalChunk = undefined;
|
|
1078
|
+
stepStructuredOutput = undefined;
|
|
1079
|
+
resolution = next;
|
|
1080
|
+
continue hopLoop;
|
|
913
1081
|
}
|
|
914
1082
|
}
|
|
915
|
-
|
|
916
|
-
// Ensure at least one TEXT_DELTA is emitted for this turn if text was produced
|
|
917
|
-
if (!textDeltaEmitted && aggregatedResponseText) {
|
|
918
|
-
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TEXT_DELTA, aggregatedResponseText);
|
|
1083
|
+
break hopLoop;
|
|
919
1084
|
}
|
|
1085
|
+
if (resolution)
|
|
1086
|
+
this.turnResolution = resolution;
|
|
1087
|
+
// The step's usage, counted once (D4c).
|
|
1088
|
+
if (stepUsage)
|
|
1089
|
+
addUsage(aggregatedUsage, stepUsage);
|
|
1090
|
+
// The step boundary (D4d): the step's own text, finish reason, hop and usage.
|
|
1091
|
+
const stepPayload = {
|
|
1092
|
+
stepIndex: safetyBreak - 1,
|
|
1093
|
+
text: currentIterationTextResponse,
|
|
1094
|
+
finishReason: stepFinishReason,
|
|
1095
|
+
providerId: modelTargetInfo.providerId,
|
|
1096
|
+
modelId: modelTargetInfo.modelId,
|
|
1097
|
+
hop: resolution?.hop ?? 0,
|
|
1098
|
+
...(stepUsage ? { usage: stepUsage } : {}),
|
|
1099
|
+
...(stepFinalChunk?.modelId ? { responseModel: stepFinalChunk.modelId } : {}),
|
|
1100
|
+
...(stepFinalChunk?.serviceTier ? { serviceTier: stepFinalChunk.serviceTier } : {}),
|
|
1101
|
+
...(stepFinalChunk?.id ? { providerMessageId: stepFinalChunk.id } : {}),
|
|
1102
|
+
...(stepFinalChunk?.cacheDiagnostics !== undefined ? { cacheDiagnostics: stepFinalChunk.cacheDiagnostics } : {}),
|
|
1103
|
+
...(stepStructuredOutput !== undefined ? { structuredOutput: stepStructuredOutput } : {}),
|
|
1104
|
+
};
|
|
1105
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.STEP_FINISHED, stepPayload, {
|
|
1106
|
+
...(stepFinishReason ? { finishReason: stepFinishReason } : {}),
|
|
1107
|
+
...(stepUsage ? { usage: stepUsage } : {}),
|
|
1108
|
+
});
|
|
920
1109
|
const assistantMessage = {
|
|
921
1110
|
role: 'assistant',
|
|
922
1111
|
content: currentIterationTextResponse || null,
|
|
@@ -968,14 +1157,21 @@ export class GMI {
|
|
|
968
1157
|
toolExecutionResults.push(...this.resultsForUnfinishedToolCalls(currentIterationToolCallRequests, toolExecutionResults.length, callInFlight, error));
|
|
969
1158
|
}
|
|
970
1159
|
// Recorded on success and failure alike, so the history this turn
|
|
971
|
-
// leaves answers every call its assistant message declared.
|
|
1160
|
+
// leaves answers every call its assistant message declared. Each
|
|
1161
|
+
// recorded result is also published as a TOOL_RESULT chunk (D4e).
|
|
972
1162
|
for (const tcResult of toolExecutionResults) {
|
|
973
1163
|
turnMessages.push(this.conversationHistoryManager.updateWithToolResult(tcResult));
|
|
1164
|
+
const toolResultPayload = {
|
|
1165
|
+
toolCallId: tcResult.toolCallId,
|
|
1166
|
+
name: tcResult.toolName,
|
|
1167
|
+
result: tcResult.output,
|
|
1168
|
+
isError: tcResult.isError === true,
|
|
1169
|
+
...(tcResult.errorDetails !== undefined ? { errorDetails: tcResult.errorDetails } : {}),
|
|
1170
|
+
};
|
|
1171
|
+
yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TOOL_RESULT, toolResultPayload);
|
|
974
1172
|
}
|
|
975
1173
|
if (toolRoundFailure)
|
|
976
1174
|
throw toolRoundFailure.error;
|
|
977
|
-
currentIterationTextResponse = ""; // Reset for next iteration if any
|
|
978
|
-
currentIterationToolCallRequests = []; // Reset
|
|
979
1175
|
if (ownsState())
|
|
980
1176
|
this.state = GMIPrimeState.PROCESSING;
|
|
981
1177
|
continue main_processing_loop;
|
|
@@ -1004,7 +1200,9 @@ export class GMI {
|
|
|
1004
1200
|
return finalTurnOutput; // Return the aggregated output
|
|
1005
1201
|
}
|
|
1006
1202
|
catch (error) {
|
|
1007
|
-
|
|
1203
|
+
// A GMIError keeps its code (an in-band provider error stays
|
|
1204
|
+
// LLM_PROVIDER_ERROR); anything else is a processing error (D4f).
|
|
1205
|
+
const gmiError = createGMIErrorFromError(error, error instanceof GMIError ? error.code : GMIErrorCode.GMI_PROCESSING_ERROR, { turnId }, `Error in GMI turn '${turnId}'.`);
|
|
1008
1206
|
if (ownsState())
|
|
1009
1207
|
this.state = GMIPrimeState.ERRORED;
|
|
1010
1208
|
lastErrorForOutput = { code: gmiError.code, message: gmiError.message, details: gmiError.details };
|