@framers/agentos 0.10.22 → 0.10.24
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 +1 -1
- package/dist/api/AgentOS.d.ts.map +1 -1
- package/dist/api/AgentOS.js +3 -2
- package/dist/api/AgentOS.js.map +1 -1
- package/dist/api/agent.d.ts.map +1 -1
- package/dist/api/agent.js +25 -10
- package/dist/api/agent.js.map +1 -1
- package/dist/api/embedText.d.ts.map +1 -1
- package/dist/api/embedText.js +53 -17
- package/dist/api/embedText.js.map +1 -1
- package/dist/api/generateImage.d.ts.map +1 -1
- package/dist/api/generateImage.js +51 -7
- package/dist/api/generateImage.js.map +1 -1
- package/dist/api/generateText.d.ts +50 -7
- package/dist/api/generateText.d.ts.map +1 -1
- package/dist/api/generateText.js +265 -35
- package/dist/api/generateText.js.map +1 -1
- package/dist/api/model.d.ts +25 -1
- package/dist/api/model.d.ts.map +1 -1
- package/dist/api/model.js +108 -3
- package/dist/api/model.js.map +1 -1
- package/dist/api/runtime/SelfImprovementSessionManager.d.ts +34 -2
- package/dist/api/runtime/SelfImprovementSessionManager.d.ts.map +1 -1
- package/dist/api/runtime/SelfImprovementSessionManager.js +119 -33
- package/dist/api/runtime/SelfImprovementSessionManager.js.map +1 -1
- package/dist/api/runtime/global-default.d.ts +9 -1
- package/dist/api/runtime/global-default.d.ts.map +1 -1
- package/dist/api/runtime/global-default.js.map +1 -1
- package/dist/api/runtime/provider-defaults.d.ts.map +1 -1
- package/dist/api/runtime/provider-defaults.js +10 -4
- package/dist/api/runtime/provider-defaults.js.map +1 -1
- package/dist/api/runtime/tool-emulation/history.d.ts +16 -0
- package/dist/api/runtime/tool-emulation/history.d.ts.map +1 -0
- package/dist/api/runtime/tool-emulation/history.js +89 -0
- package/dist/api/runtime/tool-emulation/history.js.map +1 -0
- package/dist/api/runtime/tool-emulation/index.d.ts +1 -0
- package/dist/api/runtime/tool-emulation/index.d.ts.map +1 -1
- package/dist/api/runtime/tool-emulation/index.js +1 -0
- package/dist/api/runtime/tool-emulation/index.js.map +1 -1
- package/dist/api/runtime/tool-emulation/loop.d.ts +6 -0
- package/dist/api/runtime/tool-emulation/loop.d.ts.map +1 -1
- package/dist/api/runtime/tool-emulation/loop.js +1 -0
- package/dist/api/runtime/tool-emulation/loop.js.map +1 -1
- package/dist/api/sessionTranscript.d.ts +34 -0
- package/dist/api/sessionTranscript.d.ts.map +1 -1
- package/dist/api/sessionTranscript.js +61 -1
- package/dist/api/sessionTranscript.js.map +1 -1
- package/dist/api/streamText.d.ts.map +1 -1
- package/dist/api/streamText.js +120 -24
- package/dist/api/streamText.js.map +1 -1
- package/dist/api/types/AgentOSInput.d.ts +11 -0
- package/dist/api/types/AgentOSInput.d.ts.map +1 -1
- package/dist/api/types.d.ts +14 -6
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/cognition/emergent/AdaptPersonalityTool.d.ts +13 -4
- package/dist/cognition/emergent/AdaptPersonalityTool.d.ts.map +1 -1
- package/dist/cognition/emergent/AdaptPersonalityTool.js +32 -23
- package/dist/cognition/emergent/AdaptPersonalityTool.js.map +1 -1
- package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts +18 -6
- package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentCapabilityEngine.js.map +1 -1
- package/dist/cognition/emergent/SelfEvaluateTool.d.ts +10 -3
- package/dist/cognition/emergent/SelfEvaluateTool.d.ts.map +1 -1
- package/dist/cognition/emergent/SelfEvaluateTool.js +3 -2
- package/dist/cognition/emergent/SelfEvaluateTool.js.map +1 -1
- package/dist/cognition/substrate/CognitiveMemoryBridge.d.ts +51 -2
- package/dist/cognition/substrate/CognitiveMemoryBridge.d.ts.map +1 -1
- package/dist/cognition/substrate/CognitiveMemoryBridge.js +84 -2
- package/dist/cognition/substrate/CognitiveMemoryBridge.js.map +1 -1
- package/dist/cognition/substrate/ConversationHistoryManager.d.ts +8 -4
- package/dist/cognition/substrate/ConversationHistoryManager.d.ts.map +1 -1
- package/dist/cognition/substrate/ConversationHistoryManager.js +17 -5
- package/dist/cognition/substrate/ConversationHistoryManager.js.map +1 -1
- package/dist/cognition/substrate/GMI.d.ts +92 -5
- package/dist/cognition/substrate/GMI.d.ts.map +1 -1
- package/dist/cognition/substrate/GMI.js +291 -65
- package/dist/cognition/substrate/GMI.js.map +1 -1
- package/dist/cognition/substrate/GMIManager.d.ts +12 -0
- package/dist/cognition/substrate/GMIManager.d.ts.map +1 -1
- package/dist/cognition/substrate/GMIManager.js +22 -16
- package/dist/cognition/substrate/GMIManager.js.map +1 -1
- package/dist/cognition/substrate/IGMI.d.ts +23 -0
- package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
- package/dist/cognition/substrate/IGMI.js.map +1 -1
- package/dist/cognition/substrate/MetapromptExecutor.d.ts +183 -28
- package/dist/cognition/substrate/MetapromptExecutor.d.ts.map +1 -1
- package/dist/cognition/substrate/MetapromptExecutor.js +431 -108
- package/dist/cognition/substrate/MetapromptExecutor.js.map +1 -1
- package/dist/cognition/substrate/personas/IPersonaDefinition.d.ts +5 -0
- package/dist/cognition/substrate/personas/IPersonaDefinition.d.ts.map +1 -1
- package/dist/cognition/substrate/personas/PersonaValidation.d.ts.map +1 -1
- package/dist/cognition/substrate/personas/PersonaValidation.js +30 -0
- package/dist/cognition/substrate/personas/PersonaValidation.js.map +1 -1
- package/dist/cognition/substrate/personas/SoulLoader.d.ts +14 -1
- package/dist/cognition/substrate/personas/SoulLoader.d.ts.map +1 -1
- package/dist/cognition/substrate/personas/SoulLoader.js +45 -12
- package/dist/cognition/substrate/personas/SoulLoader.js.map +1 -1
- package/dist/cognition/substrate/personas/definitions/default_assistant_persona.json +3 -35
- package/dist/cognition/substrate/personas/hexaco.d.ts +34 -0
- package/dist/cognition/substrate/personas/hexaco.d.ts.map +1 -0
- package/dist/cognition/substrate/personas/hexaco.js +66 -0
- package/dist/cognition/substrate/personas/hexaco.js.map +1 -0
- package/dist/cognition/substrate/userFeedback.d.ts +69 -0
- package/dist/cognition/substrate/userFeedback.d.ts.map +1 -0
- package/dist/cognition/substrate/userFeedback.js +107 -0
- package/dist/cognition/substrate/userFeedback.js.map +1 -0
- package/dist/core/conversation/ConversationMessage.d.ts +9 -0
- package/dist/core/conversation/ConversationMessage.d.ts.map +1 -1
- package/dist/core/conversation/ConversationMessage.js.map +1 -1
- package/dist/core/llm/IPromptEngine.d.ts +9 -0
- package/dist/core/llm/IPromptEngine.d.ts.map +1 -1
- package/dist/core/llm/IPromptEngine.js.map +1 -1
- package/dist/core/llm/PromptEngine.d.ts +30 -1
- package/dist/core/llm/PromptEngine.d.ts.map +1 -1
- package/dist/core/llm/PromptEngine.js +229 -92
- package/dist/core/llm/PromptEngine.js.map +1 -1
- package/dist/core/llm/providers/AIModelProviderManager.d.ts +20 -0
- package/dist/core/llm/providers/AIModelProviderManager.d.ts.map +1 -1
- package/dist/core/llm/providers/AIModelProviderManager.js +35 -5
- package/dist/core/llm/providers/AIModelProviderManager.js.map +1 -1
- package/dist/core/llm/providers/IProvider.d.ts +28 -8
- package/dist/core/llm/providers/IProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts +135 -49
- package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/AnthropicProvider.js +323 -114
- package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js +15 -1
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts +1 -1
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.js +51 -24
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/GeminiProvider.d.ts +25 -6
- package/dist/core/llm/providers/implementations/GeminiProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/GeminiProvider.js +108 -90
- package/dist/core/llm/providers/implementations/GeminiProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/MistralProvider.d.ts +11 -0
- package/dist/core/llm/providers/implementations/MistralProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/MistralProvider.js +32 -2
- package/dist/core/llm/providers/implementations/MistralProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/OllamaProvider.js +7 -7
- package/dist/core/llm/providers/implementations/OllamaProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts +53 -2
- package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OpenAIProvider.js +84 -17
- package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js +8 -3
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/TogetherProvider.d.ts +3 -3
- package/dist/core/llm/providers/implementations/TogetherProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/TogetherProvider.js +25 -13
- package/dist/core/llm/providers/implementations/TogetherProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/cacheLeakDetector.d.ts +15 -7
- package/dist/core/llm/providers/implementations/cacheLeakDetector.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/cacheLeakDetector.js +12 -7
- package/dist/core/llm/providers/implementations/cacheLeakDetector.js.map +1 -1
- package/dist/core/llm/providers/model-cache-capabilities.d.ts +24 -9
- package/dist/core/llm/providers/model-cache-capabilities.d.ts.map +1 -1
- package/dist/core/llm/providers/model-cache-capabilities.js +59 -44
- package/dist/core/llm/providers/model-cache-capabilities.js.map +1 -1
- package/dist/core/llm/providers/model-effort.d.ts +14 -0
- package/dist/core/llm/providers/model-effort.d.ts.map +1 -1
- package/dist/core/llm/providers/model-effort.js +22 -0
- package/dist/core/llm/providers/model-effort.js.map +1 -1
- package/dist/core/llm/providers/model-forced-tool-choice.d.ts +13 -6
- package/dist/core/llm/providers/model-forced-tool-choice.d.ts.map +1 -1
- package/dist/core/llm/providers/model-forced-tool-choice.js +14 -7
- package/dist/core/llm/providers/model-forced-tool-choice.js.map +1 -1
- package/dist/core/llm/providers/model-thinking.d.ts +64 -13
- package/dist/core/llm/providers/model-thinking.d.ts.map +1 -1
- package/dist/core/llm/providers/model-thinking.js +52 -12
- package/dist/core/llm/providers/model-thinking.js.map +1 -1
- package/dist/core/llm/providers/openrouter-only-params.d.ts +26 -7
- package/dist/core/llm/providers/openrouter-only-params.d.ts.map +1 -1
- package/dist/core/llm/providers/openrouter-only-params.js +58 -9
- package/dist/core/llm/providers/openrouter-only-params.js.map +1 -1
- package/dist/core/llm/providers/url-secrets.d.ts +42 -0
- package/dist/core/llm/providers/url-secrets.d.ts.map +1 -0
- package/dist/core/llm/providers/url-secrets.js +64 -0
- package/dist/core/llm/providers/url-secrets.js.map +1 -0
- package/dist/core/llm/streaming/StreamingBatcher.d.ts +4 -3
- package/dist/core/llm/streaming/StreamingBatcher.d.ts.map +1 -1
- package/dist/core/llm/streaming/StreamingBatcher.js +8 -3
- package/dist/core/llm/streaming/StreamingBatcher.js.map +1 -1
- package/dist/core/safety/LLMProviderHealthRegistry.d.ts +4 -0
- package/dist/core/safety/LLMProviderHealthRegistry.d.ts.map +1 -1
- package/dist/core/safety/LLMProviderHealthRegistry.js +26 -0
- package/dist/core/safety/LLMProviderHealthRegistry.js.map +1 -1
- package/dist/core/utils/sha256.d.ts +19 -0
- package/dist/core/utils/sha256.d.ts.map +1 -0
- package/dist/core/utils/sha256.js +101 -0
- package/dist/core/utils/sha256.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestration/pipeline/query/types.js +3 -3
- package/dist/orchestration/pipeline/query/types.js.map +1 -1
- package/dist/orchestration/planning/ProviderAssignmentEngine.js +2 -2
- package/dist/orchestration/planning/ProviderAssignmentEngine.js.map +1 -1
- package/package.json +1 -1
|
@@ -17,7 +17,46 @@
|
|
|
17
17
|
* @implements {IPromptEngine}
|
|
18
18
|
*/
|
|
19
19
|
import { ContextualElementType, PromptEngineError, } from './IPromptEngine.js';
|
|
20
|
+
import { resolveCacheCapabilities } from './providers/model-cache-capabilities.js';
|
|
20
21
|
import { MessageRole } from '../conversation/ConversationMessage.js'; // Corrected import: Used alias and added MessageRole
|
|
22
|
+
import { sha256Hex } from '../utils/sha256.js';
|
|
23
|
+
/**
|
|
24
|
+
* Text of `value` for a prompt cache key, built by JSON.stringify so each
|
|
25
|
+
* value serializes as prompt construction's JSON copy sees it (a `toJSON`
|
|
26
|
+
* hook gets its property key; functions and undefined drop out). Three kinds
|
|
27
|
+
* of value that JSON cannot carry as-is become placeholders: a bigint, an
|
|
28
|
+
* object seen before (so a cycle cannot throw), and a typed array (hashed, so
|
|
29
|
+
* large audio or image bytes are not turned into text). Each placeholder is
|
|
30
|
+
* also listed after the JSON with the position it took, so an input string
|
|
31
|
+
* that looks like a placeholder never reads as one.
|
|
32
|
+
*/
|
|
33
|
+
function cacheKeyText(value) {
|
|
34
|
+
const seen = new Map();
|
|
35
|
+
const placeholders = [];
|
|
36
|
+
let position = 0;
|
|
37
|
+
const text = JSON.stringify(value, (_key, current) => {
|
|
38
|
+
position += 1;
|
|
39
|
+
const placeholder = (description) => {
|
|
40
|
+
placeholders.push(`${position}:${description}`);
|
|
41
|
+
return `\u0000${description}`;
|
|
42
|
+
};
|
|
43
|
+
if (typeof current === 'bigint')
|
|
44
|
+
return placeholder(`bigint:${current}`);
|
|
45
|
+
if (current === null || typeof current !== 'object')
|
|
46
|
+
return current;
|
|
47
|
+
if (ArrayBuffer.isView(current)) {
|
|
48
|
+
const digest = sha256Hex(new Uint8Array(current.buffer, current.byteOffset, current.byteLength));
|
|
49
|
+
return placeholder(`bytes:${current.constructor?.name ?? 'view'}:${digest}`);
|
|
50
|
+
}
|
|
51
|
+
const order = seen.get(current);
|
|
52
|
+
if (order !== undefined)
|
|
53
|
+
return placeholder(`ref:${order}`);
|
|
54
|
+
seen.set(current, seen.size);
|
|
55
|
+
return current;
|
|
56
|
+
});
|
|
57
|
+
// JSON escapes control characters, so a raw \u0001 cannot occur in `text`.
|
|
58
|
+
return `${text ?? 'undefined'}\u0001${placeholders.join(',')}`;
|
|
59
|
+
}
|
|
21
60
|
/**
|
|
22
61
|
* Comprehensive implementation of the IPromptEngine interface, providing
|
|
23
62
|
* sophisticated adaptive prompting capabilities for AgentOS GMIs.
|
|
@@ -32,6 +71,10 @@ export class PromptEngine {
|
|
|
32
71
|
this.statistics = this.getInitialStatistics();
|
|
33
72
|
this.defaultTemplates = {
|
|
34
73
|
'openai_chat': this.createOpenAIChatTemplate(),
|
|
74
|
+
// GeminiProvider takes OpenAI-style ChatMessage[] and converts it to
|
|
75
|
+
// Gemini contents itself, so a caller that names this format gets the
|
|
76
|
+
// chat template rather than an empty prompt.
|
|
77
|
+
'google_gemini': this.createOpenAIChatTemplate(),
|
|
35
78
|
'anthropic_messages': this.createAnthropicMessagesTemplate(),
|
|
36
79
|
'generic_completion': this.createGenericCompletionTemplate(),
|
|
37
80
|
};
|
|
@@ -110,6 +153,10 @@ export class PromptEngine {
|
|
|
110
153
|
this.statistics.cacheMisses++;
|
|
111
154
|
}
|
|
112
155
|
try {
|
|
156
|
+
// Copied now, in the same step as the cache key and before the first
|
|
157
|
+
// await, so the key and the prompt describe the same input even when
|
|
158
|
+
// the caller changes its components object while this call runs.
|
|
159
|
+
const components = JSON.parse(JSON.stringify(baseComponents));
|
|
113
160
|
let selectedElements = [];
|
|
114
161
|
// Corrected: use contextualPromptElements directly from IPersonaDefinition
|
|
115
162
|
if (executionContext?.activePersona?.contextualPromptElements) {
|
|
@@ -128,7 +175,7 @@ export class PromptEngine {
|
|
|
128
175
|
this.recordPerformanceTimer(timerId, Date.now() - constructionStart); // Pass actual duration
|
|
129
176
|
selectedElements.forEach(el => this.statistics.contextualElementSelections[el.id || el.type] = (this.statistics.contextualElementSelections[el.id || el.type] || 0) + 1);
|
|
130
177
|
}
|
|
131
|
-
const augmentedComponents = this.augmentBaseComponents(
|
|
178
|
+
const augmentedComponents = this.augmentBaseComponents(components, selectedElements, executionContext);
|
|
132
179
|
const timerIdBudget = 'tokenBudgeting';
|
|
133
180
|
const budgetStart = Date.now();
|
|
134
181
|
this.startPerformanceTimer(timerIdBudget);
|
|
@@ -259,7 +306,7 @@ export class PromptEngine {
|
|
|
259
306
|
if (components.userInput)
|
|
260
307
|
estimatedTokens += await this.estimateTokenCount(components.userInput, modelTargetInfo.modelId);
|
|
261
308
|
if (components.conversationHistory)
|
|
262
|
-
estimatedTokens += await this.calculateTokensForMessages(components.conversationHistory, modelTargetInfo.modelId);
|
|
309
|
+
estimatedTokens += await this.calculateTokensForMessages(components.conversationHistory, modelTargetInfo.modelId, this.thinkingSentFor(components.conversationHistory, modelTargetInfo));
|
|
263
310
|
if (estimatedTokens > modelTargetInfo.maxContextTokens) {
|
|
264
311
|
issues.push({ type: 'warning', code: 'POTENTIAL_TOKEN_OVERFLOW', message: `Estimated initial token count (${estimatedTokens}) exceeds model's max context (${modelTargetInfo.maxContextTokens}). Relying on truncation/summarization.`, component: 'OverallPrompt' });
|
|
265
312
|
}
|
|
@@ -347,40 +394,56 @@ export class PromptEngine {
|
|
|
347
394
|
throw new PromptEngineError('Invalid `tokenCounting` strategy in configuration.', 'INVALID_CONFIG_PARAM', 'Initialization');
|
|
348
395
|
}
|
|
349
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Builds the key a constructed prompt is cached under. A cached result is
|
|
399
|
+
* served to any later call with the same key, so the key covers everything
|
|
400
|
+
* that shapes the prompt: every component (the whole history, retrieved and
|
|
401
|
+
* memory context, tools), the model target, the template, and the
|
|
402
|
+
* execution context that templates and contextual elements read (the
|
|
403
|
+
* persona definition, mood, task, skill level, language, signals,
|
|
404
|
+
* preferences). The working memory handle is left out, since contextual
|
|
405
|
+
* criteria do not query it. The key is a SHA-256 digest, so it carries none
|
|
406
|
+
* of that content into logs.
|
|
407
|
+
*/
|
|
350
408
|
generateCacheKey(components, modelInfo, executionContext, templateName) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
409
|
+
const { workingMemory: _workingMemory, ...context } = executionContext ?? {};
|
|
410
|
+
const persona = executionContext?.activePersona;
|
|
411
|
+
// The components serialize at the root and as given, as prompt
|
|
412
|
+
// construction's JSON copy serializes them, so a toJSON hook or a getter
|
|
413
|
+
// it reads sees the same object and key.
|
|
414
|
+
const parts = [
|
|
415
|
+
cacheKeyText(components),
|
|
416
|
+
// A tool class may define its name and schema as getters, which JSON
|
|
417
|
+
// serialization of own properties would miss.
|
|
418
|
+
cacheKeyText(components.tools?.map((tool) => ({
|
|
419
|
+
id: tool.id,
|
|
420
|
+
name: tool.name,
|
|
421
|
+
description: tool.description,
|
|
422
|
+
inputSchema: tool.inputSchema,
|
|
423
|
+
}))),
|
|
424
|
+
cacheKeyText({
|
|
425
|
+
modelInfo,
|
|
426
|
+
templateName,
|
|
427
|
+
context,
|
|
428
|
+
// Read the way prompt construction reads them, so values a context
|
|
429
|
+
// or persona object holds on its prototype still count.
|
|
430
|
+
read: {
|
|
431
|
+
personaId: persona?.id,
|
|
432
|
+
contextualPromptElements: persona?.contextualPromptElements,
|
|
433
|
+
preferencePrompts: this.buildUserPreferencePrompts(executionContext),
|
|
434
|
+
currentMood: executionContext?.currentMood,
|
|
435
|
+
userSkillLevel: executionContext?.userSkillLevel,
|
|
436
|
+
taskHint: executionContext?.taskHint,
|
|
437
|
+
taskComplexity: executionContext?.taskComplexity,
|
|
438
|
+
language: executionContext?.language,
|
|
439
|
+
conversationSignals: executionContext?.conversationSignals,
|
|
440
|
+
},
|
|
441
|
+
}),
|
|
442
|
+
];
|
|
443
|
+
// Hashed synchronously: constructPrompt copies the components in the same
|
|
444
|
+
// step, so the key and the prompt describe the same input.
|
|
445
|
+
const digest = sha256Hex(parts.map((part) => `${part.length}:${part}`).join(''));
|
|
446
|
+
return `promptcache:${modelInfo.modelId}:${digest}`;
|
|
384
447
|
}
|
|
385
448
|
setupCacheEviction() {
|
|
386
449
|
/**
|
|
@@ -390,7 +453,7 @@ export class PromptEngine {
|
|
|
390
453
|
* No size‑based LRU yet; maxCacheSizeBytes reserved for future implementation.
|
|
391
454
|
*/
|
|
392
455
|
const interval = (this.config.performance.cacheTimeoutSeconds / 2) * 1000;
|
|
393
|
-
setInterval(() => {
|
|
456
|
+
const timer = setInterval(() => {
|
|
394
457
|
const now = Date.now();
|
|
395
458
|
for (const [key, entry] of this.cache.entries()) {
|
|
396
459
|
if ((now - entry.timestamp) > this.config.performance.cacheTimeoutSeconds * 1000) {
|
|
@@ -398,6 +461,8 @@ export class PromptEngine {
|
|
|
398
461
|
}
|
|
399
462
|
}
|
|
400
463
|
}, Math.max(interval, 60000));
|
|
464
|
+
// Eviction is housekeeping; it must not keep the process alive.
|
|
465
|
+
timer.unref?.();
|
|
401
466
|
}
|
|
402
467
|
startPerformanceTimer(timerId) {
|
|
403
468
|
if (!this.statistics.performanceTimers[timerId]) {
|
|
@@ -415,9 +480,9 @@ export class PromptEngine {
|
|
|
415
480
|
// averageTimeMs will be calculated in getEngineStatistics to avoid repetitive division
|
|
416
481
|
}
|
|
417
482
|
}
|
|
418
|
-
augmentBaseComponents(
|
|
483
|
+
augmentBaseComponents(augmented, selectedElements, executionContext) {
|
|
419
484
|
/**
|
|
420
|
-
* Applies selected contextual elements onto the
|
|
485
|
+
* Applies selected contextual elements onto `augmented`, the copy of the base components constructPrompt owns, and returns it.
|
|
421
486
|
* Merging Rules:
|
|
422
487
|
* - System prompt augmenters append as new system prompts with synthetic source tag.
|
|
423
488
|
* - Few‑shot examples accumulate under customComponents.fewShotExamples.
|
|
@@ -425,7 +490,6 @@ export class PromptEngine {
|
|
|
425
490
|
* - All other element types fall back to a dynamic bucket keyed by normalized type.
|
|
426
491
|
* Sorting: Final systemPrompts sorted ascending by priority to preserve intended ordering.
|
|
427
492
|
*/
|
|
428
|
-
const augmented = JSON.parse(JSON.stringify(base));
|
|
429
493
|
if (!augmented.systemPrompts)
|
|
430
494
|
augmented.systemPrompts = [];
|
|
431
495
|
if (!augmented.customComponents)
|
|
@@ -536,20 +600,6 @@ export class PromptEngine {
|
|
|
536
600
|
}
|
|
537
601
|
return undefined;
|
|
538
602
|
}
|
|
539
|
-
buildUserPreferenceCacheKey(preferences) {
|
|
540
|
-
if (!preferences || typeof preferences !== 'object') {
|
|
541
|
-
return undefined;
|
|
542
|
-
}
|
|
543
|
-
const verbosity = this.normalizeVerbosityPreference(preferences.verbosity);
|
|
544
|
-
const preferredFormat = this.normalizePreferredFormatPreference(preferences);
|
|
545
|
-
if (!verbosity && !preferredFormat) {
|
|
546
|
-
return undefined;
|
|
547
|
-
}
|
|
548
|
-
return JSON.stringify({
|
|
549
|
-
...(verbosity ? { verbosity } : {}),
|
|
550
|
-
...(preferredFormat ? { preferredFormat } : {}),
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
603
|
async applyTokenBudget(components, modelInfo, issues) {
|
|
554
604
|
/**
|
|
555
605
|
* Enforces token budget across prompt sections (system, userInput, history, RAG context, tools).
|
|
@@ -565,7 +615,8 @@ export class PromptEngine {
|
|
|
565
615
|
details: { truncatedComponents: [], summarizedComponents: [], removedComponents: [], originalEstimatedTokenCount: 0 }
|
|
566
616
|
};
|
|
567
617
|
const budget = modelInfo.optimalContextTokens || modelInfo.maxContextTokens;
|
|
568
|
-
|
|
618
|
+
const sendsThinking = this.thinkingSentFor(optimized.conversationHistory, modelInfo);
|
|
619
|
+
let currentTokens = await this.calculateTotalTokens(optimized, modelInfo.modelId, sendsThinking);
|
|
569
620
|
modifications.details.originalEstimatedTokenCount = currentTokens;
|
|
570
621
|
const budgets = {
|
|
571
622
|
system: 0.20 * budget,
|
|
@@ -575,38 +626,59 @@ export class PromptEngine {
|
|
|
575
626
|
tools: 0.10 * budget,
|
|
576
627
|
};
|
|
577
628
|
if (optimized.conversationHistory && optimized.conversationHistory.length > 0) {
|
|
578
|
-
|
|
629
|
+
const historyTokens = await this.calculateTokensForMessages(optimized.conversationHistory, modelInfo.modelId, sendsThinking);
|
|
579
630
|
if (historyTokens > budgets.history || currentTokens > budget) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
631
|
+
// The turn being answered stays whole: summarizing or truncating it
|
|
632
|
+
// would drop the request itself (a structured user message has no
|
|
633
|
+
// userInput copy) or split a tool call from its result. Only the
|
|
634
|
+
// messages before it are reduced, to what the budget leaves them.
|
|
635
|
+
const history = optimized.conversationHistory;
|
|
636
|
+
const turnCount = Math.min(Math.max(0, Math.floor(optimized.currentTurnMessageCount ?? 0)), history.length);
|
|
637
|
+
const turnMessages = history.slice(history.length - turnCount);
|
|
638
|
+
const earlier = history.slice(0, history.length - turnCount);
|
|
639
|
+
const turnTokens = await this.calculateTokensForMessages(turnMessages, modelInfo.modelId, sendsThinking);
|
|
640
|
+
const earlierTokens = historyTokens - turnTokens;
|
|
641
|
+
// The earlier history keeps its share of the history budget, and no
|
|
642
|
+
// more than fits beside everything else in the prompt.
|
|
643
|
+
const earlierBudget = Math.max(0, budgets.history - turnTokens);
|
|
644
|
+
const earlierTarget = Math.max(0, Math.min(earlierBudget, budget - (currentTokens - earlierTokens)));
|
|
645
|
+
// When the turn is kept, or the request travels as userInput, there
|
|
646
|
+
// is always a message to send, so the earlier history may go
|
|
647
|
+
// entirely.
|
|
648
|
+
const minEarlierKept = turnMessages.length > 0 || optimized.userInput ? 0 : 1;
|
|
649
|
+
let reducedEarlier = earlier;
|
|
650
|
+
if (earlier.length > 0 && earlierTarget <= 0 && minEarlierKept === 0) {
|
|
651
|
+
// Nothing earlier fits; a summary would still add a message.
|
|
652
|
+
reducedEarlier = [];
|
|
653
|
+
modifications.details.truncatedComponents.push('conversationHistory');
|
|
654
|
+
modifications.wasModified = true;
|
|
599
655
|
}
|
|
600
|
-
else {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
656
|
+
else if (earlier.length > 0) {
|
|
657
|
+
if (this.utilityAI && this.config.historyManagement.summarizationTriggerRatio > 0 && (historyTokens / budgets.history > this.config.historyManagement.summarizationTriggerRatio)) {
|
|
658
|
+
try {
|
|
659
|
+
const { summaryMessages } = await this.utilityAI.summarizeConversationHistory(earlier, earlierTarget, modelInfo, this.config.historyManagement.preserveImportantMessages);
|
|
660
|
+
reducedEarlier = summaryMessages;
|
|
661
|
+
modifications.details.summarizedComponents.push('conversationHistory');
|
|
662
|
+
modifications.wasModified = true;
|
|
663
|
+
}
|
|
664
|
+
catch (e) {
|
|
665
|
+
issues?.push({ type: 'warning', code: 'HISTORY_SUMMARIZATION_FAILED', message: `History summarization failed: ${e instanceof Error ? e.message : String(e)}` });
|
|
666
|
+
reducedEarlier = this.truncateMessages([...earlier], earlierTarget, (content) => this.estimateTokenCount(content, modelInfo.modelId), minEarlierKept, sendsThinking);
|
|
667
|
+
modifications.details.truncatedComponents.push('conversationHistory');
|
|
668
|
+
modifications.wasModified = true;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
reducedEarlier = this.truncateMessages([...earlier], earlierTarget, (content) => this.estimateTokenCount(content, modelInfo.modelId), minEarlierKept, sendsThinking);
|
|
673
|
+
if (reducedEarlier.length < earlier.length) {
|
|
674
|
+
modifications.details.truncatedComponents.push('conversationHistory');
|
|
675
|
+
modifications.wasModified = true;
|
|
676
|
+
}
|
|
605
677
|
}
|
|
606
|
-
const newHistoryTokens = await this.calculateTokensForMessages(optimized.conversationHistory, modelInfo.modelId);
|
|
607
|
-
currentTokens = currentTokens - historyTokens + newHistoryTokens;
|
|
608
|
-
historyTokens = newHistoryTokens;
|
|
609
678
|
}
|
|
679
|
+
optimized.conversationHistory = [...reducedEarlier, ...turnMessages];
|
|
680
|
+
const newEarlierTokens = await this.calculateTokensForMessages(reducedEarlier, modelInfo.modelId, sendsThinking);
|
|
681
|
+
currentTokens = currentTokens - earlierTokens + newEarlierTokens;
|
|
610
682
|
}
|
|
611
683
|
}
|
|
612
684
|
if (optimized.retrievedContext) {
|
|
@@ -647,14 +719,14 @@ export class PromptEngine {
|
|
|
647
719
|
}
|
|
648
720
|
return { optimizedComponents: optimized, modifications };
|
|
649
721
|
}
|
|
650
|
-
async calculateTotalTokens(components, modelId) {
|
|
722
|
+
async calculateTotalTokens(components, modelId, sendsThinking = () => false) {
|
|
651
723
|
let total = 0;
|
|
652
724
|
if (components.systemPrompts)
|
|
653
725
|
total += (await Promise.all(components.systemPrompts.map(sp => this.estimateTokenCount(sp.content, modelId)))).reduce((a, b) => a + b, 0);
|
|
654
726
|
if (components.userInput)
|
|
655
727
|
total += await this.estimateTokenCount(components.userInput, modelId);
|
|
656
728
|
if (components.conversationHistory)
|
|
657
|
-
total += await this.calculateTokensForMessages(components.conversationHistory, modelId);
|
|
729
|
+
total += await this.calculateTokensForMessages(components.conversationHistory, modelId, sendsThinking);
|
|
658
730
|
if (components.retrievedContext) {
|
|
659
731
|
const contextStr = typeof components.retrievedContext === 'string' ? components.retrievedContext : components.retrievedContext.map(r => r.content).join('\n');
|
|
660
732
|
total += await this.estimateTokenCount(contextStr, modelId);
|
|
@@ -663,7 +735,37 @@ export class PromptEngine {
|
|
|
663
735
|
total += components.tools.length * 50;
|
|
664
736
|
return total;
|
|
665
737
|
}
|
|
666
|
-
|
|
738
|
+
/**
|
|
739
|
+
* Tells which history messages' thinking the target model receives. Only
|
|
740
|
+
* Anthropic sends thinking back: all of it to models that keep prior
|
|
741
|
+
* thinking in context, otherwise only the latest assistant turn's. Other
|
|
742
|
+
* providers drop it. AnthropicProvider's
|
|
743
|
+
* `AGENTOS_ANTHROPIC_STRIP_PRIOR_THINKING` override (`1`/`true` strips,
|
|
744
|
+
* `0`/`false` replays) wins over the model's retention, as it does there.
|
|
745
|
+
*
|
|
746
|
+
* @param history The history the prompt is built from.
|
|
747
|
+
* @param modelInfo The target model.
|
|
748
|
+
* @returns Whether a message's thinking blocks reach the model.
|
|
749
|
+
*/
|
|
750
|
+
thinkingSentFor(history, modelInfo) {
|
|
751
|
+
if (modelInfo.providerId !== 'anthropic' || !history?.length)
|
|
752
|
+
return () => false;
|
|
753
|
+
const stripEnv = process.env.AGENTOS_ANTHROPIC_STRIP_PRIOR_THINKING;
|
|
754
|
+
const replaysAll = stripEnv === '1' || stripEnv === 'true'
|
|
755
|
+
? false
|
|
756
|
+
: stripEnv === '0' || stripEnv === 'false'
|
|
757
|
+
? true
|
|
758
|
+
: resolveCacheCapabilities(modelInfo.modelId).retainsPriorThinkingInContext;
|
|
759
|
+
if (replaysAll)
|
|
760
|
+
return () => true;
|
|
761
|
+
let lastAssistant;
|
|
762
|
+
for (const msg of history) {
|
|
763
|
+
if (msg.role === MessageRole.ASSISTANT)
|
|
764
|
+
lastAssistant = msg;
|
|
765
|
+
}
|
|
766
|
+
return (msg) => msg === lastAssistant;
|
|
767
|
+
}
|
|
768
|
+
async calculateTokensForMessages(messages, modelId, sendsThinking = () => false) {
|
|
667
769
|
if (!messages)
|
|
668
770
|
return 0;
|
|
669
771
|
let sum = 0;
|
|
@@ -691,15 +793,42 @@ export class PromptEngine {
|
|
|
691
793
|
// This case should not be common if msg.content is Array for multi-part, but as a fallback:
|
|
692
794
|
sum += await this.estimateTokenCount(JSON.stringify(msg.content), modelId);
|
|
693
795
|
}
|
|
796
|
+
// Thinking the target receives with the message is input too.
|
|
797
|
+
if (sendsThinking(msg)) {
|
|
798
|
+
for (const block of msg.thinkingBlocks ?? []) {
|
|
799
|
+
sum += await this.estimateTokenCount(block.type === 'thinking' ? block.thinking : block.data, modelId);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
694
802
|
sum += 5; // Overhead for role, name, etc.
|
|
695
803
|
}
|
|
696
804
|
return sum;
|
|
697
805
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
806
|
+
/**
|
|
807
|
+
* Drops the oldest messages until the rest fit `targetTokenCount`, keeping
|
|
808
|
+
* at least `minKept`. A tool result left first would reach the provider
|
|
809
|
+
* without the assistant call it answers, which chat APIs reject, so leading
|
|
810
|
+
* tool results go too.
|
|
811
|
+
*/
|
|
812
|
+
truncateMessages(messages, targetTokenCount, _estimateFn, minKept = 1, sendsThinking = () => false) {
|
|
813
|
+
// Rough estimate: four characters a token, over the content and any
|
|
814
|
+
// thinking the target receives with the message.
|
|
815
|
+
const roughTokens = (msg) => {
|
|
816
|
+
if (!msg)
|
|
817
|
+
return 0;
|
|
818
|
+
const contentChars = typeof msg.content === 'string'
|
|
819
|
+
? msg.content.length
|
|
820
|
+
: (Array.isArray(msg.content) ? JSON.stringify(msg.content).length : 50);
|
|
821
|
+
const thinkingChars = sendsThinking(msg)
|
|
822
|
+
? (msg.thinkingBlocks ?? []).reduce((chars, block) => chars + (block.type === 'thinking' ? block.thinking.length : block.data.length), 0)
|
|
823
|
+
: 0;
|
|
824
|
+
return (contentChars + thinkingChars) / 4;
|
|
825
|
+
};
|
|
826
|
+
let currentTokens = messages.reduce((sum, msg) => sum + roughTokens(msg), 0);
|
|
827
|
+
while (currentTokens > targetTokenCount && messages.length > minKept) {
|
|
828
|
+
currentTokens -= roughTokens(messages.shift());
|
|
829
|
+
}
|
|
830
|
+
while (messages.length > minKept && messages[0]?.role === MessageRole.TOOL) {
|
|
831
|
+
messages.shift();
|
|
703
832
|
}
|
|
704
833
|
return messages;
|
|
705
834
|
}
|
|
@@ -854,6 +983,11 @@ export class PromptEngine {
|
|
|
854
983
|
chatMsg.content = null;
|
|
855
984
|
}
|
|
856
985
|
}
|
|
986
|
+
// Anthropic rejects a tool-loop request whose earlier assistant
|
|
987
|
+
// turn lost its signed thinking, so the blocks ride along verbatim.
|
|
988
|
+
if (role === 'assistant' && msg.thinkingBlocks && msg.thinkingBlocks.length > 0) {
|
|
989
|
+
chatMsg.thinkingBlocks = [...msg.thinkingBlocks];
|
|
990
|
+
}
|
|
857
991
|
messages.push(chatMsg);
|
|
858
992
|
});
|
|
859
993
|
}
|
|
@@ -876,8 +1010,11 @@ export class PromptEngine {
|
|
|
876
1010
|
const ragContent = typeof components.retrievedContext === 'string' ? components.retrievedContext : components.retrievedContext.map(r => `Source: ${r.source}\nContent: ${r.content}`).join('\n\n');
|
|
877
1011
|
if (messages.length > 0 && messages[messages.length - 1].role === 'user') {
|
|
878
1012
|
const lastUserMsg = messages[messages.length - 1];
|
|
879
|
-
|
|
880
|
-
|
|
1013
|
+
// A multimodal message keeps its parts: the context goes in front
|
|
1014
|
+
// as a text part instead of flattening the parts into a string.
|
|
1015
|
+
lastUserMsg.content = Array.isArray(lastUserMsg.content)
|
|
1016
|
+
? [{ type: 'text', text: `Context:\n${ragContent}\n\nUser Query:` }, ...lastUserMsg.content]
|
|
1017
|
+
: `Context:\n${ragContent}\n\nUser Query: ${lastUserMsg.content}`;
|
|
881
1018
|
}
|
|
882
1019
|
else {
|
|
883
1020
|
messages.push({ role: 'user', content: `Based on the following context:\n${ragContent}\n\nPlease respond to the implicit or explicit user query.` });
|