@vellumai/assistant 0.8.8-dev.202606072033.0e97ff6 → 0.8.8-dev.202606072131.4817a81
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/openapi.yaml +8 -0
- package/package.json +1 -1
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +34 -7
- package/src/__tests__/agent-loop-exit-reason.test.ts +95 -39
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +10 -2
- package/src/__tests__/agent-loop-output-hooks.test.ts +44 -14
- package/src/__tests__/agent-loop-override-profile.test.ts +8 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +19 -5
- package/src/__tests__/agent-loop-thinking.test.ts +8 -0
- package/src/__tests__/agent-loop.test.ts +203 -50
- package/src/__tests__/conversation-runtime-assembly.test.ts +4 -2
- package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +14 -3
- package/src/__tests__/subagent-detail.test.ts +25 -7
- package/src/agent/loop.ts +26 -30
- package/src/api/responses/conversation-message.ts +7 -0
- package/src/export/__tests__/transcript-formatter.test.ts +5 -0
- package/src/memory/conversation-crud.ts +2 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +4 -4
- package/src/runtime/__tests__/agent-wake.test.ts +1 -1
- package/src/runtime/agent-wake.ts +12 -6
- package/src/runtime/routes/conversation-routes.ts +8 -0
package/openapi.yaml
CHANGED
|
@@ -16027,6 +16027,8 @@ paths:
|
|
|
16027
16027
|
type: array
|
|
16028
16028
|
items:
|
|
16029
16029
|
type: string
|
|
16030
|
+
clientMessageId:
|
|
16031
|
+
type: string
|
|
16030
16032
|
role:
|
|
16031
16033
|
type: string
|
|
16032
16034
|
enum:
|
|
@@ -16995,6 +16997,12 @@ paths:
|
|
|
16995
16997
|
type: string
|
|
16996
16998
|
clientTimezone:
|
|
16997
16999
|
type: string
|
|
17000
|
+
clientMessageId:
|
|
17001
|
+
type: string
|
|
17002
|
+
description:
|
|
17003
|
+
Client-generated idempotency nonce. Persisted on the row and echoed back on the message_echo event and the
|
|
17004
|
+
messages snapshot so the client can correlate its optimistic row by identity. Duplicate sends for
|
|
17005
|
+
the same (conversation, clientMessageId) are deduplicated server-side.
|
|
16998
17006
|
inferenceProfile:
|
|
16999
17007
|
anyOf:
|
|
17000
17008
|
- type: string
|
package/package.json
CHANGED
|
@@ -101,10 +101,14 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
101
101
|
|
|
102
102
|
const { provider, lastConfig } = makePipeline("anthropic");
|
|
103
103
|
const loop = new AgentLoop(provider, "system", {
|
|
104
|
+
conversationId: "test-conversation",
|
|
104
105
|
config: { maxTokens: 64000 },
|
|
105
106
|
});
|
|
106
107
|
|
|
107
|
-
await loop.run([userMessage], () => {}, {
|
|
108
|
+
await loop.run([userMessage], () => {}, {
|
|
109
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
110
|
+
callSite: "mainAgent",
|
|
111
|
+
});
|
|
108
112
|
|
|
109
113
|
expect(lastConfig()!.max_tokens).toBe(4096);
|
|
110
114
|
});
|
|
@@ -121,13 +125,17 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
121
125
|
|
|
122
126
|
const { provider, lastConfig } = makePipeline("anthropic");
|
|
123
127
|
const loop = new AgentLoop(provider, "system", {
|
|
128
|
+
conversationId: "test-conversation",
|
|
124
129
|
config: {
|
|
125
130
|
maxTokens: 64000,
|
|
126
131
|
effort: "high",
|
|
127
132
|
},
|
|
128
133
|
});
|
|
129
134
|
|
|
130
|
-
await loop.run([userMessage], () => {}, {
|
|
135
|
+
await loop.run([userMessage], () => {}, {
|
|
136
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
137
|
+
callSite: "mainAgent",
|
|
138
|
+
});
|
|
131
139
|
|
|
132
140
|
expect(lastConfig()!.effort).toBe("low");
|
|
133
141
|
});
|
|
@@ -144,6 +152,7 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
144
152
|
|
|
145
153
|
const { provider, lastConfig } = makePipeline("anthropic");
|
|
146
154
|
const loop = new AgentLoop(provider, "system", {
|
|
155
|
+
conversationId: "test-conversation",
|
|
147
156
|
config: {
|
|
148
157
|
maxTokens: 64000,
|
|
149
158
|
effort: "high",
|
|
@@ -153,7 +162,10 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
153
162
|
},
|
|
154
163
|
});
|
|
155
164
|
|
|
156
|
-
await loop.run([userMessage], () => {}, {
|
|
165
|
+
await loop.run([userMessage], () => {}, {
|
|
166
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
167
|
+
callSite: "mainAgent",
|
|
168
|
+
});
|
|
157
169
|
|
|
158
170
|
expect(lastConfig()!.speed).toBe("fast");
|
|
159
171
|
});
|
|
@@ -175,6 +187,7 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
175
187
|
|
|
176
188
|
const { provider, lastConfig } = makePipeline("anthropic");
|
|
177
189
|
const loop = new AgentLoop(provider, "system", {
|
|
190
|
+
conversationId: "test-conversation",
|
|
178
191
|
config: {
|
|
179
192
|
maxTokens: 64000,
|
|
180
193
|
// Conversation default also has thinking on — without the fix, this
|
|
@@ -184,7 +197,10 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
184
197
|
},
|
|
185
198
|
});
|
|
186
199
|
|
|
187
|
-
await loop.run([userMessage], () => {}, {
|
|
200
|
+
await loop.run([userMessage], () => {}, {
|
|
201
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
202
|
+
callSite: "mainAgent",
|
|
203
|
+
});
|
|
188
204
|
|
|
189
205
|
// Call-site override resolves `thinking.enabled: false`, so the
|
|
190
206
|
// RetryProvider normalizer must send Anthropic's explicit disabled shape.
|
|
@@ -203,10 +219,14 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
203
219
|
|
|
204
220
|
const { provider, lastConfig } = makePipeline("anthropic");
|
|
205
221
|
const loop = new AgentLoop(provider, "system", {
|
|
222
|
+
conversationId: "test-conversation",
|
|
206
223
|
config: { maxTokens: 64000 },
|
|
207
224
|
});
|
|
208
225
|
|
|
209
|
-
await loop.run([userMessage], () => {}, {
|
|
226
|
+
await loop.run([userMessage], () => {}, {
|
|
227
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
228
|
+
callSite: "mainAgent",
|
|
229
|
+
});
|
|
210
230
|
|
|
211
231
|
// Must be wire-format `{ type: "adaptive" }` so the Anthropic SDK's
|
|
212
232
|
// `ThinkingConfigParam` accepts it. The schema-shape `{ enabled,
|
|
@@ -228,6 +248,7 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
228
248
|
|
|
229
249
|
const { provider, lastConfig } = makePipeline("anthropic");
|
|
230
250
|
const loop = new AgentLoop(provider, "system", {
|
|
251
|
+
conversationId: "test-conversation",
|
|
231
252
|
config: {
|
|
232
253
|
maxTokens: 64000,
|
|
233
254
|
effort: "high",
|
|
@@ -236,7 +257,9 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
236
257
|
},
|
|
237
258
|
});
|
|
238
259
|
|
|
239
|
-
await loop.run([userMessage], () => {}
|
|
260
|
+
await loop.run([userMessage], () => {}, {
|
|
261
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
262
|
+
});
|
|
240
263
|
|
|
241
264
|
const config = lastConfig()!;
|
|
242
265
|
expect(config.max_tokens).toBe(64000);
|
|
@@ -263,11 +286,15 @@ describe("AgentLoop — call-site precedence", () => {
|
|
|
263
286
|
});
|
|
264
287
|
|
|
265
288
|
const loop = new AgentLoop(provider, "system", {
|
|
289
|
+
conversationId: "test-conversation",
|
|
266
290
|
config: { maxTokens: 64000 },
|
|
267
291
|
resolveSystemPrompt: resolveSystemPrompt,
|
|
268
292
|
});
|
|
269
293
|
|
|
270
|
-
await loop.run([userMessage], () => {}, {
|
|
294
|
+
await loop.run([userMessage], () => {}, {
|
|
295
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
296
|
+
callSite: "mainAgent",
|
|
297
|
+
});
|
|
271
298
|
|
|
272
299
|
// Per-turn explicit value beats both the call-site (4096) and the
|
|
273
300
|
// default (64000).
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
} from "../agent/loop.js";
|
|
24
24
|
import { AgentLoop, isMaxTokensStopReason } from "../agent/loop.js";
|
|
25
25
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
26
|
-
import type {
|
|
26
|
+
import type { PostCompactContext } from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
|
|
27
27
|
import type {
|
|
28
28
|
Message,
|
|
29
29
|
Provider,
|
|
@@ -35,18 +35,16 @@ import type {
|
|
|
35
35
|
// The agent loop invokes the default post-compaction re-injection hook directly
|
|
36
36
|
// when it compacts in place. Stub it so these unit tests can drive the
|
|
37
37
|
// re-injection result without the daemon-level injector chain. Tests assign
|
|
38
|
-
// `
|
|
38
|
+
// `postCompactImpl` to observe the call or force a failure; when unset
|
|
39
39
|
// the hook is a no-op that returns the history it was handed.
|
|
40
|
-
let
|
|
41
|
-
| ((input:
|
|
40
|
+
let postCompactImpl:
|
|
41
|
+
| ((input: PostCompactContext) => Promise<Message[]>)
|
|
42
42
|
| null = null;
|
|
43
43
|
mock.module(
|
|
44
44
|
"../plugins/defaults/memory-retrieval/hooks/post-compact.js",
|
|
45
45
|
() => ({
|
|
46
|
-
default: async (input:
|
|
47
|
-
messages:
|
|
48
|
-
? await postCompactReinjectImpl(input)
|
|
49
|
-
: input.history,
|
|
46
|
+
default: async (input: PostCompactContext) => ({
|
|
47
|
+
messages: postCompactImpl ? await postCompactImpl(input) : input.history,
|
|
50
48
|
}),
|
|
51
49
|
}),
|
|
52
50
|
);
|
|
@@ -165,12 +163,18 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
165
163
|
|
|
166
164
|
test("emits exit event exactly once with 'no_tool_calls' on plain text response", async () => {
|
|
167
165
|
const { provider } = createMockProvider([textResponse("Hi there!")]);
|
|
168
|
-
const loop = new AgentLoop(provider, "system prompt"
|
|
166
|
+
const loop = new AgentLoop(provider, "system prompt", {
|
|
167
|
+
conversationId: "test-conversation",
|
|
168
|
+
});
|
|
169
169
|
|
|
170
170
|
const events: AgentEvent[] = [];
|
|
171
|
-
await loop.run(
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
await loop.run(
|
|
172
|
+
[userMessage],
|
|
173
|
+
(e) => {
|
|
174
|
+
events.push(e);
|
|
175
|
+
},
|
|
176
|
+
{ trust: { sourceChannel: "vellum", trustClass: "unknown" } },
|
|
177
|
+
);
|
|
174
178
|
|
|
175
179
|
expect(countExitEvents(events)).toBe(1);
|
|
176
180
|
const exit = lastExitEvent(events);
|
|
@@ -179,12 +183,18 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
179
183
|
|
|
180
184
|
test("agent_loop_exit is the last event emitted", async () => {
|
|
181
185
|
const { provider } = createMockProvider([textResponse("Hi there!")]);
|
|
182
|
-
const loop = new AgentLoop(provider, "system prompt"
|
|
186
|
+
const loop = new AgentLoop(provider, "system prompt", {
|
|
187
|
+
conversationId: "test-conversation",
|
|
188
|
+
});
|
|
183
189
|
|
|
184
190
|
const events: AgentEvent[] = [];
|
|
185
|
-
await loop.run(
|
|
186
|
-
|
|
187
|
-
|
|
191
|
+
await loop.run(
|
|
192
|
+
[userMessage],
|
|
193
|
+
(e) => {
|
|
194
|
+
events.push(e);
|
|
195
|
+
},
|
|
196
|
+
{ trust: { sourceChannel: "vellum", trustClass: "unknown" } },
|
|
197
|
+
);
|
|
188
198
|
|
|
189
199
|
expect(events.length).toBeGreaterThan(0);
|
|
190
200
|
expect(events[events.length - 1].type).toBe("agent_loop_exit");
|
|
@@ -194,12 +204,18 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
194
204
|
const { provider } = createMockProvider([
|
|
195
205
|
maxTokensResponse("Partial answer"),
|
|
196
206
|
]);
|
|
197
|
-
const loop = new AgentLoop(provider, "system prompt"
|
|
207
|
+
const loop = new AgentLoop(provider, "system prompt", {
|
|
208
|
+
conversationId: "test-conversation",
|
|
209
|
+
});
|
|
198
210
|
|
|
199
211
|
const events: AgentEvent[] = [];
|
|
200
|
-
await loop.run(
|
|
201
|
-
|
|
202
|
-
|
|
212
|
+
await loop.run(
|
|
213
|
+
[userMessage],
|
|
214
|
+
(e) => {
|
|
215
|
+
events.push(e);
|
|
216
|
+
},
|
|
217
|
+
{ trust: { sourceChannel: "vellum", trustClass: "unknown" } },
|
|
218
|
+
);
|
|
203
219
|
|
|
204
220
|
expect(events.map((e) => e.type)).toEqual([
|
|
205
221
|
"llm_call_started",
|
|
@@ -230,13 +246,18 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
230
246
|
},
|
|
231
247
|
]);
|
|
232
248
|
const loop = new AgentLoop(provider, "system prompt", {
|
|
249
|
+
conversationId: "test-conversation",
|
|
233
250
|
tools: dummyTools,
|
|
234
251
|
});
|
|
235
252
|
|
|
236
253
|
const events: AgentEvent[] = [];
|
|
237
|
-
const { history: result } = await loop.run(
|
|
238
|
-
|
|
239
|
-
|
|
254
|
+
const { history: result } = await loop.run(
|
|
255
|
+
[userMessage],
|
|
256
|
+
(e) => {
|
|
257
|
+
events.push(e);
|
|
258
|
+
},
|
|
259
|
+
{ trust: { sourceChannel: "vellum", trustClass: "unknown" } },
|
|
260
|
+
);
|
|
240
261
|
|
|
241
262
|
expect(events.some((e) => e.type === "tool_use")).toBe(false);
|
|
242
263
|
expect(lastExitEvent(events)?.reason).toBe("max_tokens_reached");
|
|
@@ -247,7 +268,9 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
247
268
|
|
|
248
269
|
test("emits 'aborted_pre_call' when signal is already aborted at run start", async () => {
|
|
249
270
|
const { provider } = createMockProvider([textResponse("never sent")]);
|
|
250
|
-
const loop = new AgentLoop(provider, "system prompt"
|
|
271
|
+
const loop = new AgentLoop(provider, "system prompt", {
|
|
272
|
+
conversationId: "test-conversation",
|
|
273
|
+
});
|
|
251
274
|
|
|
252
275
|
const controller = new AbortController();
|
|
253
276
|
controller.abort();
|
|
@@ -258,7 +281,10 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
258
281
|
(e) => {
|
|
259
282
|
events.push(e);
|
|
260
283
|
},
|
|
261
|
-
{
|
|
284
|
+
{
|
|
285
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
286
|
+
signal: controller.signal,
|
|
287
|
+
},
|
|
262
288
|
);
|
|
263
289
|
|
|
264
290
|
expect(countExitEvents(events)).toBe(1);
|
|
@@ -275,14 +301,19 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
275
301
|
yieldToUser: true,
|
|
276
302
|
});
|
|
277
303
|
const loop = new AgentLoop(provider, "system", {
|
|
304
|
+
conversationId: "test-conversation",
|
|
278
305
|
tools: dummyTools,
|
|
279
306
|
toolExecutor: toolExecutor,
|
|
280
307
|
});
|
|
281
308
|
|
|
282
309
|
const events: AgentEvent[] = [];
|
|
283
|
-
await loop.run(
|
|
284
|
-
|
|
285
|
-
|
|
310
|
+
await loop.run(
|
|
311
|
+
[userMessage],
|
|
312
|
+
(e) => {
|
|
313
|
+
events.push(e);
|
|
314
|
+
},
|
|
315
|
+
{ trust: { sourceChannel: "vellum", trustClass: "unknown" } },
|
|
316
|
+
);
|
|
286
317
|
|
|
287
318
|
expect(countExitEvents(events)).toBe(1);
|
|
288
319
|
expect(lastExitEvent(events)?.reason).toBe("yield_to_user");
|
|
@@ -295,6 +326,7 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
295
326
|
]);
|
|
296
327
|
const toolExecutor = async () => ({ content: "ok", isError: false });
|
|
297
328
|
const loop = new AgentLoop(provider, "system", {
|
|
329
|
+
conversationId: "test-conversation",
|
|
298
330
|
tools: dummyTools,
|
|
299
331
|
toolExecutor: toolExecutor,
|
|
300
332
|
});
|
|
@@ -308,7 +340,10 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
308
340
|
(e) => {
|
|
309
341
|
events.push(e);
|
|
310
342
|
},
|
|
311
|
-
{
|
|
343
|
+
{
|
|
344
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
345
|
+
onCheckpoint,
|
|
346
|
+
},
|
|
312
347
|
);
|
|
313
348
|
|
|
314
349
|
expect(countExitEvents(events)).toBe(0);
|
|
@@ -323,6 +358,7 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
323
358
|
]);
|
|
324
359
|
const toolExecutor = async () => ({ content: "ok", isError: false });
|
|
325
360
|
const loop = new AgentLoop(provider, "system", {
|
|
361
|
+
conversationId: "test-conversation",
|
|
326
362
|
tools: dummyTools,
|
|
327
363
|
toolExecutor: toolExecutor,
|
|
328
364
|
});
|
|
@@ -331,6 +367,7 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
331
367
|
// of the running history exceeds the mid-loop threshold.
|
|
332
368
|
// WHEN the loop checkpoints after the tool results land
|
|
333
369
|
const result = await loop.run([userMessage], () => {}, {
|
|
370
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
334
371
|
resolveContextWindow: () => ({
|
|
335
372
|
maxInputTokens: 10,
|
|
336
373
|
overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
|
|
@@ -350,12 +387,14 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
350
387
|
]);
|
|
351
388
|
const toolExecutor = async () => ({ content: "ok", isError: false });
|
|
352
389
|
const loop = new AgentLoop(provider, "system", {
|
|
390
|
+
conversationId: "test-conversation",
|
|
353
391
|
tools: dummyTools,
|
|
354
392
|
toolExecutor: toolExecutor,
|
|
355
393
|
});
|
|
356
394
|
|
|
357
395
|
// WHEN the loop runs to completion
|
|
358
396
|
const result = await loop.run([userMessage], () => {}, {
|
|
397
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
359
398
|
resolveContextWindow: () => ({
|
|
360
399
|
maxInputTokens: 10,
|
|
361
400
|
overflowRecovery: { enabled: false, safetyMarginRatio: 0 },
|
|
@@ -375,13 +414,14 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
375
414
|
]);
|
|
376
415
|
const toolExecutor = async () => ({ content: "ok", isError: false });
|
|
377
416
|
const loop = new AgentLoop(provider, "system", {
|
|
417
|
+
conversationId: "test-conversation",
|
|
378
418
|
tools: dummyTools,
|
|
379
419
|
toolExecutor: toolExecutor,
|
|
380
420
|
});
|
|
381
421
|
|
|
382
422
|
let reinjected = false;
|
|
383
423
|
const events: AgentEvent[] = [];
|
|
384
|
-
|
|
424
|
+
postCompactImpl = async () => {
|
|
385
425
|
reinjected = true;
|
|
386
426
|
return [userMessage];
|
|
387
427
|
};
|
|
@@ -422,11 +462,12 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
422
462
|
]);
|
|
423
463
|
const toolExecutor = async () => ({ content: "ok", isError: false });
|
|
424
464
|
const loop = new AgentLoop(provider, "system", {
|
|
465
|
+
conversationId: "test-conversation",
|
|
425
466
|
tools: dummyTools,
|
|
426
467
|
toolExecutor: toolExecutor,
|
|
427
468
|
});
|
|
428
469
|
|
|
429
|
-
|
|
470
|
+
postCompactImpl = async () => {
|
|
430
471
|
throw new Error(
|
|
431
472
|
"post-compaction re-injection must not run when exhausted",
|
|
432
473
|
);
|
|
@@ -456,12 +497,18 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
456
497
|
throw new Error("provider exploded");
|
|
457
498
|
},
|
|
458
499
|
};
|
|
459
|
-
const loop = new AgentLoop(provider, "system prompt"
|
|
500
|
+
const loop = new AgentLoop(provider, "system prompt", {
|
|
501
|
+
conversationId: "test-conversation",
|
|
502
|
+
});
|
|
460
503
|
|
|
461
504
|
const events: AgentEvent[] = [];
|
|
462
|
-
await loop.run(
|
|
463
|
-
|
|
464
|
-
|
|
505
|
+
await loop.run(
|
|
506
|
+
[userMessage],
|
|
507
|
+
(e) => {
|
|
508
|
+
events.push(e);
|
|
509
|
+
},
|
|
510
|
+
{ trust: { sourceChannel: "vellum", trustClass: "unknown" } },
|
|
511
|
+
);
|
|
465
512
|
|
|
466
513
|
expect(countExitEvents(events)).toBe(1);
|
|
467
514
|
expect(lastExitEvent(events)?.reason).toBe("error");
|
|
@@ -480,14 +527,19 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
480
527
|
yieldToUser: true,
|
|
481
528
|
});
|
|
482
529
|
const loop = new AgentLoop(provider, "system", {
|
|
530
|
+
conversationId: "test-conversation",
|
|
483
531
|
tools: dummyTools,
|
|
484
532
|
toolExecutor: toolExecutor,
|
|
485
533
|
});
|
|
486
534
|
|
|
487
535
|
const events: AgentEvent[] = [];
|
|
488
|
-
await loop.run(
|
|
489
|
-
|
|
490
|
-
|
|
536
|
+
await loop.run(
|
|
537
|
+
[userMessage],
|
|
538
|
+
(e) => {
|
|
539
|
+
events.push(e);
|
|
540
|
+
},
|
|
541
|
+
{ trust: { sourceChannel: "vellum", trustClass: "unknown" } },
|
|
542
|
+
);
|
|
491
543
|
|
|
492
544
|
expect(countExitEvents(events)).toBe(1);
|
|
493
545
|
});
|
|
@@ -504,6 +556,7 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
504
556
|
return { content: "ok", isError: false };
|
|
505
557
|
};
|
|
506
558
|
const loop = new AgentLoop(provider, "system", {
|
|
559
|
+
conversationId: "test-conversation",
|
|
507
560
|
tools: dummyTools,
|
|
508
561
|
toolExecutor: toolExecutor,
|
|
509
562
|
});
|
|
@@ -514,7 +567,10 @@ describe("AgentLoop exit-reason instrumentation", () => {
|
|
|
514
567
|
(e) => {
|
|
515
568
|
events.push(e);
|
|
516
569
|
},
|
|
517
|
-
{
|
|
570
|
+
{
|
|
571
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
572
|
+
signal: controller.signal,
|
|
573
|
+
},
|
|
518
574
|
);
|
|
519
575
|
|
|
520
576
|
expect(countExitEvents(events)).toBe(1);
|
|
@@ -105,13 +105,17 @@ describe("AgentLoop.run — mutableLatestUserMessage from memory-v3-live", () =>
|
|
|
105
105
|
},
|
|
106
106
|
];
|
|
107
107
|
const loop = new AgentLoop(provider, "system", {
|
|
108
|
+
conversationId: "test-conversation",
|
|
108
109
|
config: { maxTokens: 1024 },
|
|
109
110
|
tools: dummyTools,
|
|
110
111
|
toolExecutor: async () => ({ content: "ok", isError: false }),
|
|
111
112
|
});
|
|
112
113
|
|
|
113
114
|
// WHEN the loop runs
|
|
114
|
-
await loop.run([userMessage], () => {}, {
|
|
115
|
+
await loop.run([userMessage], () => {}, {
|
|
116
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
117
|
+
callSite: "mainAgent",
|
|
118
|
+
});
|
|
115
119
|
|
|
116
120
|
// THEN every send (initial + tool round-trip) carries the cache-anchor signal
|
|
117
121
|
expect(configs()).toHaveLength(2);
|
|
@@ -127,11 +131,15 @@ describe("AgentLoop.run — mutableLatestUserMessage from memory-v3-live", () =>
|
|
|
127
131
|
// AND a provider that records the config of each LLM call
|
|
128
132
|
const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
|
|
129
133
|
const loop = new AgentLoop(provider, "system", {
|
|
134
|
+
conversationId: "test-conversation",
|
|
130
135
|
config: { maxTokens: 1024 },
|
|
131
136
|
});
|
|
132
137
|
|
|
133
138
|
// WHEN the loop runs
|
|
134
|
-
await loop.run([userMessage], () => {}, {
|
|
139
|
+
await loop.run([userMessage], () => {}, {
|
|
140
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
141
|
+
callSite: "mainAgent",
|
|
142
|
+
});
|
|
135
143
|
|
|
136
144
|
// THEN the field is omitted entirely, not carried as false/undefined
|
|
137
145
|
expect(configs()).toHaveLength(1);
|
|
@@ -94,9 +94,13 @@ describe("agent loop output hooks", () => {
|
|
|
94
94
|
},
|
|
95
95
|
});
|
|
96
96
|
const { provider } = createMockProvider([textResponse("my secret value")]);
|
|
97
|
-
const loop = new AgentLoop(provider, "system"
|
|
97
|
+
const loop = new AgentLoop(provider, "system", {
|
|
98
|
+
conversationId: "test-conversation",
|
|
99
|
+
});
|
|
98
100
|
const events: AgentEvent[] = [];
|
|
99
|
-
const { history } = await loop.run([userMessage], collect(events)
|
|
101
|
+
const { history } = await loop.run([userMessage], collect(events), {
|
|
102
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
103
|
+
});
|
|
100
104
|
expect(textOf(lastAssistant(history).content)).toBe("my [redacted] value");
|
|
101
105
|
});
|
|
102
106
|
|
|
@@ -110,9 +114,13 @@ describe("agent loop output hooks", () => {
|
|
|
110
114
|
},
|
|
111
115
|
});
|
|
112
116
|
const { provider } = createMockProvider([textResponse("my secret value")]);
|
|
113
|
-
const loop = new AgentLoop(provider, "system"
|
|
117
|
+
const loop = new AgentLoop(provider, "system", {
|
|
118
|
+
conversationId: "test-conversation",
|
|
119
|
+
});
|
|
114
120
|
const events: AgentEvent[] = [];
|
|
115
|
-
const { history } = await loop.run([userMessage], collect(events)
|
|
121
|
+
const { history } = await loop.run([userMessage], collect(events), {
|
|
122
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
123
|
+
});
|
|
116
124
|
// Her real words never streamed; only the transformed text did, once.
|
|
117
125
|
expect(streamedText(events)).not.toContain("secret");
|
|
118
126
|
expect(streamedText(events)).toBe("[filtered]");
|
|
@@ -126,9 +134,13 @@ describe("agent loop output hooks", () => {
|
|
|
126
134
|
},
|
|
127
135
|
});
|
|
128
136
|
const { provider } = createMockProvider([textResponse("live text")]);
|
|
129
|
-
const loop = new AgentLoop(provider, "system"
|
|
137
|
+
const loop = new AgentLoop(provider, "system", {
|
|
138
|
+
conversationId: "test-conversation",
|
|
139
|
+
});
|
|
130
140
|
const events: AgentEvent[] = [];
|
|
131
|
-
const { history } = await loop.run([userMessage], collect(events)
|
|
141
|
+
const { history } = await loop.run([userMessage], collect(events), {
|
|
142
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
143
|
+
});
|
|
132
144
|
expect(streamedText(events)).toBe("live text"); // streamed live, untransformed
|
|
133
145
|
expect(textOf(lastAssistant(history).content)).toBe("[stored]"); // storage transformed
|
|
134
146
|
});
|
|
@@ -155,6 +167,7 @@ describe("agent loop output hooks", () => {
|
|
|
155
167
|
textResponse("done"),
|
|
156
168
|
]);
|
|
157
169
|
const loop = new AgentLoop(provider, "system", {
|
|
170
|
+
conversationId: "test-conversation",
|
|
158
171
|
tools: [
|
|
159
172
|
{
|
|
160
173
|
name: "noop",
|
|
@@ -164,7 +177,9 @@ describe("agent loop output hooks", () => {
|
|
|
164
177
|
],
|
|
165
178
|
toolExecutor: async () => ({ content: "ok", isError: false }),
|
|
166
179
|
});
|
|
167
|
-
const { history } = await loop.run([userMessage], collect([])
|
|
180
|
+
const { history } = await loop.run([userMessage], collect([]), {
|
|
181
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
182
|
+
});
|
|
168
183
|
const toolTurn = history.find(
|
|
169
184
|
(m) =>
|
|
170
185
|
m.role === "assistant" && m.content.some((b) => b.type === "tool_use"),
|
|
@@ -187,8 +202,12 @@ describe("agent loop output hooks", () => {
|
|
|
187
202
|
},
|
|
188
203
|
});
|
|
189
204
|
const { provider, calls } = createMockProvider([textResponse("hi")]);
|
|
190
|
-
const loop = new AgentLoop(provider, "base prompt"
|
|
191
|
-
|
|
205
|
+
const loop = new AgentLoop(provider, "base prompt", {
|
|
206
|
+
conversationId: "test-conversation",
|
|
207
|
+
});
|
|
208
|
+
await loop.run([userMessage], collect([]), {
|
|
209
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
210
|
+
});
|
|
192
211
|
expect(calls[0].systemPrompt).toContain("[EDITED]");
|
|
193
212
|
});
|
|
194
213
|
|
|
@@ -204,8 +223,12 @@ describe("agent loop output hooks", () => {
|
|
|
204
223
|
},
|
|
205
224
|
});
|
|
206
225
|
const { provider } = createMockProvider([textResponse("untouched")]);
|
|
207
|
-
const loop = new AgentLoop(provider, "system"
|
|
208
|
-
|
|
226
|
+
const loop = new AgentLoop(provider, "system", {
|
|
227
|
+
conversationId: "test-conversation",
|
|
228
|
+
});
|
|
229
|
+
const { history } = await loop.run([userMessage], collect([]), {
|
|
230
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
231
|
+
});
|
|
209
232
|
const finalContent = lastAssistant(history).content;
|
|
210
233
|
expect(textOf(finalContent)).toBe("untouched");
|
|
211
234
|
expect(
|
|
@@ -236,9 +259,13 @@ describe("agent loop output hooks", () => {
|
|
|
236
259
|
stopReason: "max_tokens",
|
|
237
260
|
};
|
|
238
261
|
const { provider } = createMockProvider([truncated]);
|
|
239
|
-
const loop = new AgentLoop(provider, "system"
|
|
262
|
+
const loop = new AgentLoop(provider, "system", {
|
|
263
|
+
conversationId: "test-conversation",
|
|
264
|
+
});
|
|
240
265
|
const events: AgentEvent[] = [];
|
|
241
|
-
const { history } = await loop.run([userMessage], collect(events)
|
|
266
|
+
const { history } = await loop.run([userMessage], collect(events), {
|
|
267
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
268
|
+
});
|
|
242
269
|
expect(seen.calls).toBe(1);
|
|
243
270
|
expect(textOf(lastAssistant(history).content)).toBe("PARTIAL ANSWER");
|
|
244
271
|
// Real stream suppressed; transformed final text emitted once.
|
|
@@ -268,6 +295,7 @@ describe("agent loop output hooks", () => {
|
|
|
268
295
|
];
|
|
269
296
|
const { provider } = createMockProvider(toolThenText);
|
|
270
297
|
const loop = new AgentLoop(provider, "system", {
|
|
298
|
+
conversationId: "test-conversation",
|
|
271
299
|
tools: [
|
|
272
300
|
{
|
|
273
301
|
name: "issue",
|
|
@@ -282,7 +310,9 @@ describe("agent loop output hooks", () => {
|
|
|
282
310
|
}),
|
|
283
311
|
});
|
|
284
312
|
const events: AgentEvent[] = [];
|
|
285
|
-
const { history } = await loop.run([userMessage], collect(events)
|
|
313
|
+
const { history } = await loop.run([userMessage], collect(events), {
|
|
314
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
315
|
+
});
|
|
286
316
|
// Persisted message keeps the placeholder — model must never see real
|
|
287
317
|
// values on reload.
|
|
288
318
|
expect(textOf(lastAssistant(history).content)).toContain(placeholder);
|
|
@@ -111,12 +111,14 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
|
|
|
111
111
|
) => ({ content: "ok", isError: false });
|
|
112
112
|
|
|
113
113
|
const loop = new AgentLoop(provider, "system", {
|
|
114
|
+
conversationId: "test-conversation",
|
|
114
115
|
config: { maxTokens: 1024 },
|
|
115
116
|
tools: dummyTools,
|
|
116
117
|
toolExecutor: toolExecutor,
|
|
117
118
|
});
|
|
118
119
|
|
|
119
120
|
await loop.run([userMessage], () => {}, {
|
|
121
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
120
122
|
callSite: "mainAgent",
|
|
121
123
|
overrideProfile: "fast",
|
|
122
124
|
});
|
|
@@ -131,10 +133,13 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
|
|
|
131
133
|
test("omits overrideProfile from providerConfig when unset (default behavior unchanged)", async () => {
|
|
132
134
|
const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
|
|
133
135
|
const loop = new AgentLoop(provider, "system", {
|
|
136
|
+
conversationId: "test-conversation",
|
|
134
137
|
config: { maxTokens: 1024 },
|
|
135
138
|
});
|
|
136
139
|
|
|
137
|
-
await loop.run([userMessage], () => {}
|
|
140
|
+
await loop.run([userMessage], () => {}, {
|
|
141
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
142
|
+
});
|
|
138
143
|
|
|
139
144
|
// Single send, no overrideProfile field at all.
|
|
140
145
|
expect(configs()).toHaveLength(1);
|
|
@@ -149,10 +154,12 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
|
|
|
149
154
|
// provider layer (covered by provider-send-message-override-profile.test.ts).
|
|
150
155
|
const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
|
|
151
156
|
const loop = new AgentLoop(provider, "system", {
|
|
157
|
+
conversationId: "test-conversation",
|
|
152
158
|
config: { maxTokens: 1024 },
|
|
153
159
|
});
|
|
154
160
|
|
|
155
161
|
await loop.run([userMessage], () => {}, {
|
|
162
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
156
163
|
callSite: "mainAgent",
|
|
157
164
|
overrideProfile: "does-not-exist",
|
|
158
165
|
});
|