@illuma-ai/agents 1.1.28 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/spawnPath.cjs +104 -0
- package/dist/cjs/common/spawnPath.cjs.map +1 -0
- package/dist/cjs/graphs/Graph.cjs +84 -33
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/HandoffRegistry.cjs +47 -8
- package/dist/cjs/graphs/HandoffRegistry.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +493 -267
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/phases/flushLoop.cjs +214 -0
- package/dist/cjs/graphs/phases/flushLoop.cjs.map +1 -0
- package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +102 -0
- package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -0
- package/dist/cjs/llm/bedrock/index.cjs +4 -3
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +113 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/memory/citations.cjs +69 -0
- package/dist/cjs/memory/citations.cjs.map +1 -0
- package/dist/cjs/memory/compositeBackend.cjs +60 -0
- package/dist/cjs/memory/compositeBackend.cjs.map +1 -0
- package/dist/cjs/memory/constants.cjs +232 -0
- package/dist/cjs/memory/constants.cjs.map +1 -0
- package/dist/cjs/memory/embeddings.cjs +151 -0
- package/dist/cjs/memory/embeddings.cjs.map +1 -0
- package/dist/cjs/memory/factory.cjs +95 -0
- package/dist/cjs/memory/factory.cjs.map +1 -0
- package/dist/cjs/memory/migrate.cjs +81 -0
- package/dist/cjs/memory/migrate.cjs.map +1 -0
- package/dist/cjs/memory/mmr.cjs +138 -0
- package/dist/cjs/memory/mmr.cjs.map +1 -0
- package/dist/cjs/memory/paths.cjs +217 -0
- package/dist/cjs/memory/paths.cjs.map +1 -0
- package/dist/cjs/memory/pgvectorStore.cjs +225 -0
- package/dist/cjs/memory/pgvectorStore.cjs.map +1 -0
- package/dist/cjs/memory/recallTracking.cjs +98 -0
- package/dist/cjs/memory/recallTracking.cjs.map +1 -0
- package/dist/cjs/memory/schema.sql +51 -0
- package/dist/cjs/memory/temporalDecay.cjs +118 -0
- package/dist/cjs/memory/temporalDecay.cjs.map +1 -0
- package/dist/cjs/nodes/ApprovalGateNode.cjs +1 -1
- package/dist/cjs/nodes/ApprovalGateNode.cjs.map +1 -1
- package/dist/cjs/prompts/memoryFlushPrompt.cjs +49 -0
- package/dist/cjs/prompts/memoryFlushPrompt.cjs.map +1 -0
- package/dist/cjs/run.cjs +16 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/AskUser.cjs +6 -1
- package/dist/cjs/tools/AskUser.cjs.map +1 -1
- package/dist/cjs/tools/BrowserTools.cjs +1 -1
- package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +127 -10
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/approval/constants.cjs +2 -2
- package/dist/cjs/tools/approval/constants.cjs.map +1 -1
- package/dist/cjs/tools/memory/index.cjs +58 -0
- package/dist/cjs/tools/memory/index.cjs.map +1 -0
- package/dist/cjs/tools/memory/memoryAppendTool.cjs +69 -0
- package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/memoryGetTool.cjs +49 -0
- package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/memorySearchTool.cjs +65 -0
- package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/shared.cjs +106 -0
- package/dist/cjs/tools/memory/shared.cjs.map +1 -0
- package/dist/cjs/types/graph.cjs.map +1 -1
- package/dist/cjs/utils/childAgentContext.cjs +242 -0
- package/dist/cjs/utils/childAgentContext.cjs.map +1 -0
- package/dist/cjs/utils/events.cjs +36 -7
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/finishReasons.cjs +44 -0
- package/dist/cjs/utils/finishReasons.cjs.map +1 -0
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/logging.cjs +34 -0
- package/dist/cjs/utils/logging.cjs.map +1 -0
- package/dist/cjs/utils/toolCallNormalization.cjs +250 -0
- package/dist/cjs/utils/toolCallNormalization.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/spawnPath.mjs +95 -0
- package/dist/esm/common/spawnPath.mjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +84 -33
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/HandoffRegistry.mjs +47 -8
- package/dist/esm/graphs/HandoffRegistry.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +493 -267
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/phases/flushLoop.mjs +209 -0
- package/dist/esm/graphs/phases/flushLoop.mjs.map +1 -0
- package/dist/esm/graphs/phases/memoryFlushPhase.mjs +99 -0
- package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -0
- package/dist/esm/llm/bedrock/index.mjs +4 -3
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/main.mjs +20 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/memory/citations.mjs +64 -0
- package/dist/esm/memory/citations.mjs.map +1 -0
- package/dist/esm/memory/compositeBackend.mjs +58 -0
- package/dist/esm/memory/compositeBackend.mjs.map +1 -0
- package/dist/esm/memory/constants.mjs +198 -0
- package/dist/esm/memory/constants.mjs.map +1 -0
- package/dist/esm/memory/embeddings.mjs +148 -0
- package/dist/esm/memory/embeddings.mjs.map +1 -0
- package/dist/esm/memory/factory.mjs +93 -0
- package/dist/esm/memory/factory.mjs.map +1 -0
- package/dist/esm/memory/migrate.mjs +78 -0
- package/dist/esm/memory/migrate.mjs.map +1 -0
- package/dist/esm/memory/mmr.mjs +130 -0
- package/dist/esm/memory/mmr.mjs.map +1 -0
- package/dist/esm/memory/paths.mjs +207 -0
- package/dist/esm/memory/paths.mjs.map +1 -0
- package/dist/esm/memory/pgvectorStore.mjs +223 -0
- package/dist/esm/memory/pgvectorStore.mjs.map +1 -0
- package/dist/esm/memory/recallTracking.mjs +94 -0
- package/dist/esm/memory/recallTracking.mjs.map +1 -0
- package/dist/esm/memory/schema.sql +51 -0
- package/dist/esm/memory/temporalDecay.mjs +110 -0
- package/dist/esm/memory/temporalDecay.mjs.map +1 -0
- package/dist/esm/nodes/ApprovalGateNode.mjs +1 -1
- package/dist/esm/nodes/ApprovalGateNode.mjs.map +1 -1
- package/dist/esm/prompts/memoryFlushPrompt.mjs +44 -0
- package/dist/esm/prompts/memoryFlushPrompt.mjs.map +1 -0
- package/dist/esm/run.mjs +16 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/AskUser.mjs +6 -1
- package/dist/esm/tools/AskUser.mjs.map +1 -1
- package/dist/esm/tools/BrowserTools.mjs +1 -1
- package/dist/esm/tools/BrowserTools.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +128 -11
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/approval/constants.mjs +2 -2
- package/dist/esm/tools/approval/constants.mjs.map +1 -1
- package/dist/esm/tools/memory/index.mjs +46 -0
- package/dist/esm/tools/memory/index.mjs.map +1 -0
- package/dist/esm/tools/memory/memoryAppendTool.mjs +67 -0
- package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -0
- package/dist/esm/tools/memory/memoryGetTool.mjs +47 -0
- package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -0
- package/dist/esm/tools/memory/memorySearchTool.mjs +63 -0
- package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -0
- package/dist/esm/tools/memory/shared.mjs +98 -0
- package/dist/esm/tools/memory/shared.mjs.map +1 -0
- package/dist/esm/types/graph.mjs.map +1 -1
- package/dist/esm/utils/childAgentContext.mjs +237 -0
- package/dist/esm/utils/childAgentContext.mjs.map +1 -0
- package/dist/esm/utils/events.mjs +36 -8
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/finishReasons.mjs +41 -0
- package/dist/esm/utils/finishReasons.mjs.map +1 -0
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/logging.mjs +31 -0
- package/dist/esm/utils/logging.mjs.map +1 -0
- package/dist/esm/utils/toolCallNormalization.mjs +247 -0
- package/dist/esm/utils/toolCallNormalization.mjs.map +1 -0
- package/dist/types/common/index.d.ts +1 -0
- package/dist/types/common/spawnPath.d.ts +59 -0
- package/dist/types/graphs/HandoffRegistry.d.ts +24 -7
- package/dist/types/graphs/MultiAgentGraph.d.ts +43 -23
- package/dist/types/graphs/phases/flushLoop.d.ts +106 -0
- package/dist/types/graphs/phases/memoryFlushPhase.d.ts +100 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/memory/__tests__/mockBackend.d.ts +40 -0
- package/dist/types/memory/citations.d.ts +39 -0
- package/dist/types/memory/compositeBackend.d.ts +30 -0
- package/dist/types/memory/constants.d.ts +121 -0
- package/dist/types/memory/embeddings.d.ts +15 -0
- package/dist/types/memory/factory.d.ts +23 -0
- package/dist/types/memory/index.d.ts +21 -0
- package/dist/types/memory/migrate.d.ts +14 -0
- package/dist/types/memory/mmr.d.ts +50 -0
- package/dist/types/memory/paths.d.ts +107 -0
- package/dist/types/memory/pgvectorStore.d.ts +56 -0
- package/dist/types/memory/recallTracking.d.ts +30 -0
- package/dist/types/memory/temporalDecay.d.ts +53 -0
- package/dist/types/memory/types.d.ts +182 -0
- package/dist/types/prompts/memoryFlushPrompt.d.ts +54 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/AskUser.d.ts +1 -1
- package/dist/types/tools/BrowserTools.d.ts +2 -2
- package/dist/types/tools/approval/constants.d.ts +2 -2
- package/dist/types/tools/memory/index.d.ts +39 -0
- package/dist/types/tools/memory/memoryAppendTool.d.ts +27 -0
- package/dist/types/tools/memory/memoryGetTool.d.ts +22 -0
- package/dist/types/tools/memory/memorySearchTool.d.ts +22 -0
- package/dist/types/tools/memory/shared.d.ts +106 -0
- package/dist/types/types/graph.d.ts +10 -3
- package/dist/types/utils/childAgentContext.d.ts +99 -0
- package/dist/types/utils/events.d.ts +21 -0
- package/dist/types/utils/finishReasons.d.ts +32 -0
- package/dist/types/utils/logging.d.ts +2 -0
- package/dist/types/utils/toolCallNormalization.d.ts +44 -0
- package/package.json +6 -4
- package/src/agents/AgentContext.ts +12 -4
- package/src/common/__tests__/enum.test.ts +4 -2
- package/src/common/__tests__/spawnPath.test.ts +110 -0
- package/src/common/index.ts +1 -0
- package/src/common/spawnPath.ts +101 -0
- package/src/graphs/Graph.ts +90 -47
- package/src/graphs/HandoffRegistry.ts +48 -17
- package/src/graphs/MultiAgentGraph.ts +588 -327
- package/src/graphs/__tests__/HandoffRegistry.test.ts +4 -1
- package/src/graphs/__tests__/multi-agent-delegate.test.ts +61 -16
- package/src/graphs/__tests__/multi-agent-edges.test.ts +4 -2
- package/src/graphs/__tests__/multi-agent-nested-subgraph.test.ts +221 -0
- package/src/graphs/__tests__/structured-output.integration.test.ts +212 -118
- package/src/graphs/contextManagement.e2e.test.ts +1 -1
- package/src/graphs/phases/__tests__/flushLoop.test.ts +264 -0
- package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +37 -0
- package/src/graphs/phases/__tests__/runMemoryFlush.test.ts +150 -0
- package/src/graphs/phases/flushLoop.ts +303 -0
- package/src/graphs/phases/memoryFlushPhase.ts +209 -0
- package/src/index.ts +30 -1
- package/src/llm/bedrock/index.ts +4 -5
- package/src/memory/__tests__/citations.test.ts +61 -0
- package/src/memory/__tests__/compositeBackend.test.ts +79 -0
- package/src/memory/__tests__/isolation.test.ts +206 -0
- package/src/memory/__tests__/mmr.test.ts +148 -0
- package/src/memory/__tests__/mockBackend.ts +161 -0
- package/src/memory/__tests__/paths.test.ts +168 -0
- package/src/memory/__tests__/recallTracking.test.ts +96 -0
- package/src/memory/__tests__/temporalDecay.test.ts +151 -0
- package/src/memory/citations.ts +80 -0
- package/src/memory/compositeBackend.ts +99 -0
- package/src/memory/constants.ts +229 -0
- package/src/memory/embeddings.ts +188 -0
- package/src/memory/factory.ts +111 -0
- package/src/memory/index.ts +46 -0
- package/src/memory/migrate.ts +116 -0
- package/src/memory/mmr.ts +161 -0
- package/src/memory/paths.ts +258 -0
- package/src/memory/pgvectorStore.ts +324 -0
- package/src/memory/recallTracking.ts +127 -0
- package/src/memory/schema.sql +51 -0
- package/src/memory/temporalDecay.ts +134 -0
- package/src/memory/types.ts +185 -0
- package/src/nodes/ApprovalGateNode.ts +4 -10
- package/src/nodes/__tests__/ApprovalGateNode.test.ts +11 -20
- package/src/prompts/memoryFlushPrompt.ts +78 -0
- package/src/run.ts +17 -6
- package/src/scripts/test-bedrock-handoff-autonomous.ts +56 -20
- package/src/specs/agent-handoffs-bedrock.integration.test.ts +8 -5
- package/src/specs/agent-handoffs.test.ts +8 -2
- package/src/tools/AskUser.ts +7 -2
- package/src/tools/BrowserTools.ts +3 -5
- package/src/tools/ToolNode.ts +150 -13
- package/src/tools/__tests__/ToolApproval.test.ts +22 -9
- package/src/tools/approval/__tests__/constants.test.ts +1 -1
- package/src/tools/approval/constants.ts +2 -2
- package/src/tools/memory/__tests__/memoryTools.test.ts +205 -0
- package/src/tools/memory/index.ts +96 -0
- package/src/tools/memory/memoryAppendTool.ts +101 -0
- package/src/tools/memory/memoryGetTool.ts +53 -0
- package/src/tools/memory/memorySearchTool.ts +80 -0
- package/src/tools/memory/shared.ts +169 -0
- package/src/tools/search/search.test.ts +6 -1
- package/src/types/graph.ts +10 -3
- package/src/utils/__tests__/childAgentContext.test.ts +217 -0
- package/src/utils/__tests__/finishReasons.test.ts +55 -0
- package/src/utils/__tests__/toolCallNormalization.test.ts +181 -0
- package/src/utils/childAgentContext.ts +259 -0
- package/src/utils/events.ts +37 -7
- package/src/utils/finishReasons.ts +40 -0
- package/src/utils/llm.ts +0 -1
- package/src/utils/logging.ts +45 -8
- package/src/utils/toolCallNormalization.ts +271 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { ToolMessage, AIMessage } from '@langchain/core/messages';
|
|
2
|
+
import { DEFAULT_MAX_FLUSH_ITERATIONS, MEMORY_APPEND_TOOL_NAME, SILENT_REPLY_TOKEN } from '../../memory/constants.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Agentic tool-execution loop for the memory-flush reflection turn.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from {@link runMemoryFlush} so the loop primitive is
|
|
8
|
+
* testable in isolation and reusable from any reflection-style phase
|
|
9
|
+
* that wants "invoke → execute tool_calls → feed results back → repeat".
|
|
10
|
+
*
|
|
11
|
+
* Design:
|
|
12
|
+
* - Bounded iterations — caller passes `maxIterations` (default
|
|
13
|
+
* {@link DEFAULT_MAX_FLUSH_ITERATIONS}). The loop exits as soon as
|
|
14
|
+
* the model stops emitting `tool_calls`, hits the cap, or the reply
|
|
15
|
+
* matches {@link SILENT_REPLY_TOKEN}.
|
|
16
|
+
* - Rich result — returns iteration count, attempted/successful
|
|
17
|
+
* appends, per-tool errors, silent-reply flag, and raw final text.
|
|
18
|
+
* The caller logs this; nothing is swallowed.
|
|
19
|
+
* - Self-contained tool execution — does not depend on LangGraph's
|
|
20
|
+
* ToolNode or any graph runtime. The only contract is the
|
|
21
|
+
* LangChain v0.3 `StructuredToolInterface.invoke(args)` shape.
|
|
22
|
+
* - Tool errors are captured as {@link ToolMessage}s with the raw
|
|
23
|
+
* JSON error, so the model can read them and self-correct (e.g.
|
|
24
|
+
* retry with a different path if the schema rejected its first
|
|
25
|
+
* attempt).
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Extract a flat text string from any AIMessage content shape
|
|
29
|
+
* (string, array of content blocks, etc).
|
|
30
|
+
*/
|
|
31
|
+
function extractText(message) {
|
|
32
|
+
const c = message.content;
|
|
33
|
+
if (typeof c === 'string')
|
|
34
|
+
return c;
|
|
35
|
+
if (!Array.isArray(c))
|
|
36
|
+
return '';
|
|
37
|
+
return c
|
|
38
|
+
.map((block) => {
|
|
39
|
+
if (typeof block === 'string')
|
|
40
|
+
return block;
|
|
41
|
+
if (block && typeof block === 'object' && 'text' in block) {
|
|
42
|
+
return String(block.text ?? '');
|
|
43
|
+
}
|
|
44
|
+
return '';
|
|
45
|
+
})
|
|
46
|
+
.join('')
|
|
47
|
+
.trim();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parse a tool result string. Memory tools return JSON with
|
|
51
|
+
* `{ ok: boolean, error?: string, path?: string }`. Non-JSON payloads
|
|
52
|
+
* are treated as opaque success strings.
|
|
53
|
+
*/
|
|
54
|
+
function parseToolResult(raw) {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(raw);
|
|
57
|
+
if (parsed && typeof parsed === 'object' && 'ok' in parsed) {
|
|
58
|
+
return {
|
|
59
|
+
ok: Boolean(parsed.ok),
|
|
60
|
+
error: typeof parsed.error === 'string' ? parsed.error : undefined,
|
|
61
|
+
path: typeof parsed.path === 'string' ? parsed.path : undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { ok: true };
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return { ok: true };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Bind a tool array to a model if `bindTools` exists on the model.
|
|
72
|
+
* Exported so the caller (runMemoryFlush) can do it once and pass the
|
|
73
|
+
* bound model in, keeping this loop free of LangChain model-specific
|
|
74
|
+
* extensions.
|
|
75
|
+
*/
|
|
76
|
+
function bindToolsIfSupported(
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
model, tools) {
|
|
79
|
+
if (typeof model?.bindTools === 'function') {
|
|
80
|
+
return model.bindTools(tools);
|
|
81
|
+
}
|
|
82
|
+
return model;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Run the agentic reflection loop until the model stops emitting
|
|
86
|
+
* tool_calls, the iteration cap is hit, or the final reply matches
|
|
87
|
+
* {@link SILENT_REPLY_TOKEN}.
|
|
88
|
+
*/
|
|
89
|
+
async function runFlushLoop(params) {
|
|
90
|
+
const { model, tools, initialMessages, maxIterations = DEFAULT_MAX_FLUSH_ITERATIONS, onIteration, } = params;
|
|
91
|
+
const toolMap = new Map();
|
|
92
|
+
for (const t of tools) {
|
|
93
|
+
toolMap.set(t.name, t);
|
|
94
|
+
}
|
|
95
|
+
const messages = [...initialMessages];
|
|
96
|
+
const toolErrors = [];
|
|
97
|
+
let appendsAttempted = 0;
|
|
98
|
+
let appendsSucceeded = 0;
|
|
99
|
+
let iterations = 0;
|
|
100
|
+
let finalText = '';
|
|
101
|
+
let hitIterationCap = false;
|
|
102
|
+
// Tracks whether the most recent iteration ended with unresolved
|
|
103
|
+
// tool_calls. If we exit the while via the cap (not via the natural
|
|
104
|
+
// `break`), this stays true and we flag hitIterationCap.
|
|
105
|
+
let lastIterationHadPendingCalls = false;
|
|
106
|
+
while (iterations < maxIterations) {
|
|
107
|
+
iterations += 1;
|
|
108
|
+
const ai = await model.invoke(messages);
|
|
109
|
+
messages.push(ai);
|
|
110
|
+
const toolCalls = (ai.tool_calls ?? []);
|
|
111
|
+
onIteration?.({ i: iterations, ai, toolCalls });
|
|
112
|
+
if (toolCalls.length === 0) {
|
|
113
|
+
finalText = extractText(ai);
|
|
114
|
+
lastIterationHadPendingCalls = false;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
lastIterationHadPendingCalls = true;
|
|
118
|
+
for (const call of toolCalls) {
|
|
119
|
+
if (call.name === MEMORY_APPEND_TOOL_NAME) {
|
|
120
|
+
appendsAttempted += 1;
|
|
121
|
+
}
|
|
122
|
+
const tool = toolMap.get(call.name);
|
|
123
|
+
if (!tool) {
|
|
124
|
+
const errStr = `tool_not_found: ${call.name}`;
|
|
125
|
+
toolErrors.push({
|
|
126
|
+
iteration: iterations,
|
|
127
|
+
toolName: call.name,
|
|
128
|
+
toolCallId: call.id,
|
|
129
|
+
error: errStr,
|
|
130
|
+
args: call.args,
|
|
131
|
+
});
|
|
132
|
+
messages.push(new ToolMessage({
|
|
133
|
+
content: JSON.stringify({ ok: false, error: errStr }),
|
|
134
|
+
tool_call_id: call.id ?? '',
|
|
135
|
+
name: call.name,
|
|
136
|
+
}));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
let rawResult;
|
|
140
|
+
try {
|
|
141
|
+
// LangChain tools accept the parsed args object.
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
143
|
+
const res = await tool.invoke(call.args);
|
|
144
|
+
rawResult = typeof res === 'string' ? res : JSON.stringify(res);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
const errStr = err instanceof Error ? err.message : String(err);
|
|
148
|
+
toolErrors.push({
|
|
149
|
+
iteration: iterations,
|
|
150
|
+
toolName: call.name,
|
|
151
|
+
toolCallId: call.id,
|
|
152
|
+
error: errStr,
|
|
153
|
+
args: call.args,
|
|
154
|
+
});
|
|
155
|
+
messages.push(new ToolMessage({
|
|
156
|
+
content: JSON.stringify({ ok: false, error: errStr }),
|
|
157
|
+
tool_call_id: call.id ?? '',
|
|
158
|
+
name: call.name,
|
|
159
|
+
}));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const parsed = parseToolResult(rawResult);
|
|
163
|
+
if (call.name === MEMORY_APPEND_TOOL_NAME) {
|
|
164
|
+
if (parsed.ok) {
|
|
165
|
+
appendsSucceeded += 1;
|
|
166
|
+
}
|
|
167
|
+
else if (parsed.error) {
|
|
168
|
+
toolErrors.push({
|
|
169
|
+
iteration: iterations,
|
|
170
|
+
toolName: call.name,
|
|
171
|
+
toolCallId: call.id,
|
|
172
|
+
error: parsed.error,
|
|
173
|
+
args: call.args,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
messages.push(new ToolMessage({
|
|
178
|
+
content: rawResult,
|
|
179
|
+
tool_call_id: call.id ?? '',
|
|
180
|
+
name: call.name,
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (iterations >= maxIterations && lastIterationHadPendingCalls) {
|
|
185
|
+
hitIterationCap = true;
|
|
186
|
+
// Surface the last AIMessage text (if any) so callers can still log it.
|
|
187
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
188
|
+
const m = messages[i];
|
|
189
|
+
if (m instanceof AIMessage) {
|
|
190
|
+
finalText = extractText(m);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const silentReply = finalText.trim() === SILENT_REPLY_TOKEN;
|
|
196
|
+
return {
|
|
197
|
+
iterations,
|
|
198
|
+
appendsAttempted,
|
|
199
|
+
appendsSucceeded,
|
|
200
|
+
toolErrors,
|
|
201
|
+
silentReply,
|
|
202
|
+
hitIterationCap,
|
|
203
|
+
finalText,
|
|
204
|
+
messages,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export { bindToolsIfSupported, extractText, parseToolResult, runFlushLoop };
|
|
209
|
+
//# sourceMappingURL=flushLoop.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flushLoop.mjs","sources":["../../../../src/graphs/phases/flushLoop.ts"],"sourcesContent":["/**\n * Agentic tool-execution loop for the memory-flush reflection turn.\n *\n * Extracted from {@link runMemoryFlush} so the loop primitive is\n * testable in isolation and reusable from any reflection-style phase\n * that wants \"invoke → execute tool_calls → feed results back → repeat\".\n *\n * Design:\n * - Bounded iterations — caller passes `maxIterations` (default\n * {@link DEFAULT_MAX_FLUSH_ITERATIONS}). The loop exits as soon as\n * the model stops emitting `tool_calls`, hits the cap, or the reply\n * matches {@link SILENT_REPLY_TOKEN}.\n * - Rich result — returns iteration count, attempted/successful\n * appends, per-tool errors, silent-reply flag, and raw final text.\n * The caller logs this; nothing is swallowed.\n * - Self-contained tool execution — does not depend on LangGraph's\n * ToolNode or any graph runtime. The only contract is the\n * LangChain v0.3 `StructuredToolInterface.invoke(args)` shape.\n * - Tool errors are captured as {@link ToolMessage}s with the raw\n * JSON error, so the model can read them and self-correct (e.g.\n * retry with a different path if the schema rejected its first\n * attempt).\n */\nimport {\n AIMessage,\n type BaseMessage,\n ToolMessage,\n} from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport {\n DEFAULT_MAX_FLUSH_ITERATIONS,\n MEMORY_APPEND_TOOL_NAME,\n SILENT_REPLY_TOKEN,\n} from '@/memory/constants';\n\n/** Minimal model contract the loop needs. */\nexport interface InvokableModel {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n invoke(messages: BaseMessage[], options?: any): Promise<AIMessage>;\n}\n\nexport interface FlushLoopParams {\n model: InvokableModel;\n tools: StructuredToolInterface[];\n /** Initial messages — typically [SystemMessage, HumanMessage]. */\n initialMessages: BaseMessage[];\n /** Upper bound on model.invoke() calls. Default 8. */\n maxIterations?: number;\n /**\n * Optional debug hook — called once per iteration with `{ i, ai, toolCalls }`.\n * Use for INFO-level tracing during rollout; pass `undefined` in prod.\n */\n onIteration?: (event: {\n i: number;\n ai: AIMessage;\n toolCalls: ToolCallLike[];\n }) => void;\n}\n\n/** Shape LangChain emits on AIMessage.tool_calls (duck-typed). */\nexport interface ToolCallLike {\n name: string;\n args: Record<string, unknown>;\n id?: string;\n}\n\nexport interface ToolErrorRecord {\n iteration: number;\n toolName: string;\n toolCallId?: string;\n error: string;\n /** Raw args the model passed — useful for diagnosing schema drift. */\n args?: Record<string, unknown>;\n}\n\nexport interface FlushLoopResult {\n /** Number of model.invoke() calls actually made. */\n iterations: number;\n /** Every `memory_append` tool_call the model emitted across all iterations. */\n appendsAttempted: number;\n /** How many of those returned `{ ok: true, ... }`. */\n appendsSucceeded: number;\n /** Tool errors the model saw (and may have reacted to). */\n toolErrors: ToolErrorRecord[];\n /** True if the final AIMessage text matched {@link SILENT_REPLY_TOKEN}. */\n silentReply: boolean;\n /** Whether the loop stopped because it hit `maxIterations`. */\n hitIterationCap: boolean;\n /** Final AIMessage content as plain text (best-effort). */\n finalText: string;\n /** Full message transcript — useful for debugging / tests. */\n messages: BaseMessage[];\n}\n\n/**\n * Extract a flat text string from any AIMessage content shape\n * (string, array of content blocks, etc).\n */\nexport function extractText(message: AIMessage): string {\n const c = message.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n return c\n .map((block) => {\n if (typeof block === 'string') return block;\n if (block && typeof block === 'object' && 'text' in block) {\n return String((block as { text?: unknown }).text ?? '');\n }\n return '';\n })\n .join('')\n .trim();\n}\n\n/**\n * Parse a tool result string. Memory tools return JSON with\n * `{ ok: boolean, error?: string, path?: string }`. Non-JSON payloads\n * are treated as opaque success strings.\n */\nexport function parseToolResult(raw: string): {\n ok: boolean;\n error?: string;\n path?: string;\n} {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === 'object' && 'ok' in parsed) {\n return {\n ok: Boolean(parsed.ok),\n error: typeof parsed.error === 'string' ? parsed.error : undefined,\n path: typeof parsed.path === 'string' ? parsed.path : undefined,\n };\n }\n return { ok: true };\n } catch {\n return { ok: true };\n }\n}\n\n/**\n * Bind a tool array to a model if `bindTools` exists on the model.\n * Exported so the caller (runMemoryFlush) can do it once and pass the\n * bound model in, keeping this loop free of LangChain model-specific\n * extensions.\n */\nexport function bindToolsIfSupported(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n model: any,\n tools: StructuredToolInterface[]\n): InvokableModel {\n if (typeof model?.bindTools === 'function') {\n return model.bindTools(tools) as InvokableModel;\n }\n return model as InvokableModel;\n}\n\n/**\n * Run the agentic reflection loop until the model stops emitting\n * tool_calls, the iteration cap is hit, or the final reply matches\n * {@link SILENT_REPLY_TOKEN}.\n */\nexport async function runFlushLoop(\n params: FlushLoopParams\n): Promise<FlushLoopResult> {\n const {\n model,\n tools,\n initialMessages,\n maxIterations = DEFAULT_MAX_FLUSH_ITERATIONS,\n onIteration,\n } = params;\n\n const toolMap = new Map<string, StructuredToolInterface>();\n for (const t of tools) {\n toolMap.set(t.name, t);\n }\n\n const messages: BaseMessage[] = [...initialMessages];\n const toolErrors: ToolErrorRecord[] = [];\n let appendsAttempted = 0;\n let appendsSucceeded = 0;\n let iterations = 0;\n let finalText = '';\n let hitIterationCap = false;\n // Tracks whether the most recent iteration ended with unresolved\n // tool_calls. If we exit the while via the cap (not via the natural\n // `break`), this stays true and we flag hitIterationCap.\n let lastIterationHadPendingCalls = false;\n\n while (iterations < maxIterations) {\n iterations += 1;\n const ai = await model.invoke(messages);\n messages.push(ai);\n\n const toolCalls = (ai.tool_calls ?? []) as ToolCallLike[];\n onIteration?.({ i: iterations, ai, toolCalls });\n\n if (toolCalls.length === 0) {\n finalText = extractText(ai);\n lastIterationHadPendingCalls = false;\n break;\n }\n lastIterationHadPendingCalls = true;\n\n for (const call of toolCalls) {\n if (call.name === MEMORY_APPEND_TOOL_NAME) {\n appendsAttempted += 1;\n }\n const tool = toolMap.get(call.name);\n if (!tool) {\n const errStr = `tool_not_found: ${call.name}`;\n toolErrors.push({\n iteration: iterations,\n toolName: call.name,\n toolCallId: call.id,\n error: errStr,\n args: call.args,\n });\n messages.push(\n new ToolMessage({\n content: JSON.stringify({ ok: false, error: errStr }),\n tool_call_id: call.id ?? '',\n name: call.name,\n })\n );\n continue;\n }\n\n let rawResult: string;\n try {\n // LangChain tools accept the parsed args object.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const res = await (tool as any).invoke(call.args);\n rawResult = typeof res === 'string' ? res : JSON.stringify(res);\n } catch (err) {\n const errStr = err instanceof Error ? err.message : String(err);\n toolErrors.push({\n iteration: iterations,\n toolName: call.name,\n toolCallId: call.id,\n error: errStr,\n args: call.args,\n });\n messages.push(\n new ToolMessage({\n content: JSON.stringify({ ok: false, error: errStr }),\n tool_call_id: call.id ?? '',\n name: call.name,\n })\n );\n continue;\n }\n\n const parsed = parseToolResult(rawResult);\n if (call.name === MEMORY_APPEND_TOOL_NAME) {\n if (parsed.ok) {\n appendsSucceeded += 1;\n } else if (parsed.error) {\n toolErrors.push({\n iteration: iterations,\n toolName: call.name,\n toolCallId: call.id,\n error: parsed.error,\n args: call.args,\n });\n }\n }\n\n messages.push(\n new ToolMessage({\n content: rawResult,\n tool_call_id: call.id ?? '',\n name: call.name,\n })\n );\n }\n }\n\n if (iterations >= maxIterations && lastIterationHadPendingCalls) {\n hitIterationCap = true;\n // Surface the last AIMessage text (if any) so callers can still log it.\n for (let i = messages.length - 1; i >= 0; i -= 1) {\n const m = messages[i];\n if (m instanceof AIMessage) {\n finalText = extractText(m);\n break;\n }\n }\n }\n\n const silentReply = finalText.trim() === SILENT_REPLY_TOKEN;\n\n return {\n iterations,\n appendsAttempted,\n appendsSucceeded,\n toolErrors,\n silentReply,\n hitIterationCap,\n finalText,\n messages,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { SystemMessage, HumanMessage } from '@langchain/core/messages';
|
|
2
|
+
import { MEMORY_PHASE_FLUSHING, DEFAULT_MAX_FLUSH_ITERATIONS, MEMORY_PHASE_NORMAL, DEFAULT_FLUSH_RESERVE_FLOOR_TOKENS, DEFAULT_FLUSH_SOFT_THRESHOLD_TOKENS } from '../../memory/constants.mjs';
|
|
3
|
+
import { buildMemoryTools } from '../../tools/memory/index.mjs';
|
|
4
|
+
import { resolveFlushPrompts } from '../../prompts/memoryFlushPrompt.mjs';
|
|
5
|
+
import { bindToolsIfSupported, runFlushLoop } from './flushLoop.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pure trigger function: fires when the current context is within
|
|
9
|
+
* `softThreshold + reserveFloor` tokens of the model window. Matches
|
|
10
|
+
* upstream's formula.
|
|
11
|
+
*/
|
|
12
|
+
function shouldFlushMemory(input) {
|
|
13
|
+
if (!Number.isFinite(input.currentTokens) ||
|
|
14
|
+
!Number.isFinite(input.windowTokens)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (input.windowTokens <= 0)
|
|
18
|
+
return false;
|
|
19
|
+
const reserve = input.reserveFloorTokens ?? DEFAULT_FLUSH_RESERVE_FLOOR_TOKENS;
|
|
20
|
+
const soft = input.softThresholdTokens ?? DEFAULT_FLUSH_SOFT_THRESHOLD_TOKENS;
|
|
21
|
+
return input.currentTokens >= input.windowTokens - reserve - soft;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run the reflection turn. The model is re-invoked with just the flush
|
|
25
|
+
* prompt + compact summary + the append tool unlocked. We intentionally
|
|
26
|
+
* drop the full history — the prompt tells the agent to write notes based
|
|
27
|
+
* on what it learned, not to continue the conversation.
|
|
28
|
+
*
|
|
29
|
+
* The loop keeps invoking the model until it stops emitting tool_calls,
|
|
30
|
+
* hits the iteration cap, or replies with {@link SILENT_REPLY_TOKEN}. Each
|
|
31
|
+
* `memory_append` tool_call is executed against the pgvector backend and
|
|
32
|
+
* the result (success path or error) is fed back as a ToolMessage so the
|
|
33
|
+
* model can self-correct (e.g. retry with a valid path).
|
|
34
|
+
*/
|
|
35
|
+
async function runMemoryFlush(params) {
|
|
36
|
+
const { model, memory, conversationSummary, setPhase, maxIterations, onIteration, } = params;
|
|
37
|
+
if (memory.flush?.enabled === false) {
|
|
38
|
+
return { ran: false };
|
|
39
|
+
}
|
|
40
|
+
setPhase(MEMORY_PHASE_FLUSHING);
|
|
41
|
+
try {
|
|
42
|
+
const tools = buildMemoryTools({
|
|
43
|
+
...memory,
|
|
44
|
+
readEnabled: false,
|
|
45
|
+
writeEnabled: true,
|
|
46
|
+
getPhase: () => MEMORY_PHASE_FLUSHING,
|
|
47
|
+
});
|
|
48
|
+
// Bind tools to the caller-provided model. If the model already came
|
|
49
|
+
// bound (some graph wrappers do), bindToolsIfSupported is a no-op.
|
|
50
|
+
const bound = bindToolsIfSupported(model, tools);
|
|
51
|
+
// Resolve flush prompts with the scope-aware rubric. For an
|
|
52
|
+
// isolated agent (no userId in scope), the user-tier paths are
|
|
53
|
+
// filtered out of the rubric so the LLM never sees them — it
|
|
54
|
+
// physically cannot route a write to `memory/user/*` because the
|
|
55
|
+
// prompt never lists those paths.
|
|
56
|
+
const { systemPrompt, prompt } = resolveFlushPrompts({
|
|
57
|
+
scope: memory.scope,
|
|
58
|
+
});
|
|
59
|
+
const initialMessages = [
|
|
60
|
+
new SystemMessage(systemPrompt),
|
|
61
|
+
new HumanMessage(`${prompt}\n\nConversation context:\n\n${conversationSummary}`),
|
|
62
|
+
];
|
|
63
|
+
const loop = await runFlushLoop({
|
|
64
|
+
model: bound,
|
|
65
|
+
tools,
|
|
66
|
+
initialMessages,
|
|
67
|
+
maxIterations: maxIterations ?? DEFAULT_MAX_FLUSH_ITERATIONS,
|
|
68
|
+
onIteration: onIteration
|
|
69
|
+
? (ev) => onIteration({
|
|
70
|
+
i: ev.i,
|
|
71
|
+
toolCallCount: ev.toolCalls.length,
|
|
72
|
+
toolNames: ev.toolCalls.map((c) => c.name),
|
|
73
|
+
})
|
|
74
|
+
: undefined,
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
ran: true,
|
|
78
|
+
iterations: loop.iterations,
|
|
79
|
+
appendsAttempted: loop.appendsAttempted,
|
|
80
|
+
appendsSucceeded: loop.appendsSucceeded,
|
|
81
|
+
toolErrors: loop.toolErrors,
|
|
82
|
+
silentReply: loop.silentReply,
|
|
83
|
+
hitIterationCap: loop.hitIterationCap,
|
|
84
|
+
finalText: loop.finalText,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return {
|
|
89
|
+
ran: false,
|
|
90
|
+
error: err instanceof Error ? err.message : String(err),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
setPhase(MEMORY_PHASE_NORMAL);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { runMemoryFlush, shouldFlushMemory };
|
|
99
|
+
//# sourceMappingURL=memoryFlushPhase.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryFlushPhase.mjs","sources":["../../../../src/graphs/phases/memoryFlushPhase.ts"],"sourcesContent":["/**\n * Memory flush phase — trigger logic + reflection invocation.\n *\n * Ported from upstream's post-turn flush handler. The agent is re-invoked\n * with a reflection system prompt and the `memory_append` tool unlocked;\n * it writes notes to its future self, then the graph returns to normal.\n *\n * This module is INTENTIONALLY decoupled from the specific graph runtime —\n * it exposes pure functions (`shouldFlushMemory`) plus a runner that takes\n * the model as a parameter, so the same logic works from `createAgentNode`,\n * `MultiAgentGraph`, or a future graph backend that wants to override\n * flush behaviour.\n *\n * The agentic tool-execution loop (invoke → run tool_calls → feed results\n * back → repeat until stop) lives in {@link ./flushLoop}. This module wires\n * it up: build tools, resolve prompts, flip the phase, call the loop,\n * shape the rich result.\n */\nimport type { BaseChatModel } from '@langchain/core/language_models/chat_models';\nimport { HumanMessage, SystemMessage } from '@langchain/core/messages';\nimport {\n DEFAULT_FLUSH_RESERVE_FLOOR_TOKENS,\n DEFAULT_FLUSH_SOFT_THRESHOLD_TOKENS,\n DEFAULT_MAX_FLUSH_ITERATIONS,\n MEMORY_PHASE_FLUSHING,\n MEMORY_PHASE_NORMAL,\n} from '@/memory/constants';\nimport type { MemoryConfig } from '@/memory/types';\nimport { buildMemoryTools } from '@/tools/memory';\nimport { resolveFlushPrompts } from '@/prompts/memoryFlushPrompt';\nimport {\n bindToolsIfSupported,\n runFlushLoop,\n type FlushLoopResult,\n type ToolErrorRecord,\n} from './flushLoop';\n\nexport interface ShouldFlushInput {\n currentTokens: number;\n windowTokens: number;\n reserveFloorTokens?: number;\n softThresholdTokens?: number;\n}\n\n/**\n * Pure trigger function: fires when the current context is within\n * `softThreshold + reserveFloor` tokens of the model window. Matches\n * upstream's formula.\n */\nexport function shouldFlushMemory(input: ShouldFlushInput): boolean {\n if (\n !Number.isFinite(input.currentTokens) ||\n !Number.isFinite(input.windowTokens)\n ) {\n return false;\n }\n if (input.windowTokens <= 0) return false;\n const reserve =\n input.reserveFloorTokens ?? DEFAULT_FLUSH_RESERVE_FLOOR_TOKENS;\n const soft = input.softThresholdTokens ?? DEFAULT_FLUSH_SOFT_THRESHOLD_TOKENS;\n return input.currentTokens >= input.windowTokens - reserve - soft;\n}\n\nexport interface RunFlushParams {\n model: BaseChatModel;\n memory: MemoryConfig;\n /** A compact summary of the conversation — last N turns, not raw history. */\n conversationSummary: string;\n /**\n * Accessor the graph runtime uses to expose the current phase to the\n * append tool. The runner sets this to `memory_flushing` for the duration\n * of the reflection turn and restores it on exit.\n */\n setPhase: (\n phase: typeof MEMORY_PHASE_NORMAL | typeof MEMORY_PHASE_FLUSHING\n ) => void;\n /**\n * @deprecated No longer used — the 8-path canonical-document model\n * does not date-stamp files. Kept in the params shape for one release\n * so host's callers don't break on the type signature.\n */\n timezone?: string;\n /**\n * @deprecated Same as `timezone` — unused in the canonical-document model.\n */\n nowMs?: number;\n /** Override for the agentic-loop iteration cap. */\n maxIterations?: number;\n /**\n * Optional per-iteration callback for structured debug logging.\n * Caller is expected to demote this to debug once the rollout is stable.\n */\n onIteration?: (event: {\n i: number;\n toolCallCount: number;\n toolNames: string[];\n }) => void;\n}\n\nexport interface RunFlushResult {\n /** Did the flush actually run (false if disabled via config). */\n ran: boolean;\n /** Model.invoke() iterations actually performed. */\n iterations?: number;\n /** Total `memory_append` calls the model emitted. */\n appendsAttempted?: number;\n /** Subset that returned `{ ok: true }` from the backend. */\n appendsSucceeded?: number;\n /** Tool errors the model saw during the flush — surfaced for logging. */\n toolErrors?: ToolErrorRecord[];\n /** Final text reply equalled SILENT_REPLY_TOKEN (`NO_REPLY`). */\n silentReply?: boolean;\n /** Loop hit its iteration cap with tool_calls still pending. */\n hitIterationCap?: boolean;\n /** Final text reply from the last AIMessage. */\n finalText?: string;\n /** Error message if the flush threw. */\n error?: string;\n}\n\n/**\n * Run the reflection turn. The model is re-invoked with just the flush\n * prompt + compact summary + the append tool unlocked. We intentionally\n * drop the full history — the prompt tells the agent to write notes based\n * on what it learned, not to continue the conversation.\n *\n * The loop keeps invoking the model until it stops emitting tool_calls,\n * hits the iteration cap, or replies with {@link SILENT_REPLY_TOKEN}. Each\n * `memory_append` tool_call is executed against the pgvector backend and\n * the result (success path or error) is fed back as a ToolMessage so the\n * model can self-correct (e.g. retry with a valid path).\n */\nexport async function runMemoryFlush(\n params: RunFlushParams\n): Promise<RunFlushResult> {\n const {\n model,\n memory,\n conversationSummary,\n setPhase,\n maxIterations,\n onIteration,\n } = params;\n if (memory.flush?.enabled === false) {\n return { ran: false };\n }\n\n setPhase(MEMORY_PHASE_FLUSHING);\n try {\n const tools = buildMemoryTools({\n ...memory,\n readEnabled: false,\n writeEnabled: true,\n getPhase: () => MEMORY_PHASE_FLUSHING,\n });\n\n // Bind tools to the caller-provided model. If the model already came\n // bound (some graph wrappers do), bindToolsIfSupported is a no-op.\n const bound = bindToolsIfSupported(model, tools);\n\n // Resolve flush prompts with the scope-aware rubric. For an\n // isolated agent (no userId in scope), the user-tier paths are\n // filtered out of the rubric so the LLM never sees them — it\n // physically cannot route a write to `memory/user/*` because the\n // prompt never lists those paths.\n const { systemPrompt, prompt } = resolveFlushPrompts({\n scope: memory.scope,\n });\n const initialMessages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(\n `${prompt}\\n\\nConversation context:\\n\\n${conversationSummary}`\n ),\n ];\n\n const loop: FlushLoopResult = await runFlushLoop({\n model: bound,\n tools,\n initialMessages,\n maxIterations: maxIterations ?? DEFAULT_MAX_FLUSH_ITERATIONS,\n onIteration: onIteration\n ? (ev): void =>\n onIteration({\n i: ev.i,\n toolCallCount: ev.toolCalls.length,\n toolNames: ev.toolCalls.map((c) => c.name),\n })\n : undefined,\n });\n\n return {\n ran: true,\n iterations: loop.iterations,\n appendsAttempted: loop.appendsAttempted,\n appendsSucceeded: loop.appendsSucceeded,\n toolErrors: loop.toolErrors,\n silentReply: loop.silentReply,\n hitIterationCap: loop.hitIterationCap,\n finalText: loop.finalText,\n };\n } catch (err) {\n return {\n ran: false,\n error: err instanceof Error ? err.message : String(err),\n };\n } finally {\n setPhase(MEMORY_PHASE_NORMAL);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -95,12 +95,13 @@ class IllumaBedrockConverse extends ChatBedrockConverse {
|
|
|
95
95
|
* Some tools (e.g., MCP-sourced or dynamically created) may have empty or
|
|
96
96
|
* missing descriptions. Patch them here to avoid Bedrock validation errors.
|
|
97
97
|
*/
|
|
98
|
-
if (params.toolConfig?.tools &&
|
|
99
|
-
Array.isArray(params.toolConfig.tools)) {
|
|
98
|
+
if (params.toolConfig?.tools && Array.isArray(params.toolConfig.tools)) {
|
|
100
99
|
for (const t of params.toolConfig.tools) {
|
|
101
100
|
const spec = t.toolSpec;
|
|
102
101
|
if (spec && (!spec.description || spec.description === '')) {
|
|
103
|
-
spec.description =
|
|
102
|
+
spec.description =
|
|
103
|
+
spec.description ||
|
|
104
|
+
`Tool: ${spec.name ?? 'unknown'}`;
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../../../src/llm/bedrock/index.ts"],"sourcesContent":["/**\n * Optimized ChatBedrockConverse wrapper that fixes content block merging for\n * streaming responses and adds support for latest @langchain/aws features:\n *\n * - Prompt caching support for Bedrock Converse API (Illuma feature)\n * - Application Inference Profiles (PR #9129)\n * - Service Tiers (Priority/Standard/Flex) (PR #9785) - requires AWS SDK 3.966.0+\n *\n * Bedrock's `@langchain/aws` library does not include an `index` property on content\n * blocks (unlike Anthropic/OpenAI), which causes LangChain's `_mergeLists` to append\n * each streaming chunk as a separate array entry instead of merging by index.\n *\n * This wrapper takes full ownership of the stream by directly interfacing with the\n * AWS SDK client (`this.client`) and using custom handlers from `./utils/` that\n * include `contentBlockIndex` in response_metadata for every delta type. It then\n * promotes `contentBlockIndex` to an `index` property on each content block\n * (mirroring Anthropic's pattern) and strips it from metadata to avoid\n * `_mergeDicts` conflicts.\n *\n * When multiple content block types are present (e.g. reasoning + text), text deltas\n * are promoted from strings to array form with `index` so they merge correctly once\n * the accumulated content is already an array.\n *\n * PROMPT CACHING:\n * When promptCache: true is set, this wrapper adds cachePoint markers to the tools array\n * to enable Bedrock prompt caching for tool definitions. This allows tool schemas to be\n * cached and reused across requests, reducing latency and costs.\n *\n * CACHE TOKEN EXTRACTION:\n * Cache token extraction is handled in `./utils/message_outputs.ts` and added\n * to usage_metadata with input_token_details (cacheReadInputTokens/cacheWriteInputTokens).\n */\n\nimport { ChatBedrockConverse } from '@langchain/aws';\nimport { ConverseStreamCommand } from '@aws-sdk/client-bedrock-runtime';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { ChatGenerationChunk, ChatResult } from '@langchain/core/outputs';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { ChatBedrockConverseInput } from '@langchain/aws';\nimport {\n convertToConverseMessages,\n handleConverseStreamContentBlockStart,\n handleConverseStreamContentBlockDelta,\n handleConverseStreamMetadata,\n} from './utils';\n\n/**\n * Service tier type for Bedrock invocations.\n * Requires AWS SDK >= 3.966.0 to actually work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\nexport type ServiceTierType = 'priority' | 'default' | 'flex' | 'reserved';\n\n/**\n * Extended input interface with additional features:\n * - promptCache: Enable Bedrock prompt caching for tool definitions\n * - applicationInferenceProfile: Use an inference profile ARN instead of model ID\n * - serviceTier: Specify service tier (Priority, Standard, Flex, Reserved)\n */\nexport interface IllumaBedrockConverseInput extends ChatBedrockConverseInput {\n /**\n * Enable Bedrock prompt caching for tool definitions.\n * When true, adds cachePoint markers to tools array.\n */\n promptCache?: boolean;\n\n /**\n * Application Inference Profile ARN to use for the model.\n * For example, \"arn:aws:bedrock:eu-west-1:123456789102:application-inference-profile/fm16bt65tzgx\"\n * When provided, this ARN will be used for the actual inference calls instead of the model ID.\n * Must still provide `model` as normal modelId to benefit from all the metadata.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-create.html\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n * Specifies the processing tier type used for serving the request.\n * Supported values are 'priority', 'default', 'flex', and 'reserved'.\n *\n * - 'priority': Prioritized processing for lower latency\n * - 'default': Standard processing tier\n * - 'flex': Flexible processing tier with lower cost\n * - 'reserved': Reserved capacity for consistent performance\n *\n * If not provided, AWS uses the default tier.\n * Note: Requires AWS SDK >= 3.966.0 to work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\n serviceTier?: ServiceTierType;\n}\n\n/**\n * Extended call options with serviceTier override support.\n */\nexport interface IllumaBedrockConverseCallOptions {\n serviceTier?: ServiceTierType;\n}\n\nexport class IllumaBedrockConverse extends ChatBedrockConverse {\n /** Enable Bedrock prompt caching for tool definitions */\n promptCache: boolean;\n\n /** Application Inference Profile ARN to use instead of model ID */\n applicationInferenceProfile?: string;\n\n /** Service tier for model invocation */\n serviceTier?: ServiceTierType;\n\n constructor(fields?: IllumaBedrockConverseInput) {\n super(fields);\n this.promptCache = fields?.promptCache ?? false;\n this.applicationInferenceProfile = fields?.applicationInferenceProfile;\n this.serviceTier = fields?.serviceTier;\n\n // Fix: Force supportsToolChoiceValues for Claude models\n // The parent constructor checks `model.includes('claude-3')` but this fails when:\n // 1. Using applicationInferenceProfile ARNs (arn:aws:bedrock:...)\n // 2. Using different naming conventions (claude-4, claude-opus-4, etc.)\n // We need to ensure tool_choice is properly set for withStructuredOutput to work\n const modelName = (fields?.model ?? '').toLowerCase();\n const profileName = (\n fields?.applicationInferenceProfile ?? ''\n ).toLowerCase();\n const isClaudeModel =\n modelName.includes('claude') ||\n modelName.includes('anthropic') ||\n profileName.includes('claude') ||\n profileName.includes('anthropic');\n\n if (isClaudeModel && !this.supportsToolChoiceValues?.length) {\n // Claude models support all tool choice values\n this.supportsToolChoiceValues = ['auto', 'any', 'tool'];\n }\n }\n\n static lc_name(): string {\n return 'IllumaBedrockConverse';\n }\n\n /**\n * Get the model ID to use for API calls.\n * Returns applicationInferenceProfile if set, otherwise returns this.model.\n */\n protected getModelId(): string {\n return this.applicationInferenceProfile ?? this.model;\n }\n\n /**\n * Override invocationParams to:\n * 1. Add cachePoint to tools when promptCache is enabled\n * 2. Add serviceTier support\n *\n * CACHING STRATEGY: Separate cachePoints for core tools and MCP tools\n * - Core tools (web_search, execute_code, etc.) are stable → cache first\n * - MCP tools (have '_mcp_' in name) are dynamic → cache separately after\n * - This allows core tools to stay cached when MCP selection changes\n *\n * NOTE: Only Claude models support cachePoint - Nova and other models will reject it.\n */\n override invocationParams(\n options?: this['ParsedCallOptions'] & IllumaBedrockConverseCallOptions\n ): ReturnType<ChatBedrockConverse['invocationParams']> & {\n serviceTier?: { type: ServiceTierType };\n } {\n const params = super.invocationParams(options);\n\n // Add cachePoint to tools array if promptCache is enabled and tools exist\n /**\n * Bedrock requires all toolSpec.description fields to be non-empty strings.\n * Some tools (e.g., MCP-sourced or dynamically created) may have empty or\n * missing descriptions. Patch them here to avoid Bedrock validation errors.\n */\n if (\n params.toolConfig?.tools &&\n Array.isArray(params.toolConfig.tools)\n ) {\n for (const t of params.toolConfig.tools) {\n const spec = (t as { toolSpec?: { description?: string } }).toolSpec;\n if (spec && (!spec.description || spec.description === '')) {\n spec.description = spec.description || `Tool: ${(spec as { name?: string }).name ?? 'unknown'}`;\n }\n }\n }\n\n // Only Claude models support cachePoint - check model name\n const modelId = this.model.toLowerCase();\n const isClaudeModel =\n modelId.includes('claude') || modelId.includes('anthropic');\n\n if (\n this.promptCache &&\n isClaudeModel &&\n params.toolConfig?.tools &&\n Array.isArray(params.toolConfig.tools) &&\n params.toolConfig.tools.length > 0\n ) {\n // Separate core tools from MCP tools\n // MCP tools have '_mcp_' in their name (e.g., 'search_emails_mcp_Google-Workspace')\n const coreTools: typeof params.toolConfig.tools = [];\n const mcpTools: typeof params.toolConfig.tools = [];\n\n for (const tool of params.toolConfig.tools) {\n // Check if tool has a name property with '_mcp_' pattern\n const toolName =\n (tool as { toolSpec?: { name?: string } }).toolSpec?.name ?? '';\n if (toolName.includes('_mcp_')) {\n mcpTools.push(tool);\n } else {\n coreTools.push(tool);\n }\n }\n\n // Build tools array with strategic cachePoints:\n // [CoreTool1, CoreTool2, cachePoint] + [MCPTool1, MCPTool2, cachePoint]\n const toolsWithCache: typeof params.toolConfig.tools = [];\n\n // Add core tools with cachePoint (if any)\n if (coreTools.length > 0) {\n toolsWithCache.push(...coreTools);\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n }\n\n // Add MCP tools with their own cachePoint (if any)\n if (mcpTools.length > 0) {\n toolsWithCache.push(...mcpTools);\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n }\n\n // If no tools at all (shouldn't happen but safety check)\n if (toolsWithCache.length === 0) {\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n }\n\n params.toolConfig.tools = toolsWithCache;\n }\n\n // Add serviceTier support\n const serviceTierType = options?.serviceTier ?? this.serviceTier;\n\n return {\n ...params,\n serviceTier: serviceTierType ? { type: serviceTierType } : undefined,\n };\n }\n\n /**\n * Override _generateNonStreaming to use applicationInferenceProfile as modelId.\n * Uses the same model-swapping pattern as streaming for consistency.\n */\n override async _generateNonStreaming(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & IllumaBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): Promise<ChatResult> {\n const originalModel = this.model;\n if (\n this.applicationInferenceProfile != null &&\n this.applicationInferenceProfile !== ''\n ) {\n this.model = this.applicationInferenceProfile;\n }\n\n try {\n return await super._generateNonStreaming(messages, options, runManager);\n } finally {\n this.model = originalModel;\n }\n }\n\n /**\n * Own the stream end-to-end so we have direct access to every\n * `contentBlockDelta.contentBlockIndex` from the AWS SDK.\n *\n * This replaces the parent's implementation which strips contentBlockIndex\n * from text and reasoning deltas, making it impossible to merge correctly.\n */\n override async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & IllumaBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const { converseMessages, converseSystem } =\n convertToConverseMessages(messages);\n const params = this.invocationParams(options);\n\n let { streamUsage } = this;\n if ((options as Record<string, unknown>).streamUsage !== undefined) {\n streamUsage = (options as Record<string, unknown>).streamUsage as boolean;\n }\n\n const modelId = this.getModelId();\n\n const command = new ConverseStreamCommand({\n modelId,\n messages: converseMessages,\n system: converseSystem,\n ...(params as Record<string, unknown>),\n });\n\n const response = await this.client.send(command, {\n abortSignal: options.signal,\n });\n\n if (!response.stream) {\n return;\n }\n\n const seenBlockIndices = new Set<number>();\n\n for await (const event of response.stream) {\n if (event.contentBlockStart != null) {\n const startChunk = handleConverseStreamContentBlockStart(\n event.contentBlockStart\n );\n if (startChunk != null) {\n const idx = event.contentBlockStart.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n }\n yield this.enrichChunk(startChunk, seenBlockIndices);\n }\n } else if (event.contentBlockDelta != null) {\n const deltaChunk = handleConverseStreamContentBlockDelta(\n event.contentBlockDelta\n );\n\n const idx = event.contentBlockDelta.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n }\n\n yield this.enrichChunk(deltaChunk, seenBlockIndices);\n\n await runManager?.handleLLMNewToken(\n deltaChunk.text,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: deltaChunk }\n );\n } else if (event.metadata != null) {\n yield handleConverseStreamMetadata(event.metadata, { streamUsage });\n } else if (event.contentBlockStop != null) {\n const stopIdx = event.contentBlockStop.contentBlockIndex;\n if (stopIdx != null) {\n seenBlockIndices.add(stopIdx);\n }\n } else {\n yield new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n response_metadata: event,\n }),\n });\n }\n }\n }\n\n /**\n * Inject `index` on content blocks for proper merge behaviour, then strip\n * `contentBlockIndex` from response_metadata to prevent `_mergeDicts` conflicts.\n *\n * Text string content is promoted to array form only when the stream contains\n * multiple content block indices (e.g. reasoning at index 0, text at index 1),\n * ensuring text merges correctly with the already-array accumulated content.\n */\n enrichChunk(\n chunk: ChatGenerationChunk,\n seenBlockIndices: Set<number>\n ): ChatGenerationChunk {\n const message = chunk.message;\n if (!(message instanceof AIMessageChunk)) {\n return chunk;\n }\n\n const metadata = message.response_metadata as Record<string, unknown>;\n const blockIndex = this.extractContentBlockIndex(metadata);\n const hasMetadataIndex = blockIndex != null;\n\n let content: AIMessageChunk['content'] = message.content;\n let contentModified = false;\n\n if (Array.isArray(content) && blockIndex != null) {\n content = content.map((block) =>\n typeof block === 'object' && !('index' in block)\n ? { ...block, index: blockIndex }\n : block\n );\n contentModified = true;\n } else if (\n typeof content === 'string' &&\n content !== '' &&\n blockIndex != null &&\n seenBlockIndices.size > 1\n ) {\n content = [{ type: 'text', text: content, index: blockIndex }];\n contentModified = true;\n }\n\n if (!contentModified && !hasMetadataIndex) {\n return chunk;\n }\n\n const cleanedMetadata = hasMetadataIndex\n ? (this.removeContentBlockIndex(metadata) as Record<string, unknown>)\n : metadata;\n\n return new ChatGenerationChunk({\n text: chunk.text,\n message: new AIMessageChunk({\n ...message,\n content,\n response_metadata: cleanedMetadata,\n }),\n generationInfo: chunk.generationInfo,\n });\n }\n\n /**\n * Extract `contentBlockIndex` from the top level of response_metadata.\n * Our custom handlers always place it at the top level.\n */\n private extractContentBlockIndex(\n metadata: Record<string, unknown>\n ): number | undefined {\n if (\n 'contentBlockIndex' in metadata &&\n typeof metadata.contentBlockIndex === 'number'\n ) {\n return metadata.contentBlockIndex;\n }\n return undefined;\n }\n\n private removeContentBlockIndex(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.removeContentBlockIndex(item));\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key !== 'contentBlockIndex') {\n cleaned[key] = this.removeContentBlockIndex(value);\n }\n }\n return cleaned;\n }\n\n return obj;\n }\n}\n\n/** @deprecated Use IllumaBedrockConverse. Alias kept for backward compatibility with existing imports. */\nexport const CustomChatBedrockConverse = IllumaBedrockConverse;\n\nexport type { ChatBedrockConverseInput };\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../../src/llm/bedrock/index.ts"],"sourcesContent":["/**\n * Optimized ChatBedrockConverse wrapper that fixes content block merging for\n * streaming responses and adds support for latest @langchain/aws features:\n *\n * - Prompt caching support for Bedrock Converse API (Illuma feature)\n * - Application Inference Profiles (PR #9129)\n * - Service Tiers (Priority/Standard/Flex) (PR #9785) - requires AWS SDK 3.966.0+\n *\n * Bedrock's `@langchain/aws` library does not include an `index` property on content\n * blocks (unlike Anthropic/OpenAI), which causes LangChain's `_mergeLists` to append\n * each streaming chunk as a separate array entry instead of merging by index.\n *\n * This wrapper takes full ownership of the stream by directly interfacing with the\n * AWS SDK client (`this.client`) and using custom handlers from `./utils/` that\n * include `contentBlockIndex` in response_metadata for every delta type. It then\n * promotes `contentBlockIndex` to an `index` property on each content block\n * (mirroring Anthropic's pattern) and strips it from metadata to avoid\n * `_mergeDicts` conflicts.\n *\n * When multiple content block types are present (e.g. reasoning + text), text deltas\n * are promoted from strings to array form with `index` so they merge correctly once\n * the accumulated content is already an array.\n *\n * PROMPT CACHING:\n * When promptCache: true is set, this wrapper adds cachePoint markers to the tools array\n * to enable Bedrock prompt caching for tool definitions. This allows tool schemas to be\n * cached and reused across requests, reducing latency and costs.\n *\n * CACHE TOKEN EXTRACTION:\n * Cache token extraction is handled in `./utils/message_outputs.ts` and added\n * to usage_metadata with input_token_details (cacheReadInputTokens/cacheWriteInputTokens).\n */\n\nimport { ChatBedrockConverse } from '@langchain/aws';\nimport { ConverseStreamCommand } from '@aws-sdk/client-bedrock-runtime';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { ChatGenerationChunk, ChatResult } from '@langchain/core/outputs';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { ChatBedrockConverseInput } from '@langchain/aws';\nimport {\n convertToConverseMessages,\n handleConverseStreamContentBlockStart,\n handleConverseStreamContentBlockDelta,\n handleConverseStreamMetadata,\n} from './utils';\n\n/**\n * Service tier type for Bedrock invocations.\n * Requires AWS SDK >= 3.966.0 to actually work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\nexport type ServiceTierType = 'priority' | 'default' | 'flex' | 'reserved';\n\n/**\n * Extended input interface with additional features:\n * - promptCache: Enable Bedrock prompt caching for tool definitions\n * - applicationInferenceProfile: Use an inference profile ARN instead of model ID\n * - serviceTier: Specify service tier (Priority, Standard, Flex, Reserved)\n */\nexport interface IllumaBedrockConverseInput extends ChatBedrockConverseInput {\n /**\n * Enable Bedrock prompt caching for tool definitions.\n * When true, adds cachePoint markers to tools array.\n */\n promptCache?: boolean;\n\n /**\n * Application Inference Profile ARN to use for the model.\n * For example, \"arn:aws:bedrock:eu-west-1:123456789102:application-inference-profile/fm16bt65tzgx\"\n * When provided, this ARN will be used for the actual inference calls instead of the model ID.\n * Must still provide `model` as normal modelId to benefit from all the metadata.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-create.html\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n * Specifies the processing tier type used for serving the request.\n * Supported values are 'priority', 'default', 'flex', and 'reserved'.\n *\n * - 'priority': Prioritized processing for lower latency\n * - 'default': Standard processing tier\n * - 'flex': Flexible processing tier with lower cost\n * - 'reserved': Reserved capacity for consistent performance\n *\n * If not provided, AWS uses the default tier.\n * Note: Requires AWS SDK >= 3.966.0 to work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\n serviceTier?: ServiceTierType;\n}\n\n/**\n * Extended call options with serviceTier override support.\n */\nexport interface IllumaBedrockConverseCallOptions {\n serviceTier?: ServiceTierType;\n}\n\nexport class IllumaBedrockConverse extends ChatBedrockConverse {\n /** Enable Bedrock prompt caching for tool definitions */\n promptCache: boolean;\n\n /** Application Inference Profile ARN to use instead of model ID */\n applicationInferenceProfile?: string;\n\n /** Service tier for model invocation */\n serviceTier?: ServiceTierType;\n\n constructor(fields?: IllumaBedrockConverseInput) {\n super(fields);\n this.promptCache = fields?.promptCache ?? false;\n this.applicationInferenceProfile = fields?.applicationInferenceProfile;\n this.serviceTier = fields?.serviceTier;\n\n // Fix: Force supportsToolChoiceValues for Claude models\n // The parent constructor checks `model.includes('claude-3')` but this fails when:\n // 1. Using applicationInferenceProfile ARNs (arn:aws:bedrock:...)\n // 2. Using different naming conventions (claude-4, claude-opus-4, etc.)\n // We need to ensure tool_choice is properly set for withStructuredOutput to work\n const modelName = (fields?.model ?? '').toLowerCase();\n const profileName = (\n fields?.applicationInferenceProfile ?? ''\n ).toLowerCase();\n const isClaudeModel =\n modelName.includes('claude') ||\n modelName.includes('anthropic') ||\n profileName.includes('claude') ||\n profileName.includes('anthropic');\n\n if (isClaudeModel && !this.supportsToolChoiceValues?.length) {\n // Claude models support all tool choice values\n this.supportsToolChoiceValues = ['auto', 'any', 'tool'];\n }\n }\n\n static lc_name(): string {\n return 'IllumaBedrockConverse';\n }\n\n /**\n * Get the model ID to use for API calls.\n * Returns applicationInferenceProfile if set, otherwise returns this.model.\n */\n protected getModelId(): string {\n return this.applicationInferenceProfile ?? this.model;\n }\n\n /**\n * Override invocationParams to:\n * 1. Add cachePoint to tools when promptCache is enabled\n * 2. Add serviceTier support\n *\n * CACHING STRATEGY: Separate cachePoints for core tools and MCP tools\n * - Core tools (web_search, execute_code, etc.) are stable → cache first\n * - MCP tools (have '_mcp_' in name) are dynamic → cache separately after\n * - This allows core tools to stay cached when MCP selection changes\n *\n * NOTE: Only Claude models support cachePoint - Nova and other models will reject it.\n */\n override invocationParams(\n options?: this['ParsedCallOptions'] & IllumaBedrockConverseCallOptions\n ): ReturnType<ChatBedrockConverse['invocationParams']> & {\n serviceTier?: { type: ServiceTierType };\n } {\n const params = super.invocationParams(options);\n\n // Add cachePoint to tools array if promptCache is enabled and tools exist\n /**\n * Bedrock requires all toolSpec.description fields to be non-empty strings.\n * Some tools (e.g., MCP-sourced or dynamically created) may have empty or\n * missing descriptions. Patch them here to avoid Bedrock validation errors.\n */\n if (params.toolConfig?.tools && Array.isArray(params.toolConfig.tools)) {\n for (const t of params.toolConfig.tools) {\n const spec = (t as { toolSpec?: { description?: string } }).toolSpec;\n if (spec && (!spec.description || spec.description === '')) {\n spec.description =\n spec.description ||\n `Tool: ${(spec as { name?: string }).name ?? 'unknown'}`;\n }\n }\n }\n\n // Only Claude models support cachePoint - check model name\n const modelId = this.model.toLowerCase();\n const isClaudeModel =\n modelId.includes('claude') || modelId.includes('anthropic');\n\n if (\n this.promptCache &&\n isClaudeModel &&\n params.toolConfig?.tools &&\n Array.isArray(params.toolConfig.tools) &&\n params.toolConfig.tools.length > 0\n ) {\n // Separate core tools from MCP tools\n // MCP tools have '_mcp_' in their name (e.g., 'search_emails_mcp_Google-Workspace')\n const coreTools: typeof params.toolConfig.tools = [];\n const mcpTools: typeof params.toolConfig.tools = [];\n\n for (const tool of params.toolConfig.tools) {\n // Check if tool has a name property with '_mcp_' pattern\n const toolName =\n (tool as { toolSpec?: { name?: string } }).toolSpec?.name ?? '';\n if (toolName.includes('_mcp_')) {\n mcpTools.push(tool);\n } else {\n coreTools.push(tool);\n }\n }\n\n // Build tools array with strategic cachePoints:\n // [CoreTool1, CoreTool2, cachePoint] + [MCPTool1, MCPTool2, cachePoint]\n const toolsWithCache: typeof params.toolConfig.tools = [];\n\n // Add core tools with cachePoint (if any)\n if (coreTools.length > 0) {\n toolsWithCache.push(...coreTools);\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n }\n\n // Add MCP tools with their own cachePoint (if any)\n if (mcpTools.length > 0) {\n toolsWithCache.push(...mcpTools);\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n }\n\n // If no tools at all (shouldn't happen but safety check)\n if (toolsWithCache.length === 0) {\n toolsWithCache.push({ cachePoint: { type: 'default' } });\n }\n\n params.toolConfig.tools = toolsWithCache;\n }\n\n // Add serviceTier support\n const serviceTierType = options?.serviceTier ?? this.serviceTier;\n\n return {\n ...params,\n serviceTier: serviceTierType ? { type: serviceTierType } : undefined,\n };\n }\n\n /**\n * Override _generateNonStreaming to use applicationInferenceProfile as modelId.\n * Uses the same model-swapping pattern as streaming for consistency.\n */\n override async _generateNonStreaming(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & IllumaBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): Promise<ChatResult> {\n const originalModel = this.model;\n if (\n this.applicationInferenceProfile != null &&\n this.applicationInferenceProfile !== ''\n ) {\n this.model = this.applicationInferenceProfile;\n }\n\n try {\n return await super._generateNonStreaming(messages, options, runManager);\n } finally {\n this.model = originalModel;\n }\n }\n\n /**\n * Own the stream end-to-end so we have direct access to every\n * `contentBlockDelta.contentBlockIndex` from the AWS SDK.\n *\n * This replaces the parent's implementation which strips contentBlockIndex\n * from text and reasoning deltas, making it impossible to merge correctly.\n */\n override async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & IllumaBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const { converseMessages, converseSystem } =\n convertToConverseMessages(messages);\n const params = this.invocationParams(options);\n\n let { streamUsage } = this;\n if ((options as Record<string, unknown>).streamUsage !== undefined) {\n streamUsage = (options as Record<string, unknown>).streamUsage as boolean;\n }\n\n const modelId = this.getModelId();\n\n const command = new ConverseStreamCommand({\n modelId,\n messages: converseMessages,\n system: converseSystem,\n ...(params as Record<string, unknown>),\n });\n\n const response = await this.client.send(command, {\n abortSignal: options.signal,\n });\n\n if (!response.stream) {\n return;\n }\n\n const seenBlockIndices = new Set<number>();\n\n for await (const event of response.stream) {\n if (event.contentBlockStart != null) {\n const startChunk = handleConverseStreamContentBlockStart(\n event.contentBlockStart\n );\n if (startChunk != null) {\n const idx = event.contentBlockStart.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n }\n yield this.enrichChunk(startChunk, seenBlockIndices);\n }\n } else if (event.contentBlockDelta != null) {\n const deltaChunk = handleConverseStreamContentBlockDelta(\n event.contentBlockDelta\n );\n\n const idx = event.contentBlockDelta.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n }\n\n yield this.enrichChunk(deltaChunk, seenBlockIndices);\n\n await runManager?.handleLLMNewToken(\n deltaChunk.text,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: deltaChunk }\n );\n } else if (event.metadata != null) {\n yield handleConverseStreamMetadata(event.metadata, { streamUsage });\n } else if (event.contentBlockStop != null) {\n const stopIdx = event.contentBlockStop.contentBlockIndex;\n if (stopIdx != null) {\n seenBlockIndices.add(stopIdx);\n }\n } else {\n yield new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n response_metadata: event,\n }),\n });\n }\n }\n }\n\n /**\n * Inject `index` on content blocks for proper merge behaviour, then strip\n * `contentBlockIndex` from response_metadata to prevent `_mergeDicts` conflicts.\n *\n * Text string content is promoted to array form only when the stream contains\n * multiple content block indices (e.g. reasoning at index 0, text at index 1),\n * ensuring text merges correctly with the already-array accumulated content.\n */\n enrichChunk(\n chunk: ChatGenerationChunk,\n seenBlockIndices: Set<number>\n ): ChatGenerationChunk {\n const message = chunk.message;\n if (!(message instanceof AIMessageChunk)) {\n return chunk;\n }\n\n const metadata = message.response_metadata as Record<string, unknown>;\n const blockIndex = this.extractContentBlockIndex(metadata);\n const hasMetadataIndex = blockIndex != null;\n\n let content: AIMessageChunk['content'] = message.content;\n let contentModified = false;\n\n if (Array.isArray(content) && blockIndex != null) {\n content = content.map((block) =>\n typeof block === 'object' && !('index' in block)\n ? { ...block, index: blockIndex }\n : block\n );\n contentModified = true;\n } else if (\n typeof content === 'string' &&\n content !== '' &&\n blockIndex != null &&\n seenBlockIndices.size > 1\n ) {\n content = [{ type: 'text', text: content, index: blockIndex }];\n contentModified = true;\n }\n\n if (!contentModified && !hasMetadataIndex) {\n return chunk;\n }\n\n const cleanedMetadata = hasMetadataIndex\n ? (this.removeContentBlockIndex(metadata) as Record<string, unknown>)\n : metadata;\n\n return new ChatGenerationChunk({\n text: chunk.text,\n message: new AIMessageChunk({\n ...message,\n content,\n response_metadata: cleanedMetadata,\n }),\n generationInfo: chunk.generationInfo,\n });\n }\n\n /**\n * Extract `contentBlockIndex` from the top level of response_metadata.\n * Our custom handlers always place it at the top level.\n */\n private extractContentBlockIndex(\n metadata: Record<string, unknown>\n ): number | undefined {\n if (\n 'contentBlockIndex' in metadata &&\n typeof metadata.contentBlockIndex === 'number'\n ) {\n return metadata.contentBlockIndex;\n }\n return undefined;\n }\n\n private removeContentBlockIndex(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.removeContentBlockIndex(item));\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key !== 'contentBlockIndex') {\n cleaned[key] = this.removeContentBlockIndex(value);\n }\n }\n return cleaned;\n }\n\n return obj;\n }\n}\n\n/** @deprecated Use IllumaBedrockConverse. Alias kept for backward compatibility with existing imports. */\nexport const CustomChatBedrockConverse = IllumaBedrockConverse;\n\nexport type { ChatBedrockConverseInput };\n"],"names":[],"mappings":"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"}
|
package/dist/esm/main.mjs
CHANGED
|
@@ -25,10 +25,26 @@ export { createSchemaOnlyTool, createSchemaOnlyTools } from './tools/schema.mjs'
|
|
|
25
25
|
export { handleServerToolResult, handleToolCallChunks, handleToolCalls, toolResultTypes } from './tools/handlers.mjs';
|
|
26
26
|
export { createSearchTool } from './tools/search/tool.mjs';
|
|
27
27
|
export { DATE_RANGE, DEFAULT_COUNTRY_DESCRIPTION, DEFAULT_QUERY_DESCRIPTION, WebSearchToolDefinition, WebSearchToolDescription, WebSearchToolName, WebSearchToolSchema, countrySchema, dateSchema, imagesSchema, newsSchema, querySchema, videosSchema } from './tools/search/schema.mjs';
|
|
28
|
+
export { buildMemoryTools } from './tools/memory/index.mjs';
|
|
29
|
+
export { DEFAULT_CITATIONS_MODE, DEFAULT_FLUSH_RESERVE_FLOOR_TOKENS, DEFAULT_FLUSH_SOFT_THRESHOLD_TOKENS, DEFAULT_MAX_APPENDS_PER_FLUSH, DEFAULT_MAX_FLUSH_ITERATIONS, DEFAULT_MAX_INJECTED_CHARS, DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_MEMORY_DIMENSIONS, DEFAULT_MEMORY_FLUSH_PROMPT, DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT, DEFAULT_MEMORY_MODEL, DEFAULT_MEMORY_PROVIDER, DEFAULT_MEMORY_SCHEMA, DEFAULT_MEMORY_TABLE, DEFAULT_MIN_SCORE, DEFAULT_MMR_ENABLED, DEFAULT_MMR_LAMBDA, DEFAULT_RECALL_TRACKING_ENABLED, DEFAULT_TEMPORAL_DECAY_ENABLED, DEFAULT_TEMPORAL_DECAY_HALF_LIFE_DAYS, FLUSH_PROMPT_RUBRIC_PLACEHOLDER, HYBRID_TEXT_WEIGHT, HYBRID_VECTOR_WEIGHT, MEMORY_APPEND_DESCRIPTION, MEMORY_APPEND_TOOL_NAME, MEMORY_GET_DESCRIPTION, MEMORY_GET_TOOL_NAME, MEMORY_PATH_PREFIX, MEMORY_PHASE_FLUSHING, MEMORY_PHASE_NORMAL, MEMORY_SEARCH_DESCRIPTION, MEMORY_SEARCH_TOOL_NAME, SILENT_REPLY_TOKEN } from './memory/constants.mjs';
|
|
30
|
+
export { MEMORY_AGENT_PATHS, MEMORY_ALL_PATHS, MEMORY_USER_PATHS, MEMORY_WRITABLE_PATHS, assertWritablePath, getPathDescriptor, getTierForPath, getWritablePathsForScope, renderPathsRubric } from './memory/paths.mjs';
|
|
31
|
+
export { PgvectorMemoryStore } from './memory/pgvectorStore.mjs';
|
|
32
|
+
export { CompositeMemoryBackend } from './memory/compositeBackend.mjs';
|
|
33
|
+
export { getMemoryEmbedder, resetMemoryEmbedder } from './memory/embeddings.mjs';
|
|
34
|
+
export { runMemoryMigration } from './memory/migrate.mjs';
|
|
35
|
+
export { buildMemoryBackendFromEnv } from './memory/factory.mjs';
|
|
36
|
+
export { DEFAULT_MMR_CONFIG, applyMMRToMemoryHits, computeMMRScore, jaccardSimilarity, mmrRerank, textSimilarity, tokenize } from './memory/mmr.mjs';
|
|
37
|
+
export { DEFAULT_TEMPORAL_DECAY_CONFIG, applyTemporalDecayToHits, applyTemporalDecayToScore, calculateTemporalDecayMultiplier, isEvergreenMemoryPath, parseMemoryDateFromPath } from './memory/temporalDecay.mjs';
|
|
38
|
+
export { decorateCitations, resolveMemoryCitationsMode, shouldIncludeCitations } from './memory/citations.mjs';
|
|
39
|
+
export { NullRecallTracker, PgvectorRecallTracker, RECALL_TABLE } from './memory/recallTracking.mjs';
|
|
40
|
+
export { MEMORY_FLUSH_SYSTEM_PROMPT } from './prompts/memoryFlushPrompt.mjs';
|
|
41
|
+
export { runMemoryFlush, shouldFlushMemory } from './graphs/phases/memoryFlushPhase.mjs';
|
|
42
|
+
export { bindToolsIfSupported, extractText as extractFlushText, parseToolResult as parseFlushToolResult, runFlushLoop } from './graphs/phases/flushLoop.mjs';
|
|
28
43
|
export { createValidationErrorMessage, isValidJsonSchema, normalizeJsonSchema, prepareSchemaForProvider, validateStructuredOutput, zodToJsonSchema } from './schemas/validate.mjs';
|
|
29
44
|
export { createApprovalGateNode, getApprovalGateNodeId } from './nodes/ApprovalGateNode.mjs';
|
|
30
45
|
export { Callback, CommonEvents, Constants, ContentTypes, DEFAULT_HANDOFF_MAX_RESULT_CHARS, EdgeType, EnvVar, FinishReasons, GraphEvents, GraphNodeActions, GraphNodeKeys, HANDOFF_TIMEOUT_MS, MessageTypes, Providers, StepTypes, TitleMethod, ToolCallTypes } from './common/enum.mjs';
|
|
31
46
|
export { COMPACTION_RECENT_ROUNDS, CONTEXT_SAFETY_BUFFER, DEDUP_MAX_CONTENT_LENGTH, MIN_THINKING_BUDGET, MULTI_DOCUMENT_THRESHOLD, PROACTIVE_SUMMARY_THRESHOLD, PRUNING_EMA_ALPHA, PRUNING_INITIAL_CALIBRATION, SUMMARIZATION_CONTEXT_THRESHOLD, SUMMARIZATION_RESERVE_RATIO, TOOL_DISCOVERY_CACHE_MAX_SIZE, TOOL_TURN_THINKING_BUDGET } from './common/constants.mjs';
|
|
47
|
+
export { MAX_NESTING_DEPTH, SPAWN_PATH_SEP, buildSpawnPath, isAncestorSpawnPath, leafSpawnKey, parentSpawnPath, spawnPathDepth, spawnPathParts } from './common/spawnPath.mjs';
|
|
32
48
|
export { ActionCategory, ApprovalPolicy, ApprovalTier, ExecutionContext, MCP_DELIMITER, RiskLevel } from './tools/approval/constants.mjs';
|
|
33
49
|
export { joinKeys, resetIfNotEmpty } from './utils/graph.mjs';
|
|
34
50
|
export { isGoogleLike, isOpenAILike } from './utils/llm.mjs';
|
|
@@ -47,4 +63,8 @@ export { FILE_MANIFEST_PREFIX, buildFileManifestBlock } from './utils/fileManife
|
|
|
47
63
|
export { CustomOpenAIClient } from './llm/openai/index.mjs';
|
|
48
64
|
export { ChatOpenRouter } from './llm/openrouter/index.mjs';
|
|
49
65
|
export { getChatModelClass, llmProviders } from './llm/providers.mjs';
|
|
66
|
+
export { MemoryAppendSchema, MemoryGetSchema, MemorySearchSchema, assertAppendAllowed, buildMemorySearchUnavailableResult, clampResultsByInjectedChars, toAppendInput } from './tools/memory/shared.mjs';
|
|
67
|
+
export { createMemoryAppendTool } from './tools/memory/memoryAppendTool.mjs';
|
|
68
|
+
export { createMemoryGetTool } from './tools/memory/memoryGetTool.mjs';
|
|
69
|
+
export { createMemorySearchTool } from './tools/memory/memorySearchTool.mjs';
|
|
50
70
|
//# sourceMappingURL=main.mjs.map
|
package/dist/esm/main.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"main.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Citation decoration — Phase 2.
|
|
3
|
+
*
|
|
4
|
+
* Ported from upstream `extensions/memory-core/src/tools.citations.ts`.
|
|
5
|
+
* Decorates memory_search hits with `[path#L{start}-L{end}]` markers so
|
|
6
|
+
* the model can attribute claims back to specific memory files when it
|
|
7
|
+
* uses them in its answer.
|
|
8
|
+
*
|
|
9
|
+
* Since our backend stores files as single rows (not line-chunked), we
|
|
10
|
+
* compute line ranges from the returned content block on the fly:
|
|
11
|
+
* - `startLine` = 1 (line 1 of the file)
|
|
12
|
+
* - `endLine` = total number of lines in the block
|
|
13
|
+
* This matches upstream's output format exactly while keeping the pg
|
|
14
|
+
* schema chunk-free.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_CITATIONS_MODE = 'auto';
|
|
17
|
+
function resolveMemoryCitationsMode(raw) {
|
|
18
|
+
if (raw === 'on' || raw === 'off' || raw === 'auto')
|
|
19
|
+
return raw;
|
|
20
|
+
return DEFAULT_CITATIONS_MODE;
|
|
21
|
+
}
|
|
22
|
+
function countLines(text) {
|
|
23
|
+
if (!text)
|
|
24
|
+
return 1;
|
|
25
|
+
return Math.max(1, text.split('\n').length);
|
|
26
|
+
}
|
|
27
|
+
function formatCitation(path, startLine, endLine) {
|
|
28
|
+
if (startLine === endLine)
|
|
29
|
+
return `${path}#L${startLine}`;
|
|
30
|
+
return `${path}#L${startLine}-L${endLine}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Decorate each hit with a citation marker. Mirrors upstream's behavior:
|
|
34
|
+
* appends `\n\nSource: <citation>` to the content and sets `citation`.
|
|
35
|
+
* When `include=false`, clears any existing citation field.
|
|
36
|
+
*/
|
|
37
|
+
function decorateCitations(hits, include) {
|
|
38
|
+
if (!include)
|
|
39
|
+
return hits.map((h) => ({ ...h, citation: undefined }));
|
|
40
|
+
return hits.map((h) => {
|
|
41
|
+
const start = Math.max(1, Math.floor(h.startLine ?? 1));
|
|
42
|
+
const end = Math.max(start, Math.floor(h.endLine ?? countLines(h.content)));
|
|
43
|
+
const citation = formatCitation(h.path, start, end);
|
|
44
|
+
const content = `${(h.content ?? '').trimEnd()}\n\nSource: ${citation}`;
|
|
45
|
+
return { ...h, citation, content, startLine: start, endLine: end };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether citations should be emitted for this call.
|
|
50
|
+
*
|
|
51
|
+
* Upstream keys `auto` off the session type (direct/group/channel). In
|
|
52
|
+
* Phase 1 we only have direct chat, so `auto` => `on`. Callers that
|
|
53
|
+
* later distinguish session types can pass `mode` explicitly.
|
|
54
|
+
*/
|
|
55
|
+
function shouldIncludeCitations(mode) {
|
|
56
|
+
if (mode === 'on')
|
|
57
|
+
return true;
|
|
58
|
+
if (mode === 'off')
|
|
59
|
+
return false;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { DEFAULT_CITATIONS_MODE, decorateCitations, resolveMemoryCitationsMode, shouldIncludeCitations };
|
|
64
|
+
//# sourceMappingURL=citations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"citations.mjs","sources":["../../../src/memory/citations.ts"],"sourcesContent":["/**\n * Citation decoration — Phase 2.\n *\n * Ported from upstream `extensions/memory-core/src/tools.citations.ts`.\n * Decorates memory_search hits with `[path#L{start}-L{end}]` markers so\n * the model can attribute claims back to specific memory files when it\n * uses them in its answer.\n *\n * Since our backend stores files as single rows (not line-chunked), we\n * compute line ranges from the returned content block on the fly:\n * - `startLine` = 1 (line 1 of the file)\n * - `endLine` = total number of lines in the block\n * This matches upstream's output format exactly while keeping the pg\n * schema chunk-free.\n */\n\nexport type MemoryCitationsMode = 'on' | 'off' | 'auto';\n\nexport const DEFAULT_CITATIONS_MODE: MemoryCitationsMode = 'auto';\n\nexport function resolveMemoryCitationsMode(\n raw: string | undefined | null\n): MemoryCitationsMode {\n if (raw === 'on' || raw === 'off' || raw === 'auto') return raw;\n return DEFAULT_CITATIONS_MODE;\n}\n\nexport interface CitationCandidate {\n path: string;\n content: string;\n startLine?: number;\n endLine?: number;\n citation?: string;\n}\n\nfunction countLines(text: string): number {\n if (!text) return 1;\n return Math.max(1, text.split('\\n').length);\n}\n\nfunction formatCitation(\n path: string,\n startLine: number,\n endLine: number\n): string {\n if (startLine === endLine) return `${path}#L${startLine}`;\n return `${path}#L${startLine}-L${endLine}`;\n}\n\n/**\n * Decorate each hit with a citation marker. Mirrors upstream's behavior:\n * appends `\\n\\nSource: <citation>` to the content and sets `citation`.\n * When `include=false`, clears any existing citation field.\n */\nexport function decorateCitations<T extends CitationCandidate>(\n hits: T[],\n include: boolean\n): T[] {\n if (!include) return hits.map((h) => ({ ...h, citation: undefined }));\n return hits.map((h) => {\n const start = Math.max(1, Math.floor(h.startLine ?? 1));\n const end = Math.max(start, Math.floor(h.endLine ?? countLines(h.content)));\n const citation = formatCitation(h.path, start, end);\n const content = `${(h.content ?? '').trimEnd()}\\n\\nSource: ${citation}`;\n return { ...h, citation, content, startLine: start, endLine: end };\n });\n}\n\n/**\n * Whether citations should be emitted for this call.\n *\n * Upstream keys `auto` off the session type (direct/group/channel). In\n * Phase 1 we only have direct chat, so `auto` => `on`. Callers that\n * later distinguish session types can pass `mode` explicitly.\n */\nexport function shouldIncludeCitations(mode: MemoryCitationsMode): boolean {\n if (mode === 'on') return true;\n if (mode === 'off') return false;\n return true;\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;AAcG;AAII,MAAM,sBAAsB,GAAwB;AAErD,SAAU,0BAA0B,CACxC,GAA8B,EAAA;IAE9B,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM;AAAE,QAAA,OAAO,GAAG;AAC/D,IAAA,OAAO,sBAAsB;AAC/B;AAUA,SAAS,UAAU,CAAC,IAAY,EAAA;AAC9B,IAAA,IAAI,CAAC,IAAI;AAAE,QAAA,OAAO,CAAC;AACnB,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AAC7C;AAEA,SAAS,cAAc,CACrB,IAAY,EACZ,SAAiB,EACjB,OAAe,EAAA;IAEf,IAAI,SAAS,KAAK,OAAO;AAAE,QAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAA,EAAK,SAAS,EAAE;AACzD,IAAA,OAAO,GAAG,IAAI,CAAA,EAAA,EAAK,SAAS,CAAA,EAAA,EAAK,OAAO,EAAE;AAC5C;AAEA;;;;AAIG;AACG,SAAU,iBAAiB,CAC/B,IAAS,EACT,OAAgB,EAAA;AAEhB,IAAA,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;AACrE,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAI;AACpB,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3E,QAAA,MAAM,QAAQ,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC;AACnD,QAAA,MAAM,OAAO,GAAG,CAAA,EAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,OAAO,EAAE,CAAA,YAAA,EAAe,QAAQ,EAAE;AACvE,QAAA,OAAO,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;AACpE,IAAA,CAAC,CAAC;AACJ;AAEA;;;;;;AAMG;AACG,SAAU,sBAAsB,CAAC,IAAyB,EAAA;IAC9D,IAAI,IAAI,KAAK,IAAI;AAAE,QAAA,OAAO,IAAI;IAC9B,IAAI,IAAI,KAAK,KAAK;AAAE,QAAA,OAAO,KAAK;AAChC,IAAA,OAAO,IAAI;AACb;;;;"}
|