@strands-agents/sdk 1.5.0 → 1.6.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/src/__fixtures__/agent-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/agent-helpers.js +3 -0
- package/dist/src/__fixtures__/agent-helpers.js.map +1 -1
- package/dist/src/agent/__tests__/agent.stateful-model.test.js +2 -2
- package/dist/src/agent/__tests__/agent.stateful-model.test.js.map +1 -1
- package/dist/src/agent/agent.d.ts +8 -5
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +60 -33
- package/dist/src/agent/agent.js.map +1 -1
- package/dist/src/context-manager/modes/agentic/agentic-context.d.ts +19 -0
- package/dist/src/context-manager/modes/agentic/agentic-context.d.ts.map +1 -0
- package/dist/src/context-manager/modes/agentic/agentic-context.js +245 -0
- package/dist/src/context-manager/modes/agentic/agentic-context.js.map +1 -0
- package/dist/src/conversation-manager/__tests__/agentic-context.test.d.ts +2 -0
- package/dist/src/conversation-manager/__tests__/agentic-context.test.d.ts.map +1 -0
- package/dist/src/conversation-manager/__tests__/agentic-context.test.js +332 -0
- package/dist/src/conversation-manager/__tests__/agentic-context.test.js.map +1 -0
- package/dist/src/conversation-manager/__tests__/context-compression.test.d.ts +2 -0
- package/dist/src/conversation-manager/__tests__/context-compression.test.d.ts.map +1 -0
- package/dist/src/conversation-manager/__tests__/context-compression.test.js +176 -0
- package/dist/src/conversation-manager/__tests__/context-compression.test.js.map +1 -0
- package/dist/src/conversation-manager/__tests__/pin.test.js +1 -1
- package/dist/src/conversation-manager/__tests__/pin.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +1 -1
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js +1 -1
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.d.ts +2 -0
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.d.ts.map +1 -0
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.js +138 -0
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.js.map +1 -0
- package/dist/src/conversation-manager/compression/context-compression.d.ts +39 -0
- package/dist/src/conversation-manager/compression/context-compression.d.ts.map +1 -0
- package/dist/src/conversation-manager/compression/context-compression.js +150 -0
- package/dist/src/conversation-manager/compression/context-compression.js.map +1 -0
- package/dist/src/conversation-manager/{pin-message.d.ts → compression/pin-message.d.ts} +1 -1
- package/dist/src/conversation-manager/compression/pin-message.d.ts.map +1 -0
- package/dist/src/conversation-manager/{pin-message.js → compression/pin-message.js} +1 -1
- package/dist/src/conversation-manager/compression/pin-message.js.map +1 -0
- package/dist/src/conversation-manager/conversation-manager.d.ts +2 -0
- package/dist/src/conversation-manager/conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/conversation-manager.js +2 -2
- package/dist/src/conversation-manager/conversation-manager.js.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js +4 -35
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js.map +1 -1
- package/dist/src/conversation-manager/summarizing-conversation-manager.d.ts +0 -19
- package/dist/src/conversation-manager/summarizing-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/summarizing-conversation-manager.js +4 -107
- package/dist/src/conversation-manager/summarizing-conversation-manager.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/injection/__tests__/message-injection.test.d.ts +2 -0
- package/dist/src/injection/__tests__/message-injection.test.d.ts.map +1 -0
- package/dist/src/injection/__tests__/message-injection.test.js +200 -0
- package/dist/src/injection/__tests__/message-injection.test.js.map +1 -0
- package/dist/src/injection/index.d.ts +6 -0
- package/dist/src/injection/index.d.ts.map +1 -0
- package/dist/src/injection/index.js +2 -0
- package/dist/src/injection/index.js.map +1 -0
- package/dist/src/injection/message-injection.d.ts +65 -0
- package/dist/src/injection/message-injection.d.ts.map +1 -0
- package/dist/src/injection/message-injection.js +134 -0
- package/dist/src/injection/message-injection.js.map +1 -0
- package/dist/src/injection/types.d.ts +63 -0
- package/dist/src/injection/types.d.ts.map +1 -0
- package/dist/src/injection/types.js +2 -0
- package/dist/src/injection/types.js.map +1 -0
- package/dist/src/injection/xml.d.ts +27 -0
- package/dist/src/injection/xml.d.ts.map +1 -0
- package/dist/src/injection/xml.js +31 -0
- package/dist/src/injection/xml.js.map +1 -0
- package/dist/src/memory/__tests__/memory-manager.test.js +187 -1
- package/dist/src/memory/__tests__/memory-manager.test.js.map +1 -1
- package/dist/src/memory/index.d.ts +2 -1
- package/dist/src/memory/index.d.ts.map +1 -1
- package/dist/src/memory/index.js.map +1 -1
- package/dist/src/memory/memory-manager.d.ts +49 -2
- package/dist/src/memory/memory-manager.d.ts.map +1 -1
- package/dist/src/memory/memory-manager.js +124 -2
- package/dist/src/memory/memory-manager.js.map +1 -1
- package/dist/src/memory/types.d.ts +65 -0
- package/dist/src/memory/types.d.ts.map +1 -1
- package/dist/src/middleware/__tests__/agent-middleware.test.js +0 -1
- package/dist/src/middleware/__tests__/agent-middleware.test.js.map +1 -1
- package/dist/src/middleware/__tests__/copy-on-input.test.d.ts +2 -0
- package/dist/src/middleware/__tests__/copy-on-input.test.d.ts.map +1 -0
- package/dist/src/middleware/__tests__/copy-on-input.test.js +379 -0
- package/dist/src/middleware/__tests__/copy-on-input.test.js.map +1 -0
- package/dist/src/middleware/stages.d.ts +14 -7
- package/dist/src/middleware/stages.d.ts.map +1 -1
- package/dist/src/middleware/stages.js +3 -0
- package/dist/src/middleware/stages.js.map +1 -1
- package/dist/src/sandbox/__tests__/docker.test.node.js +2 -2
- package/dist/src/sandbox/__tests__/docker.test.node.js.map +1 -1
- package/dist/src/sandbox/__tests__/ssh.test.node.js +2 -2
- package/dist/src/sandbox/__tests__/ssh.test.node.js.map +1 -1
- package/dist/src/sandbox/base.d.ts +0 -5
- package/dist/src/sandbox/base.d.ts.map +1 -1
- package/dist/src/sandbox/base.js +0 -5
- package/dist/src/sandbox/base.js.map +1 -1
- package/dist/src/sandbox/docker.d.ts.map +1 -1
- package/dist/src/sandbox/docker.js +2 -0
- package/dist/src/sandbox/docker.js.map +1 -1
- package/dist/src/sandbox/ssh.d.ts.map +1 -1
- package/dist/src/sandbox/ssh.js +2 -0
- package/dist/src/sandbox/ssh.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/types/messages.d.ts +8 -0
- package/dist/src/types/messages.d.ts.map +1 -1
- package/dist/src/types/messages.js +13 -1
- package/dist/src/types/messages.js.map +1 -1
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.d.ts +2 -0
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.d.ts.map +1 -0
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.js +675 -0
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.js.map +1 -0
- package/dist/src/vended-interventions/cedar/cedar.d.ts +102 -0
- package/dist/src/vended-interventions/cedar/cedar.d.ts.map +1 -0
- package/dist/src/vended-interventions/cedar/cedar.js +228 -0
- package/dist/src/vended-interventions/cedar/cedar.js.map +1 -0
- package/dist/src/vended-interventions/cedar/index.d.ts +3 -0
- package/dist/src/vended-interventions/cedar/index.d.ts.map +1 -0
- package/dist/src/vended-interventions/cedar/index.js +2 -0
- package/dist/src/vended-interventions/cedar/index.js.map +1 -0
- package/dist/src/vended-interventions/cedar/schema-generator.d.ts +10 -0
- package/dist/src/vended-interventions/cedar/schema-generator.d.ts.map +1 -0
- package/dist/src/vended-interventions/cedar/schema-generator.js +33 -0
- package/dist/src/vended-interventions/cedar/schema-generator.js.map +1 -0
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.d.ts +2 -0
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.js +96 -0
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.js.map +1 -0
- package/dist/src/vended-plugins/context-injector/index.d.ts +25 -0
- package/dist/src/vended-plugins/context-injector/index.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-injector/index.js +23 -0
- package/dist/src/vended-plugins/context-injector/index.js.map +1 -0
- package/dist/src/vended-plugins/context-injector/plugin.d.ts +55 -0
- package/dist/src/vended-plugins/context-injector/plugin.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-injector/plugin.js +41 -0
- package/dist/src/vended-plugins/context-injector/plugin.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js +43 -4
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js +68 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/plugin.d.ts.map +1 -1
- package/dist/src/vended-plugins/context-offloader/plugin.js +12 -2
- package/dist/src/vended-plugins/context-offloader/plugin.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/storage.d.ts +29 -1
- package/dist/src/vended-plugins/context-offloader/storage.d.ts.map +1 -1
- package/dist/src/vended-plugins/context-offloader/storage.js +61 -3
- package/dist/src/vended-plugins/context-offloader/storage.js.map +1 -1
- package/dist/src/vended-plugins/index.d.ts +2 -1
- package/dist/src/vended-plugins/index.d.ts.map +1 -1
- package/dist/src/vended-plugins/index.js +2 -1
- package/dist/src/vended-plugins/index.js.map +1 -1
- package/package.json +19 -1
- package/dist/src/conversation-manager/pin-message.d.ts.map +0 -1
- package/dist/src/conversation-manager/pin-message.js.map +0 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Message, TextBlock, ToolResultBlock } from '../../types/messages.js';
|
|
3
|
+
import { foldIntoLastUserMessage, isUserTurn, resolveTrigger, createInjectionMiddleware } from '../message-injection.js';
|
|
4
|
+
import { createMockAgent } from '../../__fixtures__/agent-helpers.js';
|
|
5
|
+
import { logger } from '../../logging/logger.js';
|
|
6
|
+
const user = (text) => new Message({ role: 'user', content: [new TextBlock(text)] });
|
|
7
|
+
const assistant = (text) => new Message({ role: 'assistant', content: [new TextBlock(text)] });
|
|
8
|
+
const toolResult = () => new Message({
|
|
9
|
+
role: 'user',
|
|
10
|
+
content: [new ToolResultBlock({ toolUseId: 't1', status: 'success', content: [new TextBlock('done')] })],
|
|
11
|
+
});
|
|
12
|
+
// resolveTrigger predicates take an InjectionContext; tests only exercise `messages`, so a minimal bag suffices.
|
|
13
|
+
const injectionCtx = (messages) => ({ messages });
|
|
14
|
+
describe('foldIntoLastUserMessage', () => {
|
|
15
|
+
it('prepends the text as a leading TextBlock on the last user message, ahead of its content', () => {
|
|
16
|
+
const messages = [user('original task'), assistant('prior step'), user('next ask')];
|
|
17
|
+
const result = foldIntoLastUserMessage(messages, 'INJECTED');
|
|
18
|
+
// The earlier user/assistant turns are untouched; the last user message gains a leading INJECTED
|
|
19
|
+
// block ahead of its own content, keeping the user's ask in the recency slot.
|
|
20
|
+
expect(result.map((m) => m.toJSON())).toStrictEqual([
|
|
21
|
+
{ role: 'user', content: [{ text: 'original task' }] },
|
|
22
|
+
{ role: 'assistant', content: [{ text: 'prior step' }] },
|
|
23
|
+
{ role: 'user', content: [{ text: 'INJECTED' }, { text: 'next ask' }] },
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
it('returns a new array and does not mutate the input or its messages', () => {
|
|
27
|
+
const original = user('ask');
|
|
28
|
+
const messages = [assistant('prior'), original];
|
|
29
|
+
const result = foldIntoLastUserMessage(messages, 'INJECTED');
|
|
30
|
+
expect(result).not.toBe(messages);
|
|
31
|
+
expect(messages[1]).toBe(original);
|
|
32
|
+
expect(original.content).toHaveLength(1); // untouched
|
|
33
|
+
expect(result[1]).not.toBe(original);
|
|
34
|
+
});
|
|
35
|
+
it('appends after the tool result block when the target is a tool-result turn', () => {
|
|
36
|
+
const tr = toolResult();
|
|
37
|
+
const result = foldIntoLastUserMessage([user('task'), assistant('thinking'), tr], 'INJECTED');
|
|
38
|
+
// Providers require the tool result to be the first block in the turn, so the injected text is
|
|
39
|
+
// appended rather than prepended here.
|
|
40
|
+
expect(result.map((m) => m.toJSON())).toStrictEqual([
|
|
41
|
+
{ role: 'user', content: [{ text: 'task' }] },
|
|
42
|
+
{ role: 'assistant', content: [{ text: 'thinking' }] },
|
|
43
|
+
{ role: 'user', content: [tr.toJSON().content[0], { text: 'INJECTED' }] },
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
it('targets the most recent user message when several exist', () => {
|
|
47
|
+
const messages = [user('first'), assistant('a'), user('second')];
|
|
48
|
+
const result = foldIntoLastUserMessage(messages, 'INJECTED');
|
|
49
|
+
expect(result.map((m) => m.toJSON())).toStrictEqual([
|
|
50
|
+
{ role: 'user', content: [{ text: 'first' }] }, // earlier user untouched
|
|
51
|
+
{ role: 'assistant', content: [{ text: 'a' }] },
|
|
52
|
+
{ role: 'user', content: [{ text: 'INJECTED' }, { text: 'second' }] },
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
it('preserves message metadata on the folded message', () => {
|
|
56
|
+
const tagged = new Message({
|
|
57
|
+
role: 'user',
|
|
58
|
+
content: [new TextBlock('ask')],
|
|
59
|
+
metadata: { custom: { keep: 'me' } },
|
|
60
|
+
});
|
|
61
|
+
const result = foldIntoLastUserMessage([tagged], 'INJECTED');
|
|
62
|
+
expect(result[0].metadata?.custom).toStrictEqual({ keep: 'me' });
|
|
63
|
+
});
|
|
64
|
+
it('returns the input unchanged when there is no user message', () => {
|
|
65
|
+
const messages = [assistant('only assistant')];
|
|
66
|
+
const result = foldIntoLastUserMessage(messages, 'INJECTED');
|
|
67
|
+
expect(result).toBe(messages);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('isUserTurn', () => {
|
|
71
|
+
it('is true when the last message is a plain user ask', () => {
|
|
72
|
+
expect(isUserTurn([assistant('prior').toJSON(), user('ask').toJSON()])).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it('is false when the last message is a user tool-result turn', () => {
|
|
75
|
+
expect(isUserTurn([user('task').toJSON(), assistant('a').toJSON(), toolResult().toJSON()])).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
it('is false when the last message is an assistant message', () => {
|
|
78
|
+
expect(isUserTurn([user('ask').toJSON(), assistant('reply').toJSON()])).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
it('is false for an empty conversation', () => {
|
|
81
|
+
expect(isUserTurn([])).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('resolveTrigger', () => {
|
|
85
|
+
it('defaults (undefined) to the userTurn policy', () => {
|
|
86
|
+
const trigger = resolveTrigger(undefined);
|
|
87
|
+
expect(trigger(injectionCtx([user('ask').toJSON()]))).toBe(true);
|
|
88
|
+
expect(trigger(injectionCtx([toolResult().toJSON()]))).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
it("'userTurn' uses isUserTurn", () => {
|
|
91
|
+
const trigger = resolveTrigger('userTurn');
|
|
92
|
+
expect(trigger(injectionCtx([user('ask').toJSON()]))).toBe(true);
|
|
93
|
+
expect(trigger(injectionCtx([toolResult().toJSON()]))).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it("'everyTurn' always fires", () => {
|
|
96
|
+
const trigger = resolveTrigger('everyTurn');
|
|
97
|
+
expect(trigger(injectionCtx([]))).toBe(true);
|
|
98
|
+
expect(trigger(injectionCtx([toolResult().toJSON()]))).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it('uses a custom predicate over the context', () => {
|
|
101
|
+
const trigger = resolveTrigger((context) => context.messages.length >= 2);
|
|
102
|
+
expect(trigger(injectionCtx([user('a').toJSON()]))).toBe(false);
|
|
103
|
+
expect(trigger(injectionCtx([user('a').toJSON(), assistant('b').toJSON()]))).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
it('fails open (returns false, logs) when a custom predicate throws', () => {
|
|
106
|
+
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => { });
|
|
107
|
+
const trigger = resolveTrigger(() => {
|
|
108
|
+
throw new Error('boom');
|
|
109
|
+
});
|
|
110
|
+
expect(trigger(injectionCtx([user('ask').toJSON()]))).toBe(false);
|
|
111
|
+
expect(warn).toHaveBeenCalled();
|
|
112
|
+
warn.mockRestore();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('createInjectionMiddleware', () => {
|
|
116
|
+
// The handler is an InvokeModelStage.Input transformer. It reads `context.messages` and derives the
|
|
117
|
+
// InjectionContext (appState/agent) from `context.agent`, then spreads the rest through, so a context
|
|
118
|
+
// carrying `messages` plus a mock agent exercises it faithfully.
|
|
119
|
+
const ctx = (messages) => ({ messages, agent: createMockAgent() });
|
|
120
|
+
it('folds renderContent() text into the latest user message, leaving other context fields intact', async () => {
|
|
121
|
+
const handler = createInjectionMiddleware({ renderContent: async () => 'INJECTED' });
|
|
122
|
+
const result = await handler(ctx([assistant('prior'), user('ask')]));
|
|
123
|
+
expect(result.messages.map((m) => m.toJSON())).toStrictEqual([
|
|
124
|
+
{ role: 'assistant', content: [{ text: 'prior' }] },
|
|
125
|
+
{ role: 'user', content: [{ text: 'INJECTED' }, { text: 'ask' }] },
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
it('passes an InjectionContext carrying the conversation (as data) to renderContent', async () => {
|
|
129
|
+
const seen = [];
|
|
130
|
+
const handler = createInjectionMiddleware({
|
|
131
|
+
renderContent: async (context) => {
|
|
132
|
+
seen.push(...context.messages.map((m) => m.role));
|
|
133
|
+
return 'x';
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
await handler(ctx([assistant('prior'), user('ask')]));
|
|
137
|
+
expect(seen).toStrictEqual(['assistant', 'user']);
|
|
138
|
+
});
|
|
139
|
+
it('exposes appState and the agent on the InjectionContext', async () => {
|
|
140
|
+
const appState = { get: () => 'stashed' };
|
|
141
|
+
const agent = { appState };
|
|
142
|
+
const input = { messages: [user('ask')], agent };
|
|
143
|
+
let received;
|
|
144
|
+
const handler = createInjectionMiddleware({
|
|
145
|
+
renderContent: async (context) => {
|
|
146
|
+
received = { appState: context.appState, agent: context.agent };
|
|
147
|
+
return undefined;
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
await handler(input);
|
|
151
|
+
expect(received).toStrictEqual({ appState, agent });
|
|
152
|
+
});
|
|
153
|
+
it('returns the context unchanged when the trigger does not fire', async () => {
|
|
154
|
+
const renderContent = vi.fn(async () => 'x');
|
|
155
|
+
const handler = createInjectionMiddleware({ renderContent }); // default 'userTurn'
|
|
156
|
+
const input = ctx([user('task'), assistant('a'), toolResult()]);
|
|
157
|
+
const result = await handler(input);
|
|
158
|
+
expect(result).toBe(input);
|
|
159
|
+
expect(renderContent).not.toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
it("'everyTurn' injects on an autonomous tool-result turn, keeping the tool result first", async () => {
|
|
162
|
+
const handler = createInjectionMiddleware({ trigger: 'everyTurn', renderContent: async () => 'INJECTED' });
|
|
163
|
+
const tr = toolResult();
|
|
164
|
+
const result = await handler(ctx([user('task'), assistant('a'), tr]));
|
|
165
|
+
// The most recent user message on a tool-result turn carries the tool result, which must stay the
|
|
166
|
+
// first block, so the injected text is appended after it.
|
|
167
|
+
expect(result.messages.map((m) => m.toJSON())).toStrictEqual([
|
|
168
|
+
{ role: 'user', content: [{ text: 'task' }] },
|
|
169
|
+
{ role: 'assistant', content: [{ text: 'a' }] },
|
|
170
|
+
{ role: 'user', content: [tr.toJSON().content[0], { text: 'INJECTED' }] },
|
|
171
|
+
]);
|
|
172
|
+
});
|
|
173
|
+
it('returns the context unchanged when renderContent yields empty text', async () => {
|
|
174
|
+
const handler = createInjectionMiddleware({ renderContent: async () => ' ' });
|
|
175
|
+
const input = ctx([assistant('prior'), user('ask')]);
|
|
176
|
+
const result = await handler(input);
|
|
177
|
+
expect(result).toBe(input);
|
|
178
|
+
});
|
|
179
|
+
it('fails open (returns the context unchanged, logs) when renderContent throws', async () => {
|
|
180
|
+
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => { });
|
|
181
|
+
const handler = createInjectionMiddleware({
|
|
182
|
+
renderContent: async () => {
|
|
183
|
+
throw new Error('boom');
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
const input = ctx([assistant('prior'), user('ask')]);
|
|
187
|
+
const result = await handler(input);
|
|
188
|
+
expect(result).toBe(input);
|
|
189
|
+
expect(warn).toHaveBeenCalled();
|
|
190
|
+
warn.mockRestore();
|
|
191
|
+
});
|
|
192
|
+
it('does not mutate the original context messages', async () => {
|
|
193
|
+
const handler = createInjectionMiddleware({ renderContent: async () => 'INJECTED' });
|
|
194
|
+
const input = ctx([assistant('prior'), user('ask')]);
|
|
195
|
+
const before = input.messages[1];
|
|
196
|
+
await handler(input);
|
|
197
|
+
expect(before.content).toHaveLength(1); // the original user message is untouched
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=message-injection.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-injection.test.js","sourceRoot":"","sources":["../../../../src/injection/__tests__/message-injection.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/injection/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/injection/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Message } from '../types/messages.js';
|
|
2
|
+
import type { MessageData } from '../types/messages.js';
|
|
3
|
+
import type { InvokeModelContext } from '../middleware/index.js';
|
|
4
|
+
import type { InjectionMiddlewareOptions, InjectionTrigger, InjectionContext } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Builds an `InvokeModelStage` `Input` handler that folds {@link InjectionMiddlewareOptions.renderContent}'s
|
|
7
|
+
* text into the latest user message, ephemerally — the model sees the augmented input for this one call
|
|
8
|
+
* while the agent's durable history is never touched.
|
|
9
|
+
*
|
|
10
|
+
* Runs as an input-phase transformer (`(ctx) => ctx`): it gates on the resolved trigger, asks
|
|
11
|
+
* `renderContent` for the text, and returns a context with the folded messages. Anything that skips —
|
|
12
|
+
* the trigger not firing, `renderContent` returning empty, or any callback throwing — returns the
|
|
13
|
+
* context unchanged so the model call proceeds (fail open). The injected text never enters durable
|
|
14
|
+
* history because the input phase only rewrites the per-call context, not the agent's stored messages.
|
|
15
|
+
*
|
|
16
|
+
* @param opts - The trigger and `renderContent` callback the handler uses
|
|
17
|
+
* @returns An `InvokeModelStage.Input` handler that returns a (possibly) folded context
|
|
18
|
+
* @internal Delivery primitive. Reach injection through `ContextInjector` or `MemoryManager`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createInjectionMiddleware(opts: InjectionMiddlewareOptions): (context: InvokeModelContext) => Promise<InvokeModelContext>;
|
|
21
|
+
/**
|
|
22
|
+
* Resolves an {@link InjectionTrigger} name or predicate into a single gate predicate over the
|
|
23
|
+
* {@link InjectionContext}.
|
|
24
|
+
*
|
|
25
|
+
* `'userTurn'` maps to {@link isUserTurn} (over `ctx.messages`); `'everyTurn'` to an always-true gate;
|
|
26
|
+
* a user-supplied predicate is wrapped so that a throw fails open (logs and skips injection rather than
|
|
27
|
+
* aborting the model call).
|
|
28
|
+
*
|
|
29
|
+
* @param trigger - An {@link InjectionTrigger} name, a predicate, or `undefined` (defaults to `'userTurn'`)
|
|
30
|
+
* @returns A predicate that, given the {@link InjectionContext}, returns whether to inject this call
|
|
31
|
+
* @internal Delivery primitive. Reach injection through `ContextInjector` or `MemoryManager`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveTrigger(trigger: InjectionTrigger | ((context: InjectionContext) => boolean) | undefined): (context: InjectionContext) => boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the latest message is a fresh user ask: a `user` message carrying no tool result. This is
|
|
36
|
+
* the `'userTurn'` policy — it distinguishes a new chat ask from an autonomous tool-result turn.
|
|
37
|
+
*
|
|
38
|
+
* @param messages - The current conversation, as data
|
|
39
|
+
* @returns `true` when the latest message is a plain user ask, otherwise `false`
|
|
40
|
+
* @internal Delivery primitive. Reach injection through `ContextInjector` or `MemoryManager`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isUserTurn(messages: MessageData[]): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Folds `text` into the most recent `user` message as a {@link TextBlock}, returning a NEW array. Other
|
|
45
|
+
* messages are returned as-is.
|
|
46
|
+
*
|
|
47
|
+
* Folding into the existing user message (rather than inserting a standalone message) keeps role
|
|
48
|
+
* alternation valid in both chat and the autonomous tool loop. The block is placed to keep the message
|
|
49
|
+
* valid for the model:
|
|
50
|
+
* - A plain user ask: the text is **prepended**, leaving the user's own ask in the recency slot — the
|
|
51
|
+
* last thing the model reads.
|
|
52
|
+
* - A tool-result turn (the message carries a `ToolResultBlock`): the text is **appended**,
|
|
53
|
+
* because providers require the tool result to be the first content block in the turn that answers a
|
|
54
|
+
* tool use.
|
|
55
|
+
*
|
|
56
|
+
* {@link Message} fields are readonly, so the target is rebuilt as a new {@link Message}. When there is
|
|
57
|
+
* no `user` message, the input array is returned unchanged.
|
|
58
|
+
*
|
|
59
|
+
* @param messages - The conversation to fold into
|
|
60
|
+
* @param text - The text to fold into the most recent user message
|
|
61
|
+
* @returns A new array with the folded message, or the input array when there is no user message
|
|
62
|
+
* @internal Delivery primitive. Reach injection through `ContextInjector` or `MemoryManager`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function foldIntoLastUserMessage(messages: Message[], text: string): Message[];
|
|
65
|
+
//# sourceMappingURL=message-injection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-injection.d.ts","sourceRoot":"","sources":["../../../src/injection/message-injection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAa,MAAM,sBAAsB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAEhG;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CA0B9D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,gBAAgB,GAAG,CAAC,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,GAAG,SAAS,GAC/E,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAgBxC;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAG3D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CA2BpF"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Message, TextBlock } from '../types/messages.js';
|
|
2
|
+
import { logger } from '../logging/logger.js';
|
|
3
|
+
import { normalizeError } from '../errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds an `InvokeModelStage` `Input` handler that folds {@link InjectionMiddlewareOptions.renderContent}'s
|
|
6
|
+
* text into the latest user message, ephemerally — the model sees the augmented input for this one call
|
|
7
|
+
* while the agent's durable history is never touched.
|
|
8
|
+
*
|
|
9
|
+
* Runs as an input-phase transformer (`(ctx) => ctx`): it gates on the resolved trigger, asks
|
|
10
|
+
* `renderContent` for the text, and returns a context with the folded messages. Anything that skips —
|
|
11
|
+
* the trigger not firing, `renderContent` returning empty, or any callback throwing — returns the
|
|
12
|
+
* context unchanged so the model call proceeds (fail open). The injected text never enters durable
|
|
13
|
+
* history because the input phase only rewrites the per-call context, not the agent's stored messages.
|
|
14
|
+
*
|
|
15
|
+
* @param opts - The trigger and `renderContent` callback the handler uses
|
|
16
|
+
* @returns An `InvokeModelStage.Input` handler that returns a (possibly) folded context
|
|
17
|
+
* @internal Delivery primitive. Reach injection through `ContextInjector` or `MemoryManager`.
|
|
18
|
+
*/
|
|
19
|
+
export function createInjectionMiddleware(opts) {
|
|
20
|
+
const trigger = resolveTrigger(opts.trigger);
|
|
21
|
+
return async (context) => {
|
|
22
|
+
const agent = context.agent;
|
|
23
|
+
const injectionContext = {
|
|
24
|
+
messages: context.messages.map((message) => message.toJSON()),
|
|
25
|
+
appState: agent.appState,
|
|
26
|
+
agent,
|
|
27
|
+
};
|
|
28
|
+
if (!trigger(injectionContext)) {
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
let text;
|
|
32
|
+
try {
|
|
33
|
+
text = await opts.renderContent(injectionContext);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.warn(`reason=<${normalizeError(error).message}> | injection renderContent threw; skipping injection`);
|
|
37
|
+
return context;
|
|
38
|
+
}
|
|
39
|
+
if (!text?.trim()) {
|
|
40
|
+
return context;
|
|
41
|
+
}
|
|
42
|
+
return { ...context, messages: foldIntoLastUserMessage([...context.messages], text) };
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolves an {@link InjectionTrigger} name or predicate into a single gate predicate over the
|
|
47
|
+
* {@link InjectionContext}.
|
|
48
|
+
*
|
|
49
|
+
* `'userTurn'` maps to {@link isUserTurn} (over `ctx.messages`); `'everyTurn'` to an always-true gate;
|
|
50
|
+
* a user-supplied predicate is wrapped so that a throw fails open (logs and skips injection rather than
|
|
51
|
+
* aborting the model call).
|
|
52
|
+
*
|
|
53
|
+
* @param trigger - An {@link InjectionTrigger} name, a predicate, or `undefined` (defaults to `'userTurn'`)
|
|
54
|
+
* @returns A predicate that, given the {@link InjectionContext}, returns whether to inject this call
|
|
55
|
+
* @internal Delivery primitive. Reach injection through `ContextInjector` or `MemoryManager`.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveTrigger(trigger) {
|
|
58
|
+
if (trigger === undefined || trigger === 'userTurn') {
|
|
59
|
+
return (context) => isUserTurn(context.messages);
|
|
60
|
+
}
|
|
61
|
+
if (trigger === 'everyTurn') {
|
|
62
|
+
return () => true;
|
|
63
|
+
}
|
|
64
|
+
const predicate = trigger;
|
|
65
|
+
return (context) => {
|
|
66
|
+
try {
|
|
67
|
+
return predicate(context);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.warn(`reason=<${normalizeError(error).message}> | injection trigger threw; skipping injection`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether the latest message is a fresh user ask: a `user` message carrying no tool result. This is
|
|
77
|
+
* the `'userTurn'` policy — it distinguishes a new chat ask from an autonomous tool-result turn.
|
|
78
|
+
*
|
|
79
|
+
* @param messages - The current conversation, as data
|
|
80
|
+
* @returns `true` when the latest message is a plain user ask, otherwise `false`
|
|
81
|
+
* @internal Delivery primitive. Reach injection through `ContextInjector` or `MemoryManager`.
|
|
82
|
+
*/
|
|
83
|
+
export function isUserTurn(messages) {
|
|
84
|
+
const last = messages[messages.length - 1];
|
|
85
|
+
return !!last && last.role === 'user' && !last.content.some((block) => 'toolResult' in block);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Folds `text` into the most recent `user` message as a {@link TextBlock}, returning a NEW array. Other
|
|
89
|
+
* messages are returned as-is.
|
|
90
|
+
*
|
|
91
|
+
* Folding into the existing user message (rather than inserting a standalone message) keeps role
|
|
92
|
+
* alternation valid in both chat and the autonomous tool loop. The block is placed to keep the message
|
|
93
|
+
* valid for the model:
|
|
94
|
+
* - A plain user ask: the text is **prepended**, leaving the user's own ask in the recency slot — the
|
|
95
|
+
* last thing the model reads.
|
|
96
|
+
* - A tool-result turn (the message carries a `ToolResultBlock`): the text is **appended**,
|
|
97
|
+
* because providers require the tool result to be the first content block in the turn that answers a
|
|
98
|
+
* tool use.
|
|
99
|
+
*
|
|
100
|
+
* {@link Message} fields are readonly, so the target is rebuilt as a new {@link Message}. When there is
|
|
101
|
+
* no `user` message, the input array is returned unchanged.
|
|
102
|
+
*
|
|
103
|
+
* @param messages - The conversation to fold into
|
|
104
|
+
* @param text - The text to fold into the most recent user message
|
|
105
|
+
* @returns A new array with the folded message, or the input array when there is no user message
|
|
106
|
+
* @internal Delivery primitive. Reach injection through `ContextInjector` or `MemoryManager`.
|
|
107
|
+
*/
|
|
108
|
+
export function foldIntoLastUserMessage(messages, text) {
|
|
109
|
+
let targetIndex = -1;
|
|
110
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
111
|
+
if (messages[i].role === 'user') {
|
|
112
|
+
targetIndex = i;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (targetIndex < 0) {
|
|
117
|
+
return messages;
|
|
118
|
+
}
|
|
119
|
+
const target = messages[targetIndex];
|
|
120
|
+
const injected = new TextBlock(text);
|
|
121
|
+
// A tool result must stay the first block in the turn that answers a tool use, so append rather than
|
|
122
|
+
// prepend when the target carries one.
|
|
123
|
+
const hasToolResult = target.content.some((block) => block.type === 'toolResultBlock');
|
|
124
|
+
const content = hasToolResult ? [...target.content, injected] : [injected, ...target.content];
|
|
125
|
+
const folded = new Message({
|
|
126
|
+
role: target.role,
|
|
127
|
+
content,
|
|
128
|
+
...(target.metadata !== undefined && { metadata: target.metadata }),
|
|
129
|
+
});
|
|
130
|
+
const result = [...messages];
|
|
131
|
+
result[targetIndex] = folded;
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=message-injection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-injection.js","sourceRoot":"","sources":["../../../src/injection/message-injection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { MessageData } from '../types/messages.js';
|
|
2
|
+
import type { LocalAgent } from '../types/agent.js';
|
|
3
|
+
import type { StateStore } from '../state-store.js';
|
|
4
|
+
/**
|
|
5
|
+
* Determines when injection runs before a model call.
|
|
6
|
+
*
|
|
7
|
+
* - `'userTurn'`: only when the latest message is a fresh user ask (a `user` message with no tool
|
|
8
|
+
* result) — the common case for chat agents, where it keeps the user's ask the final message the
|
|
9
|
+
* model sees.
|
|
10
|
+
* - `'everyTurn'`: before every model call, including mid-task tool-result turns — for autonomous
|
|
11
|
+
* agents that should consult injected context at each step.
|
|
12
|
+
*
|
|
13
|
+
* For finer control, pass a predicate as {@link InjectionConfig.trigger} instead.
|
|
14
|
+
*/
|
|
15
|
+
export type InjectionTrigger = 'userTurn' | 'everyTurn';
|
|
16
|
+
/**
|
|
17
|
+
* The context an injection consumer receives on each model call, passed to `renderContent` and to a
|
|
18
|
+
* predicate {@link InjectionConfig.trigger}.
|
|
19
|
+
*/
|
|
20
|
+
export interface InjectionContext {
|
|
21
|
+
/** The current conversation, as data. */
|
|
22
|
+
messages: MessageData[];
|
|
23
|
+
/** Durable app state shared across calls, hooks, and tools — read what a tool stashed last turn. */
|
|
24
|
+
appState: StateStore;
|
|
25
|
+
/** The agent the injection is attached to (escape hatch for advanced consumers). */
|
|
26
|
+
agent: LocalAgent;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration common to every injection consumer: when to inject. What text to inject is a consumer
|
|
30
|
+
* concern, added by the interfaces that extend this one (e.g. {@link MemoryInjectionConfig}).
|
|
31
|
+
*/
|
|
32
|
+
export interface InjectionConfig {
|
|
33
|
+
/**
|
|
34
|
+
* When injection runs. An {@link InjectionTrigger} name selects a built-in policy; a predicate is
|
|
35
|
+
* the escape hatch — it receives the {@link InjectionContext} and returns whether to inject this
|
|
36
|
+
* call. A predicate that throws fails open (injection is skipped, the model call proceeds).
|
|
37
|
+
*
|
|
38
|
+
* @defaultValue 'userTurn'
|
|
39
|
+
*/
|
|
40
|
+
trigger?: InjectionTrigger | ((context: InjectionContext) => boolean);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Options for {@link createInjectionMiddleware}.
|
|
44
|
+
*
|
|
45
|
+
* The engine is text-in: it knows nothing about queries, search, or rendering. A consumer supplies a
|
|
46
|
+
* single {@link InjectionMiddlewareOptions.renderContent} callback that returns the text to fold into
|
|
47
|
+
* the conversation, and (optionally) a trigger that gates when to do so.
|
|
48
|
+
*
|
|
49
|
+
* @internal Engine options. Consumers configure injection via `ContextInjectorConfig` or
|
|
50
|
+
* `MemoryInjectionConfig`, not this type.
|
|
51
|
+
*/
|
|
52
|
+
export interface InjectionMiddlewareOptions {
|
|
53
|
+
/**
|
|
54
|
+
* When to inject. See {@link InjectionConfig.trigger}. Defaults to `'userTurn'`.
|
|
55
|
+
*/
|
|
56
|
+
trigger?: InjectionTrigger | ((context: InjectionContext) => boolean);
|
|
57
|
+
/**
|
|
58
|
+
* Returns the text to fold into the latest user message, or `undefined`/`''` to skip this call. A
|
|
59
|
+
* callback that throws fails open (injection is skipped, the model call proceeds).
|
|
60
|
+
*/
|
|
61
|
+
renderContent: (context: InjectionContext) => Promise<string | undefined>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/injection/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,WAAW,CAAA;AAEvD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,oGAAoG;IACpG,QAAQ,EAAE,UAAU,CAAA;IACpB,oFAAoF;IACpF,KAAK,EAAE,UAAU,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAA;CACtE;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,0BAA0B;IACzC;;OAEG;IACH,OAAO,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAA;IACrE;;;OAGG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CAC1E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/injection/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal XML escaping for folding untrusted text into an XML-shaped block.
|
|
3
|
+
*
|
|
4
|
+
* Memory entries and other injected content are frequently user-derived, so interpolating them raw
|
|
5
|
+
* into `<entry>…</entry>` both breaks the block structurally (a stray `</entry>` or `"`) and opens a
|
|
6
|
+
* stored-prompt-injection surface. These helpers are deliberately tiny — enough to keep a `<memory>`
|
|
7
|
+
* block well-formed, not a general-purpose serializer.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Escapes text content for placement between XML tags: `&` (first, so later replacements are not
|
|
11
|
+
* double-escaped), then `<` and `>`.
|
|
12
|
+
*
|
|
13
|
+
* @param value - The raw text to escape
|
|
14
|
+
* @returns The escaped text, safe to place in element content
|
|
15
|
+
* @internal Used by the memory default formatter
|
|
16
|
+
*/
|
|
17
|
+
export declare function escapeXmlText(value: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Escapes a value for placement inside a double-quoted XML attribute: the {@link escapeXmlText} rules
|
|
20
|
+
* plus `"` and `'`.
|
|
21
|
+
*
|
|
22
|
+
* @param value - The raw attribute value to escape
|
|
23
|
+
* @returns The escaped value, safe to place inside a quoted attribute
|
|
24
|
+
* @internal Default-format helper; not part of the public surface.
|
|
25
|
+
*/
|
|
26
|
+
export declare function escapeXmlAttr(value: string): string;
|
|
27
|
+
//# sourceMappingURL=xml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../../src/injection/xml.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal XML escaping for folding untrusted text into an XML-shaped block.
|
|
3
|
+
*
|
|
4
|
+
* Memory entries and other injected content are frequently user-derived, so interpolating them raw
|
|
5
|
+
* into `<entry>…</entry>` both breaks the block structurally (a stray `</entry>` or `"`) and opens a
|
|
6
|
+
* stored-prompt-injection surface. These helpers are deliberately tiny — enough to keep a `<memory>`
|
|
7
|
+
* block well-formed, not a general-purpose serializer.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Escapes text content for placement between XML tags: `&` (first, so later replacements are not
|
|
11
|
+
* double-escaped), then `<` and `>`.
|
|
12
|
+
*
|
|
13
|
+
* @param value - The raw text to escape
|
|
14
|
+
* @returns The escaped text, safe to place in element content
|
|
15
|
+
* @internal Used by the memory default formatter
|
|
16
|
+
*/
|
|
17
|
+
export function escapeXmlText(value) {
|
|
18
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Escapes a value for placement inside a double-quoted XML attribute: the {@link escapeXmlText} rules
|
|
22
|
+
* plus `"` and `'`.
|
|
23
|
+
*
|
|
24
|
+
* @param value - The raw attribute value to escape
|
|
25
|
+
* @returns The escaped value, safe to place inside a quoted attribute
|
|
26
|
+
* @internal Default-format helper; not part of the public surface.
|
|
27
|
+
*/
|
|
28
|
+
export function escapeXmlAttr(value) {
|
|
29
|
+
return escapeXmlText(value).replace(/"/g, '"').replace(/'/g, ''');
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=xml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.js","sourceRoot":"","sources":["../../../src/injection/xml.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AACjF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAC5E,CAAC"}
|