@trigger.dev/sdk 0.0.0-prerelease-20260310150337 → 0.0.0-prerelease-20260324161542
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/commonjs/v3/ai.d.ts +571 -27
- package/dist/commonjs/v3/ai.js +865 -56
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat.d.ts +1 -1
- package/dist/commonjs/v3/chat.js +2 -2
- package/dist/commonjs/v3/index.d.ts +1 -0
- package/dist/commonjs/v3/index.js +2 -1
- package/dist/commonjs/v3/index.js.map +1 -1
- package/dist/commonjs/v3/prompt.d.ts +38 -0
- package/dist/commonjs/v3/prompt.js +150 -0
- package/dist/commonjs/v3/prompt.js.map +1 -0
- package/dist/commonjs/v3/promptManagement.d.ts +26 -0
- package/dist/commonjs/v3/promptManagement.js +180 -0
- package/dist/commonjs/v3/promptManagement.js.map +1 -0
- package/dist/commonjs/v3/prompts.d.ts +3 -0
- package/dist/commonjs/v3/prompts.js +15 -0
- package/dist/commonjs/v3/prompts.js.map +1 -0
- package/dist/commonjs/v3/streams.js +18 -8
- package/dist/commonjs/v3/streams.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +571 -27
- package/dist/esm/v3/ai.js +865 -56
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat.d.ts +1 -1
- package/dist/esm/v3/chat.js +2 -2
- package/dist/esm/v3/index.d.ts +1 -0
- package/dist/esm/v3/index.js +1 -0
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/esm/v3/prompt.d.ts +38 -0
- package/dist/esm/v3/prompt.js +147 -0
- package/dist/esm/v3/prompt.js.map +1 -0
- package/dist/esm/v3/promptManagement.d.ts +26 -0
- package/dist/esm/v3/promptManagement.js +170 -0
- package/dist/esm/v3/promptManagement.js.map +1 -0
- package/dist/esm/v3/prompts.d.ts +3 -0
- package/dist/esm/v3/prompts.js +3 -0
- package/dist/esm/v3/prompts.js.map +1 -0
- package/dist/esm/v3/streams.js +18 -8
- package/dist/esm/v3/streams.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +2 -2
package/dist/commonjs/v3/ai.js
CHANGED
|
@@ -14,6 +14,14 @@ const chat_constants_js_1 = require("./chat-constants.js");
|
|
|
14
14
|
Object.defineProperty(exports, "CHAT_MESSAGES_STREAM_ID", { enumerable: true, get: function () { return chat_constants_js_1.CHAT_MESSAGES_STREAM_ID; } });
|
|
15
15
|
Object.defineProperty(exports, "CHAT_STOP_STREAM_ID", { enumerable: true, get: function () { return chat_constants_js_1.CHAT_STOP_STREAM_ID; } });
|
|
16
16
|
const METADATA_KEY = "tool.execute.options";
|
|
17
|
+
/**
|
|
18
|
+
* Wrapper around `convertToModelMessages` that always passes
|
|
19
|
+
* `ignoreIncompleteToolCalls: true` to prevent failures from
|
|
20
|
+
* stopped/aborted conversations with partial tool parts.
|
|
21
|
+
*/
|
|
22
|
+
function toModelMessages(messages) {
|
|
23
|
+
return (0, ai_1.convertToModelMessages)(messages, { ignoreIncompleteToolCalls: true });
|
|
24
|
+
}
|
|
17
25
|
const chatTurnContextKey = locals_js_1.locals.create("chat.turnContext");
|
|
18
26
|
function toolFromTask(task, options) {
|
|
19
27
|
if (("schema" in task && !task.schema) || ("jsonSchema" in task && !task.jsonSchema)) {
|
|
@@ -218,6 +226,407 @@ const chatStopControllerKey = locals_js_1.locals.create("chat.stopController");
|
|
|
218
226
|
const chatUIStreamStaticKey = locals_js_1.locals.create("chat.uiMessageStreamOptions.static");
|
|
219
227
|
/** Per-turn UIMessageStream options, set via chat.setUIMessageStreamOptions(). @internal */
|
|
220
228
|
const chatUIStreamPerTurnKey = locals_js_1.locals.create("chat.uiMessageStreamOptions.perTurn");
|
|
229
|
+
function emptyUsage() {
|
|
230
|
+
return {
|
|
231
|
+
inputTokens: undefined,
|
|
232
|
+
outputTokens: undefined,
|
|
233
|
+
totalTokens: undefined,
|
|
234
|
+
inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined },
|
|
235
|
+
outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined },
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function addUsage(a, b) {
|
|
239
|
+
const add = (x, y) => x != null || y != null ? (x ?? 0) + (y ?? 0) : undefined;
|
|
240
|
+
return {
|
|
241
|
+
inputTokens: add(a.inputTokens, b.inputTokens),
|
|
242
|
+
outputTokens: add(a.outputTokens, b.outputTokens),
|
|
243
|
+
totalTokens: add(a.totalTokens, b.totalTokens),
|
|
244
|
+
inputTokenDetails: {
|
|
245
|
+
noCacheTokens: add(a.inputTokenDetails?.noCacheTokens, b.inputTokenDetails?.noCacheTokens),
|
|
246
|
+
cacheReadTokens: add(a.inputTokenDetails?.cacheReadTokens, b.inputTokenDetails?.cacheReadTokens),
|
|
247
|
+
cacheWriteTokens: add(a.inputTokenDetails?.cacheWriteTokens, b.inputTokenDetails?.cacheWriteTokens),
|
|
248
|
+
},
|
|
249
|
+
outputTokenDetails: {
|
|
250
|
+
textTokens: add(a.outputTokenDetails?.textTokens, b.outputTokenDetails?.textTokens),
|
|
251
|
+
reasoningTokens: add(a.outputTokenDetails?.reasoningTokens, b.outputTokenDetails?.reasoningTokens),
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// chat.setMessages — replace accumulated messages for compaction
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
/** @internal */
|
|
259
|
+
const chatOverrideMessagesKey = locals_js_1.locals.create("chat.overrideMessages");
|
|
260
|
+
/**
|
|
261
|
+
* Replace the accumulated conversation messages for the current run.
|
|
262
|
+
*
|
|
263
|
+
* Call from `onTurnStart` to compact before `run()` executes, or from
|
|
264
|
+
* `onTurnComplete` to compact before the next turn. Takes `UIMessage[]`
|
|
265
|
+
* and converts to `ModelMessage[]` internally.
|
|
266
|
+
*/
|
|
267
|
+
function setChatMessages(uiMessages) {
|
|
268
|
+
locals_js_1.locals.set(chatOverrideMessagesKey, uiMessages);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Model-only message override. Set by compaction to replace only the model
|
|
272
|
+
* messages (what goes to the LLM) without affecting UI messages (what gets
|
|
273
|
+
* persisted and displayed). This preserves full conversation history for the
|
|
274
|
+
* user while keeping LLM context compact.
|
|
275
|
+
* @internal
|
|
276
|
+
*/
|
|
277
|
+
const chatOverrideModelMessagesKey = locals_js_1.locals.create("chat.overrideModelMessages");
|
|
278
|
+
/** @internal */
|
|
279
|
+
const chatCompactionStateKey = locals_js_1.locals.create("chat.compaction");
|
|
280
|
+
const chatOnCompactedKey = locals_js_1.locals.create("chat.onCompacted");
|
|
281
|
+
const chatPrepareMessagesKey = locals_js_1.locals.create("chat.prepareMessages");
|
|
282
|
+
/** @internal */
|
|
283
|
+
const chatTaskCompactionKey = locals_js_1.locals.create("chat.taskCompaction");
|
|
284
|
+
/**
|
|
285
|
+
* Check that no tool calls are in-flight in a step's content.
|
|
286
|
+
* Used before compaction to avoid losing tool state mid-execution.
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
function isStepBoundarySafe(step) {
|
|
290
|
+
if (step.finishReason === "error")
|
|
291
|
+
return false;
|
|
292
|
+
const callIds = new Set(step.content.filter((p) => p.type === "tool-call").map((p) => p.toolCallId));
|
|
293
|
+
const settledIds = new Set(step.content
|
|
294
|
+
.filter((p) => p.type === "tool-result" || p.type === "tool-error")
|
|
295
|
+
.map((p) => p.toolCallId));
|
|
296
|
+
return ![...callIds].some((id) => !settledIds.has(id));
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Apply the prepareMessages hook if one is set in locals.
|
|
300
|
+
* @internal
|
|
301
|
+
*/
|
|
302
|
+
async function applyPrepareMessages(messages, reason) {
|
|
303
|
+
const hook = locals_js_1.locals.get(chatPrepareMessagesKey);
|
|
304
|
+
if (!hook)
|
|
305
|
+
return messages;
|
|
306
|
+
const turnCtx = locals_js_1.locals.get(chatTurnContextKey);
|
|
307
|
+
return tracer_js_1.tracer.startActiveSpan("prepareMessages()", async () => {
|
|
308
|
+
return hook({
|
|
309
|
+
messages,
|
|
310
|
+
reason,
|
|
311
|
+
chatId: turnCtx?.chatId ?? "",
|
|
312
|
+
turn: turnCtx?.turn ?? 0,
|
|
313
|
+
clientData: turnCtx?.clientData,
|
|
314
|
+
});
|
|
315
|
+
}, {
|
|
316
|
+
attributes: {
|
|
317
|
+
[v3_1.SemanticInternalAttributes.STYLE_ICON]: "task-hook-onStart",
|
|
318
|
+
[v3_1.SemanticInternalAttributes.COLLAPSED]: true,
|
|
319
|
+
"chat.prepareMessages.reason": reason,
|
|
320
|
+
"chat.prepareMessages.messageCount": messages.length,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Read the current compaction state. Returns the summary and base message count
|
|
326
|
+
* if compaction has occurred in this turn, or `undefined` if not.
|
|
327
|
+
*
|
|
328
|
+
* Use in a custom `prepareStep` to rebuild from a previous compaction:
|
|
329
|
+
* ```ts
|
|
330
|
+
* const state = chat.getCompactionState();
|
|
331
|
+
* if (state) {
|
|
332
|
+
* return { messages: [{ role: "user", content: state.summary }, ...newMsgs] };
|
|
333
|
+
* }
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
function getCompactionState() {
|
|
337
|
+
return locals_js_1.locals.get(chatCompactionStateKey);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Low-level compaction for use inside a custom `prepareStep`.
|
|
341
|
+
*
|
|
342
|
+
* Handles the full decision tree: first step, already-compacted rebuild,
|
|
343
|
+
* boundary safety, threshold check, summarization, stream chunks, state
|
|
344
|
+
* storage, and accumulator update.
|
|
345
|
+
*
|
|
346
|
+
* Returns a `CompactResult` — inspect `result.type` to see what happened,
|
|
347
|
+
* or convert to a prepareStep return with `result.type === "skipped" ? undefined : result`.
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* prepareStep: async ({ messages, steps }) => {
|
|
352
|
+
* // your custom logic here...
|
|
353
|
+
* const result = await chat.compact(messages, steps, {
|
|
354
|
+
* threshold: 80_000,
|
|
355
|
+
* summarize: async (msgs) => generateText({ model, messages: msgs }).then(r => r.text),
|
|
356
|
+
* });
|
|
357
|
+
* if (result.type === "compacted") {
|
|
358
|
+
* logger.info("Compacted!", { summary: result.summary });
|
|
359
|
+
* }
|
|
360
|
+
* return result.type === "skipped" ? undefined : result;
|
|
361
|
+
* },
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
async function chatCompact(messages, steps, options) {
|
|
365
|
+
const currentStep = steps.at(-1);
|
|
366
|
+
// First step — nothing to check
|
|
367
|
+
if (!currentStep) {
|
|
368
|
+
return { type: "skipped" };
|
|
369
|
+
}
|
|
370
|
+
// Already compacted — rebuild from summary + new response messages
|
|
371
|
+
const state = locals_js_1.locals.get(chatCompactionStateKey);
|
|
372
|
+
if (state && isStepBoundarySafe(currentStep)) {
|
|
373
|
+
return {
|
|
374
|
+
type: "rebuilt",
|
|
375
|
+
messages: await applyPrepareMessages([
|
|
376
|
+
{ role: "user", content: state.summary },
|
|
377
|
+
...currentStep.response.messages.slice(state.baseResponseMessageCount),
|
|
378
|
+
], "compaction-rebuild"),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
// Boundary unsafe — skip
|
|
382
|
+
if (!isStepBoundarySafe(currentStep)) {
|
|
383
|
+
return { type: "skipped" };
|
|
384
|
+
}
|
|
385
|
+
const totalTokens = currentStep.usage.totalTokens;
|
|
386
|
+
const inputTokens = currentStep.usage.inputTokens;
|
|
387
|
+
const outputTokens = currentStep.usage.outputTokens;
|
|
388
|
+
const turnCtx = locals_js_1.locals.get(chatTurnContextKey);
|
|
389
|
+
const stepNumber = steps.length - 1;
|
|
390
|
+
const shouldTrigger = options.shouldCompact
|
|
391
|
+
? await options.shouldCompact({
|
|
392
|
+
messages,
|
|
393
|
+
totalTokens,
|
|
394
|
+
inputTokens,
|
|
395
|
+
outputTokens,
|
|
396
|
+
usage: currentStep.usage,
|
|
397
|
+
source: "inner",
|
|
398
|
+
stepNumber,
|
|
399
|
+
steps,
|
|
400
|
+
chatId: turnCtx?.chatId,
|
|
401
|
+
turn: turnCtx?.turn,
|
|
402
|
+
clientData: turnCtx?.clientData,
|
|
403
|
+
})
|
|
404
|
+
: (totalTokens != null && options.threshold != null && totalTokens > options.threshold);
|
|
405
|
+
if (!shouldTrigger) {
|
|
406
|
+
return { type: "skipped" };
|
|
407
|
+
}
|
|
408
|
+
const result = await tracer_js_1.tracer.startActiveSpan("context compaction", async (span) => {
|
|
409
|
+
const compactionId = (0, ai_1.generateId)();
|
|
410
|
+
let summary;
|
|
411
|
+
const { waitUntilComplete } = streams_js_1.streams.writer(exports.CHAT_STREAM_KEY, {
|
|
412
|
+
spanName: "stream compaction chunks",
|
|
413
|
+
collapsed: true,
|
|
414
|
+
execute: async ({ write }) => {
|
|
415
|
+
write({ type: "step-start" });
|
|
416
|
+
write({
|
|
417
|
+
type: "data-compaction",
|
|
418
|
+
id: compactionId,
|
|
419
|
+
data: { status: "compacting", totalTokens },
|
|
420
|
+
});
|
|
421
|
+
// Generate summary
|
|
422
|
+
summary = await options.summarize(messages);
|
|
423
|
+
// Store state in locals for subsequent steps
|
|
424
|
+
locals_js_1.locals.set(chatCompactionStateKey, {
|
|
425
|
+
summary,
|
|
426
|
+
baseResponseMessageCount: currentStep.response.messages.length,
|
|
427
|
+
});
|
|
428
|
+
// Set model-only override — UI messages stay intact for persistence.
|
|
429
|
+
// The summary becomes the model message history for the next turn,
|
|
430
|
+
// while accumulatedUIMessages keeps the full conversation for display.
|
|
431
|
+
locals_js_1.locals.set(chatOverrideModelMessagesKey, [
|
|
432
|
+
{ role: "assistant", content: [{ type: "text", text: `[Conversation summary]\n\n${summary}` }] },
|
|
433
|
+
]);
|
|
434
|
+
// Fire onCompacted hook
|
|
435
|
+
const onCompactedHook = locals_js_1.locals.get(chatOnCompactedKey);
|
|
436
|
+
if (onCompactedHook) {
|
|
437
|
+
await onCompactedHook({
|
|
438
|
+
summary,
|
|
439
|
+
messages,
|
|
440
|
+
messageCount: messages.length,
|
|
441
|
+
usage: currentStep.usage,
|
|
442
|
+
totalTokens,
|
|
443
|
+
inputTokens,
|
|
444
|
+
outputTokens,
|
|
445
|
+
stepNumber,
|
|
446
|
+
chatId: turnCtx?.chatId,
|
|
447
|
+
turn: turnCtx?.turn,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
write({
|
|
451
|
+
type: "data-compaction",
|
|
452
|
+
id: compactionId,
|
|
453
|
+
data: { status: "complete", totalTokens },
|
|
454
|
+
});
|
|
455
|
+
write({ type: "finish-step" });
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
await waitUntilComplete();
|
|
459
|
+
// Set attributes after we have the summary
|
|
460
|
+
span.setAttribute("compaction.summary_length", summary.length);
|
|
461
|
+
return {
|
|
462
|
+
type: "compacted",
|
|
463
|
+
messages: await applyPrepareMessages([{ role: "user", content: summary }], "compaction-result"),
|
|
464
|
+
summary,
|
|
465
|
+
};
|
|
466
|
+
}, {
|
|
467
|
+
attributes: {
|
|
468
|
+
[v3_1.SemanticInternalAttributes.STYLE_ICON]: "tabler-scissors",
|
|
469
|
+
"compaction.threshold": options.threshold,
|
|
470
|
+
"compaction.total_tokens": totalTokens ?? 0,
|
|
471
|
+
"compaction.input_tokens": inputTokens ?? 0,
|
|
472
|
+
"compaction.message_count": messages.length,
|
|
473
|
+
"compaction.step_number": stepNumber,
|
|
474
|
+
...(turnCtx?.chatId ? { "compaction.chat_id": turnCtx.chatId } : {}),
|
|
475
|
+
...(turnCtx?.turn != null ? { "compaction.turn": turnCtx.turn } : {}),
|
|
476
|
+
...(0, v3_1.accessoryAttributes)({
|
|
477
|
+
items: [
|
|
478
|
+
{ text: `${totalTokens ?? 0} tokens`, variant: "normal" },
|
|
479
|
+
{ text: `${messages.length} msgs`, variant: "normal" },
|
|
480
|
+
],
|
|
481
|
+
style: "codepath",
|
|
482
|
+
}),
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
return result;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Returns a `prepareStep` function that handles context compaction automatically.
|
|
489
|
+
*
|
|
490
|
+
* Monitors token usage between tool-call steps. When `totalTokens` exceeds
|
|
491
|
+
* the threshold, generates a summary via `summarize()`, replaces the message
|
|
492
|
+
* history, and emits `data-compaction` stream chunks for the frontend.
|
|
493
|
+
*
|
|
494
|
+
* @example
|
|
495
|
+
* ```ts
|
|
496
|
+
* return streamText({
|
|
497
|
+
* ...chat.toStreamTextOptions({ registry }),
|
|
498
|
+
* messages: chat.addCacheBreaks(messages),
|
|
499
|
+
* prepareStep: chat.compactionStep({
|
|
500
|
+
* threshold: 80_000,
|
|
501
|
+
* summarize: async (messages) => {
|
|
502
|
+
* return generateText({ model, messages: [...messages, { role: "user", content: "Summarize." }] })
|
|
503
|
+
* .then((r) => r.text);
|
|
504
|
+
* },
|
|
505
|
+
* }),
|
|
506
|
+
* tools: { ... },
|
|
507
|
+
* });
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
510
|
+
function chatCompactionStep(options) {
|
|
511
|
+
return async ({ messages, steps }) => {
|
|
512
|
+
const result = await chatCompact(messages, steps, options);
|
|
513
|
+
return result.type === "skipped" ? undefined : result;
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
// ---------------------------------------------------------------------------
|
|
517
|
+
// chat.isCompactionSafe — check if it's safe to compact messages
|
|
518
|
+
// ---------------------------------------------------------------------------
|
|
519
|
+
/**
|
|
520
|
+
* Checks whether it's safe to compact the message history. Returns `false`
|
|
521
|
+
* if any tool calls are in-flight (incomplete tool invocations without results).
|
|
522
|
+
*
|
|
523
|
+
* Call before `chat.setMessages()` to avoid corrupting tool-call state.
|
|
524
|
+
*/
|
|
525
|
+
function isCompactionSafe(messages) {
|
|
526
|
+
for (const msg of messages) {
|
|
527
|
+
if (msg.role !== "assistant")
|
|
528
|
+
continue;
|
|
529
|
+
for (const part of msg.parts) {
|
|
530
|
+
if (part.type === "tool-invocation") {
|
|
531
|
+
const state = part.toolInvocation?.state ?? part.state;
|
|
532
|
+
if (state !== "result" && state !== "error") {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
/** @internal */
|
|
541
|
+
const chatPromptKey = locals_js_1.locals.create("chat.prompt");
|
|
542
|
+
/**
|
|
543
|
+
* Store a resolved prompt (or plain string) for the current run.
|
|
544
|
+
* Call from any hook (`onPreload`, `onChatStart`, `onTurnStart`) or `run()`.
|
|
545
|
+
*/
|
|
546
|
+
function setChatPrompt(resolved) {
|
|
547
|
+
if (typeof resolved === "string") {
|
|
548
|
+
locals_js_1.locals.set(chatPromptKey, {
|
|
549
|
+
text: resolved,
|
|
550
|
+
model: undefined,
|
|
551
|
+
config: undefined,
|
|
552
|
+
promptId: "",
|
|
553
|
+
version: 0,
|
|
554
|
+
labels: [],
|
|
555
|
+
toAISDKTelemetry: () => ({
|
|
556
|
+
experimental_telemetry: { isEnabled: true, metadata: {} },
|
|
557
|
+
}),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
locals_js_1.locals.set(chatPromptKey, resolved);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Read the stored prompt. Throws if `chat.prompt.set()` has not been called.
|
|
566
|
+
*/
|
|
567
|
+
function getChatPrompt() {
|
|
568
|
+
const prompt = locals_js_1.locals.get(chatPromptKey);
|
|
569
|
+
if (!prompt) {
|
|
570
|
+
throw new Error("chat.prompt() called before chat.prompt.set(). Set a prompt in onPreload, onChatStart, onTurnStart, or run() first.");
|
|
571
|
+
}
|
|
572
|
+
return prompt;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Returns an options object ready to spread into `streamText()`.
|
|
576
|
+
*
|
|
577
|
+
* Includes `system`, `experimental_telemetry`, and any config fields
|
|
578
|
+
* (temperature, maxTokens, etc.) from the stored prompt.
|
|
579
|
+
*
|
|
580
|
+
* When a `registry` is provided and the prompt has a `model` string,
|
|
581
|
+
* the resolved `LanguageModel` is included as `model`.
|
|
582
|
+
*
|
|
583
|
+
* If no prompt has been set, returns `{}` (no-op spread).
|
|
584
|
+
*/
|
|
585
|
+
function toStreamTextOptions(options) {
|
|
586
|
+
const prompt = locals_js_1.locals.get(chatPromptKey);
|
|
587
|
+
if (!prompt)
|
|
588
|
+
return {};
|
|
589
|
+
const result = {
|
|
590
|
+
system: prompt.text,
|
|
591
|
+
};
|
|
592
|
+
// Resolve model via registry if both are present
|
|
593
|
+
if (options?.registry && prompt.model) {
|
|
594
|
+
result.model = options.registry.languageModel(prompt.model);
|
|
595
|
+
}
|
|
596
|
+
// Spread config (temperature, maxTokens, etc.)
|
|
597
|
+
if (prompt.config) {
|
|
598
|
+
Object.assign(result, prompt.config);
|
|
599
|
+
}
|
|
600
|
+
// Add telemetry (forward additional metadata from caller)
|
|
601
|
+
const telemetry = prompt.toAISDKTelemetry(options?.telemetry);
|
|
602
|
+
Object.assign(result, telemetry);
|
|
603
|
+
// Auto-inject prepareStep when task-level compaction is configured.
|
|
604
|
+
// We build a custom prepareStep instead of using chatCompactionStep so we
|
|
605
|
+
// can pass the enriched SummarizeEvent to taskCompaction.summarize.
|
|
606
|
+
const taskCompaction = locals_js_1.locals.get(chatTaskCompactionKey);
|
|
607
|
+
if (taskCompaction) {
|
|
608
|
+
result.prepareStep = async ({ messages, steps }) => {
|
|
609
|
+
const compactResult = await chatCompact(messages, steps, {
|
|
610
|
+
shouldCompact: taskCompaction.shouldCompact,
|
|
611
|
+
summarize: (msgs) => {
|
|
612
|
+
const ctx = locals_js_1.locals.get(chatTurnContextKey);
|
|
613
|
+
const lastStep = steps.at(-1);
|
|
614
|
+
return taskCompaction.summarize({
|
|
615
|
+
messages: msgs,
|
|
616
|
+
usage: lastStep?.usage,
|
|
617
|
+
source: "inner",
|
|
618
|
+
stepNumber: steps.length - 1,
|
|
619
|
+
chatId: ctx?.chatId,
|
|
620
|
+
turn: ctx?.turn,
|
|
621
|
+
clientData: ctx?.clientData,
|
|
622
|
+
});
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
return compactResult.type === "skipped" ? undefined : compactResult;
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
221
630
|
function isUIMessageStreamable(value) {
|
|
222
631
|
return (typeof value === "object" &&
|
|
223
632
|
value !== null &&
|
|
@@ -324,7 +733,7 @@ async function pipeChat(source, options) {
|
|
|
324
733
|
* ```
|
|
325
734
|
*/
|
|
326
735
|
function chatTask(options) {
|
|
327
|
-
const { run: userRun, clientDataSchema, onPreload, onChatStart, onTurnStart, onTurnComplete, maxTurns = 100, turnTimeout = "1h",
|
|
736
|
+
const { run: userRun, clientDataSchema, onPreload, onChatStart, onTurnStart, onBeforeTurnComplete, onCompacted, compaction, prepareMessages, onTurnComplete, maxTurns = 100, turnTimeout = "1h", idleTimeoutInSeconds = 30, chatAccessTokenTTL = "1h", preloadIdleTimeoutInSeconds, preloadTimeout, uiMessageStreamOptions, ...restOptions } = options;
|
|
328
737
|
const parseClientData = clientDataSchema
|
|
329
738
|
? (0, v3_1.getSchemaParseFn)(clientDataSchema)
|
|
330
739
|
: undefined;
|
|
@@ -340,6 +749,16 @@ function chatTask(options) {
|
|
|
340
749
|
if (uiMessageStreamOptions) {
|
|
341
750
|
locals_js_1.locals.set(chatUIStreamStaticKey, uiMessageStreamOptions);
|
|
342
751
|
}
|
|
752
|
+
// Store onCompacted hook in locals so chat.compact() can call it
|
|
753
|
+
if (onCompacted) {
|
|
754
|
+
locals_js_1.locals.set(chatOnCompactedKey, onCompacted);
|
|
755
|
+
}
|
|
756
|
+
if (prepareMessages) {
|
|
757
|
+
locals_js_1.locals.set(chatPrepareMessagesKey, prepareMessages);
|
|
758
|
+
}
|
|
759
|
+
if (compaction) {
|
|
760
|
+
locals_js_1.locals.set(chatTaskCompactionKey, compaction);
|
|
761
|
+
}
|
|
343
762
|
let currentWirePayload = payload;
|
|
344
763
|
const continuation = payload.continuation ?? false;
|
|
345
764
|
const previousRunId = payload.previousRunId;
|
|
@@ -351,6 +770,9 @@ function chatTask(options) {
|
|
|
351
770
|
// Accumulated UI messages for persistence. Mirrors the model accumulator
|
|
352
771
|
// but in frontend-friendly UIMessage format (with parts, id, etc.).
|
|
353
772
|
let accumulatedUIMessages = [];
|
|
773
|
+
// Token usage tracking across turns
|
|
774
|
+
let previousTurnUsage;
|
|
775
|
+
let cumulativeUsage = emptyUsage();
|
|
354
776
|
// Mutable reference to the current turn's stop controller so the
|
|
355
777
|
// stop input stream listener (registered once) can abort the right turn.
|
|
356
778
|
let currentStopController;
|
|
@@ -403,14 +825,14 @@ function chatTask(options) {
|
|
|
403
825
|
});
|
|
404
826
|
}
|
|
405
827
|
// Wait for the first real message — use preload-specific timeouts if configured
|
|
406
|
-
const
|
|
407
|
-
??
|
|
408
|
-
??
|
|
828
|
+
const effectivePreloadIdleTimeout = payload.idleTimeoutInSeconds
|
|
829
|
+
?? preloadIdleTimeoutInSeconds
|
|
830
|
+
?? idleTimeoutInSeconds;
|
|
409
831
|
const effectivePreloadTimeout = metadata_js_1.metadata.get(TURN_TIMEOUT_METADATA_KEY)
|
|
410
832
|
?? preloadTimeout
|
|
411
833
|
?? turnTimeout;
|
|
412
|
-
const preloadResult = await messagesInput.
|
|
413
|
-
|
|
834
|
+
const preloadResult = await messagesInput.waitWithIdleTimeout({
|
|
835
|
+
idleTimeoutInSeconds: effectivePreloadIdleTimeout,
|
|
414
836
|
timeout: effectivePreloadTimeout,
|
|
415
837
|
spanName: "waiting for first message",
|
|
416
838
|
});
|
|
@@ -450,9 +872,10 @@ function chatTask(options) {
|
|
|
450
872
|
turnAttributes["chat.client_data"] =
|
|
451
873
|
typeof wireMetadata === "string" ? wireMetadata : JSON.stringify(wireMetadata);
|
|
452
874
|
}
|
|
453
|
-
const turnResult = await tracer_js_1.tracer.startActiveSpan(`chat turn ${turn + 1}`, async () => {
|
|
875
|
+
const turnResult = await tracer_js_1.tracer.startActiveSpan(`chat turn ${turn + 1}`, async (turnSpan) => {
|
|
454
876
|
locals_js_1.locals.set(chatPipeCountKey, 0);
|
|
455
877
|
locals_js_1.locals.set(chatDeferKey, new Set());
|
|
878
|
+
locals_js_1.locals.set(chatCompactionStateKey, undefined);
|
|
456
879
|
// Store chat context for auto-detection by ai.tool subtasks
|
|
457
880
|
locals_js_1.locals.set(chatTurnContextKey, {
|
|
458
881
|
chatId: currentWirePayload.chatId,
|
|
@@ -482,7 +905,7 @@ function chatTask(options) {
|
|
|
482
905
|
// Convert the incoming UIMessages to model messages and update the accumulator.
|
|
483
906
|
// Turn 1: full history from the frontend → replaces the accumulator.
|
|
484
907
|
// Turn 2+: only the new message(s) → appended to the accumulator.
|
|
485
|
-
const incomingModelMessages = await (
|
|
908
|
+
const incomingModelMessages = await toModelMessages(cleanedUIMessages);
|
|
486
909
|
// Track new messages for this turn (user input + assistant response).
|
|
487
910
|
const turnNewModelMessages = [];
|
|
488
911
|
const turnNewUIMessages = [];
|
|
@@ -570,7 +993,16 @@ function chatTask(options) {
|
|
|
570
993
|
continuation,
|
|
571
994
|
previousRunId,
|
|
572
995
|
preloaded,
|
|
996
|
+
previousTurnUsage,
|
|
997
|
+
totalUsage: cumulativeUsage,
|
|
573
998
|
});
|
|
999
|
+
// Check if onTurnStart replaced messages (compaction)
|
|
1000
|
+
const turnStartOverride = locals_js_1.locals.get(chatOverrideMessagesKey);
|
|
1001
|
+
if (turnStartOverride) {
|
|
1002
|
+
locals_js_1.locals.set(chatOverrideMessagesKey, undefined);
|
|
1003
|
+
accumulatedUIMessages = [...turnStartOverride];
|
|
1004
|
+
accumulatedMessages = await toModelMessages(turnStartOverride);
|
|
1005
|
+
}
|
|
574
1006
|
}, {
|
|
575
1007
|
attributes: {
|
|
576
1008
|
[v3_1.SemanticInternalAttributes.STYLE_ICON]: "task-hook-onStart",
|
|
@@ -595,14 +1027,17 @@ function chatTask(options) {
|
|
|
595
1027
|
let resolveOnFinish;
|
|
596
1028
|
const onFinishPromise = new Promise((r) => { resolveOnFinish = r; });
|
|
597
1029
|
let onFinishAttached = false;
|
|
1030
|
+
let runResult;
|
|
598
1031
|
try {
|
|
599
|
-
|
|
1032
|
+
runResult = await userRun({
|
|
600
1033
|
...restWire,
|
|
601
|
-
messages: accumulatedMessages,
|
|
1034
|
+
messages: await applyPrepareMessages(accumulatedMessages, "run"),
|
|
602
1035
|
clientData,
|
|
603
1036
|
continuation,
|
|
604
1037
|
previousRunId,
|
|
605
1038
|
preloaded,
|
|
1039
|
+
previousTurnUsage,
|
|
1040
|
+
totalUsage: cumulativeUsage,
|
|
606
1041
|
signal: combinedSignal,
|
|
607
1042
|
cancelSignal,
|
|
608
1043
|
stopSignal,
|
|
@@ -610,9 +1045,9 @@ function chatTask(options) {
|
|
|
610
1045
|
// Auto-pipe if the run function returned a StreamTextResult or similar,
|
|
611
1046
|
// but only if pipeChat() wasn't already called manually during this turn.
|
|
612
1047
|
// We call toUIMessageStream ourselves to attach onFinish for response capture.
|
|
613
|
-
if ((locals_js_1.locals.get(chatPipeCountKey) ?? 0) === 0 && isUIMessageStreamable(
|
|
1048
|
+
if ((locals_js_1.locals.get(chatPipeCountKey) ?? 0) === 0 && isUIMessageStreamable(runResult)) {
|
|
614
1049
|
onFinishAttached = true;
|
|
615
|
-
const uiStream =
|
|
1050
|
+
const uiStream = runResult.toUIMessageStream({
|
|
616
1051
|
...resolveUIMessageStreamOptions(),
|
|
617
1052
|
onFinish: ({ responseMessage }) => {
|
|
618
1053
|
capturedResponseMessage = responseMessage;
|
|
@@ -642,6 +1077,72 @@ function chatTask(options) {
|
|
|
642
1077
|
if (onFinishAttached) {
|
|
643
1078
|
await onFinishPromise;
|
|
644
1079
|
}
|
|
1080
|
+
// Capture token usage from the streamText result (if available).
|
|
1081
|
+
// totalUsage is a PromiseLike that resolves after the stream is consumed.
|
|
1082
|
+
let turnUsage;
|
|
1083
|
+
if (runResult != null && typeof runResult.totalUsage?.then === "function") {
|
|
1084
|
+
try {
|
|
1085
|
+
turnUsage = await runResult.totalUsage;
|
|
1086
|
+
}
|
|
1087
|
+
catch { /* non-fatal — usage capture failed */ }
|
|
1088
|
+
}
|
|
1089
|
+
if (turnUsage) {
|
|
1090
|
+
cumulativeUsage = addUsage(cumulativeUsage, turnUsage);
|
|
1091
|
+
previousTurnUsage = turnUsage;
|
|
1092
|
+
// Add usage attributes to the turn span
|
|
1093
|
+
if (turnUsage.inputTokens != null) {
|
|
1094
|
+
turnSpan.setAttribute("gen_ai.usage.input_tokens", turnUsage.inputTokens);
|
|
1095
|
+
}
|
|
1096
|
+
if (turnUsage.outputTokens != null) {
|
|
1097
|
+
turnSpan.setAttribute("gen_ai.usage.output_tokens", turnUsage.outputTokens);
|
|
1098
|
+
}
|
|
1099
|
+
if (turnUsage.totalTokens != null) {
|
|
1100
|
+
turnSpan.setAttribute("gen_ai.usage.total_tokens", turnUsage.totalTokens);
|
|
1101
|
+
}
|
|
1102
|
+
if (cumulativeUsage.totalTokens != null) {
|
|
1103
|
+
turnSpan.setAttribute("gen_ai.usage.cumulative_total_tokens", cumulativeUsage.totalTokens);
|
|
1104
|
+
}
|
|
1105
|
+
if (cumulativeUsage.inputTokens != null) {
|
|
1106
|
+
turnSpan.setAttribute("gen_ai.usage.cumulative_input_tokens", cumulativeUsage.inputTokens);
|
|
1107
|
+
}
|
|
1108
|
+
if (cumulativeUsage.outputTokens != null) {
|
|
1109
|
+
turnSpan.setAttribute("gen_ai.usage.cumulative_output_tokens", cumulativeUsage.outputTokens);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
// Check if run() (e.g. via prepareStep) replaced messages during this turn.
|
|
1113
|
+
// This supports intra-turn compaction — the compacted messages become the
|
|
1114
|
+
// new base, and the response gets appended on top.
|
|
1115
|
+
const runOverride = locals_js_1.locals.get(chatOverrideMessagesKey);
|
|
1116
|
+
if (runOverride) {
|
|
1117
|
+
locals_js_1.locals.set(chatOverrideMessagesKey, undefined);
|
|
1118
|
+
accumulatedUIMessages = [...runOverride];
|
|
1119
|
+
accumulatedMessages = await toModelMessages(runOverride);
|
|
1120
|
+
}
|
|
1121
|
+
// Check if compaction set a model-only override (preserves UI messages).
|
|
1122
|
+
// Apply compactUIMessages/compactModelMessages callbacks if configured.
|
|
1123
|
+
const modelOnlyOverride = locals_js_1.locals.get(chatOverrideModelMessagesKey);
|
|
1124
|
+
if (modelOnlyOverride) {
|
|
1125
|
+
const compactionSummary = locals_js_1.locals.get(chatCompactionStateKey)?.summary ?? "";
|
|
1126
|
+
const taskCompactionConfig = locals_js_1.locals.get(chatTaskCompactionKey);
|
|
1127
|
+
locals_js_1.locals.set(chatOverrideModelMessagesKey, undefined);
|
|
1128
|
+
const compactEvent = {
|
|
1129
|
+
summary: compactionSummary,
|
|
1130
|
+
uiMessages: accumulatedUIMessages,
|
|
1131
|
+
modelMessages: accumulatedMessages,
|
|
1132
|
+
chatId: currentWirePayload.chatId,
|
|
1133
|
+
turn,
|
|
1134
|
+
clientData,
|
|
1135
|
+
source: "inner",
|
|
1136
|
+
};
|
|
1137
|
+
// Apply model messages: callback or default (use override)
|
|
1138
|
+
accumulatedMessages = taskCompactionConfig?.compactModelMessages
|
|
1139
|
+
? await taskCompactionConfig.compactModelMessages(compactEvent)
|
|
1140
|
+
: modelOnlyOverride;
|
|
1141
|
+
// Apply UI messages: callback or default (preserve all)
|
|
1142
|
+
if (taskCompactionConfig?.compactUIMessages) {
|
|
1143
|
+
accumulatedUIMessages = await taskCompactionConfig.compactUIMessages(compactEvent);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
645
1146
|
// Determine if the user stopped generation this turn (not a full run cancel).
|
|
646
1147
|
const wasStopped = stopController.signal.aborted && !runSignal.aborted;
|
|
647
1148
|
// Append the assistant's response (partial or complete) to the accumulator.
|
|
@@ -664,7 +1165,7 @@ function chatTask(options) {
|
|
|
664
1165
|
accumulatedUIMessages.push(capturedResponseMessage);
|
|
665
1166
|
turnNewUIMessages.push(capturedResponseMessage);
|
|
666
1167
|
try {
|
|
667
|
-
const responseModelMessages = await (
|
|
1168
|
+
const responseModelMessages = await toModelMessages([
|
|
668
1169
|
stripProviderMetadata(capturedResponseMessage),
|
|
669
1170
|
]);
|
|
670
1171
|
accumulatedMessages.push(...responseModelMessages);
|
|
@@ -680,11 +1181,8 @@ function chatTask(options) {
|
|
|
680
1181
|
// accumulation (e.g. via a `chat.addMessages()` helper).
|
|
681
1182
|
if (runSignal.aborted)
|
|
682
1183
|
return "exit";
|
|
683
|
-
// Write turn-complete control chunk so frontend closes its stream.
|
|
684
|
-
// Capture the lastEventId from the stream writer for resume support.
|
|
685
|
-
const turnCompleteResult = await writeTurnCompleteChunk(currentWirePayload.chatId, turnAccessToken);
|
|
686
1184
|
// Await deferred background work (e.g. DB writes from onTurnStart)
|
|
687
|
-
// before firing
|
|
1185
|
+
// before firing hooks so they can rely on the work being done.
|
|
688
1186
|
const deferredWork = locals_js_1.locals.get(chatDeferKey);
|
|
689
1187
|
if (deferredWork && deferredWork.size > 0) {
|
|
690
1188
|
await Promise.race([
|
|
@@ -692,27 +1190,169 @@ function chatTask(options) {
|
|
|
692
1190
|
new Promise((r) => setTimeout(r, 5_000)),
|
|
693
1191
|
]);
|
|
694
1192
|
}
|
|
695
|
-
//
|
|
1193
|
+
// Outer-loop compaction: runs between turns for single-step responses
|
|
1194
|
+
// where prepareStep never fires (no tool calls = no step boundaries).
|
|
1195
|
+
// Only triggers when: task has compaction configured, prepareStep didn't
|
|
1196
|
+
// already compact this turn, and shouldCompact returns true.
|
|
1197
|
+
const outerCompaction = locals_js_1.locals.get(chatTaskCompactionKey);
|
|
1198
|
+
const innerCompactionState = locals_js_1.locals.get(chatCompactionStateKey);
|
|
1199
|
+
if (outerCompaction && !innerCompactionState && turnUsage && !wasStopped) {
|
|
1200
|
+
const shouldTrigger = await outerCompaction.shouldCompact({
|
|
1201
|
+
messages: accumulatedMessages,
|
|
1202
|
+
totalTokens: turnUsage.totalTokens,
|
|
1203
|
+
inputTokens: turnUsage.inputTokens,
|
|
1204
|
+
outputTokens: turnUsage.outputTokens,
|
|
1205
|
+
usage: turnUsage,
|
|
1206
|
+
totalUsage: cumulativeUsage,
|
|
1207
|
+
chatId: currentWirePayload.chatId,
|
|
1208
|
+
turn,
|
|
1209
|
+
clientData,
|
|
1210
|
+
source: "outer",
|
|
1211
|
+
});
|
|
1212
|
+
if (shouldTrigger) {
|
|
1213
|
+
await tracer_js_1.tracer.startActiveSpan("context compaction (outer loop)", async (compactionSpan) => {
|
|
1214
|
+
const compactionId = (0, ai_1.generateId)();
|
|
1215
|
+
const { waitUntilComplete } = streams_js_1.streams.writer(exports.CHAT_STREAM_KEY, {
|
|
1216
|
+
spanName: "stream compaction chunks",
|
|
1217
|
+
collapsed: true,
|
|
1218
|
+
execute: async ({ write }) => {
|
|
1219
|
+
write({
|
|
1220
|
+
type: "data-compaction",
|
|
1221
|
+
id: compactionId,
|
|
1222
|
+
data: { status: "compacting", totalTokens: turnUsage.totalTokens },
|
|
1223
|
+
});
|
|
1224
|
+
const summary = await outerCompaction.summarize({
|
|
1225
|
+
messages: accumulatedMessages,
|
|
1226
|
+
usage: turnUsage,
|
|
1227
|
+
totalUsage: cumulativeUsage,
|
|
1228
|
+
chatId: currentWirePayload.chatId,
|
|
1229
|
+
turn,
|
|
1230
|
+
clientData,
|
|
1231
|
+
source: "outer",
|
|
1232
|
+
});
|
|
1233
|
+
// Apply compactModelMessages/compactUIMessages callbacks, or defaults.
|
|
1234
|
+
const outerCompactEvent = {
|
|
1235
|
+
summary,
|
|
1236
|
+
uiMessages: accumulatedUIMessages,
|
|
1237
|
+
modelMessages: accumulatedMessages,
|
|
1238
|
+
chatId: currentWirePayload.chatId,
|
|
1239
|
+
turn,
|
|
1240
|
+
clientData,
|
|
1241
|
+
source: "outer",
|
|
1242
|
+
};
|
|
1243
|
+
// Model messages: callback or default (replace with summary)
|
|
1244
|
+
accumulatedMessages = outerCompaction.compactModelMessages
|
|
1245
|
+
? await outerCompaction.compactModelMessages(outerCompactEvent)
|
|
1246
|
+
: [{ role: "assistant", content: [{ type: "text", text: `[Conversation summary]\n\n${summary}` }] }];
|
|
1247
|
+
// UI messages: callback or default (preserve all)
|
|
1248
|
+
if (outerCompaction.compactUIMessages) {
|
|
1249
|
+
accumulatedUIMessages = await outerCompaction.compactUIMessages(outerCompactEvent);
|
|
1250
|
+
}
|
|
1251
|
+
// Fire onCompacted hook
|
|
1252
|
+
const onCompactedHook = locals_js_1.locals.get(chatOnCompactedKey);
|
|
1253
|
+
if (onCompactedHook) {
|
|
1254
|
+
await onCompactedHook({
|
|
1255
|
+
summary,
|
|
1256
|
+
messages: accumulatedMessages,
|
|
1257
|
+
messageCount: accumulatedMessages.length,
|
|
1258
|
+
usage: turnUsage,
|
|
1259
|
+
totalTokens: turnUsage.totalTokens,
|
|
1260
|
+
inputTokens: turnUsage.inputTokens,
|
|
1261
|
+
outputTokens: turnUsage.outputTokens,
|
|
1262
|
+
stepNumber: -1, // outer loop, not a step
|
|
1263
|
+
chatId: currentWirePayload.chatId,
|
|
1264
|
+
turn,
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
compactionSpan.setAttribute("compaction.summary_length", summary.length);
|
|
1268
|
+
write({
|
|
1269
|
+
type: "data-compaction",
|
|
1270
|
+
id: compactionId,
|
|
1271
|
+
data: { status: "complete", totalTokens: turnUsage.totalTokens },
|
|
1272
|
+
});
|
|
1273
|
+
},
|
|
1274
|
+
});
|
|
1275
|
+
await waitUntilComplete();
|
|
1276
|
+
}, {
|
|
1277
|
+
attributes: {
|
|
1278
|
+
[v3_1.SemanticInternalAttributes.STYLE_ICON]: "tabler-scissors",
|
|
1279
|
+
"compaction.total_tokens": turnUsage.totalTokens ?? 0,
|
|
1280
|
+
"compaction.input_tokens": turnUsage.inputTokens ?? 0,
|
|
1281
|
+
"compaction.message_count": accumulatedMessages.length,
|
|
1282
|
+
"compaction.outer_loop": true,
|
|
1283
|
+
"compaction.turn": turn,
|
|
1284
|
+
...(currentWirePayload.chatId ? { "compaction.chat_id": currentWirePayload.chatId } : {}),
|
|
1285
|
+
...(0, v3_1.accessoryAttributes)({
|
|
1286
|
+
items: [
|
|
1287
|
+
{ text: `${turnUsage.totalTokens ?? 0} tokens`, variant: "normal" },
|
|
1288
|
+
{ text: `${accumulatedMessages.length} msgs`, variant: "normal" },
|
|
1289
|
+
{ text: "outer loop", variant: "normal" },
|
|
1290
|
+
],
|
|
1291
|
+
style: "codepath",
|
|
1292
|
+
}),
|
|
1293
|
+
},
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
const turnCompleteEvent = {
|
|
1298
|
+
chatId: currentWirePayload.chatId,
|
|
1299
|
+
messages: accumulatedMessages,
|
|
1300
|
+
uiMessages: accumulatedUIMessages,
|
|
1301
|
+
newMessages: turnNewModelMessages,
|
|
1302
|
+
newUIMessages: turnNewUIMessages,
|
|
1303
|
+
responseMessage: capturedResponseMessage,
|
|
1304
|
+
rawResponseMessage,
|
|
1305
|
+
turn,
|
|
1306
|
+
runId: currentRunId,
|
|
1307
|
+
chatAccessToken: turnAccessToken,
|
|
1308
|
+
clientData,
|
|
1309
|
+
stopped: wasStopped,
|
|
1310
|
+
continuation,
|
|
1311
|
+
previousRunId,
|
|
1312
|
+
preloaded,
|
|
1313
|
+
usage: turnUsage,
|
|
1314
|
+
totalUsage: cumulativeUsage,
|
|
1315
|
+
};
|
|
1316
|
+
// Fire onBeforeTurnComplete — stream is still open so the hook
|
|
1317
|
+
// can write custom chunks to the frontend (e.g. compaction progress).
|
|
1318
|
+
if (onBeforeTurnComplete) {
|
|
1319
|
+
await tracer_js_1.tracer.startActiveSpan("onBeforeTurnComplete()", async () => {
|
|
1320
|
+
await onBeforeTurnComplete(turnCompleteEvent);
|
|
1321
|
+
// Check if the hook replaced messages (compaction)
|
|
1322
|
+
const override = locals_js_1.locals.get(chatOverrideMessagesKey);
|
|
1323
|
+
if (override) {
|
|
1324
|
+
locals_js_1.locals.set(chatOverrideMessagesKey, undefined);
|
|
1325
|
+
accumulatedUIMessages = [...override];
|
|
1326
|
+
accumulatedMessages = await toModelMessages(override);
|
|
1327
|
+
// Update event so onTurnComplete sees compacted messages
|
|
1328
|
+
turnCompleteEvent.messages = accumulatedMessages;
|
|
1329
|
+
turnCompleteEvent.uiMessages = accumulatedUIMessages;
|
|
1330
|
+
}
|
|
1331
|
+
}, {
|
|
1332
|
+
attributes: {
|
|
1333
|
+
[v3_1.SemanticInternalAttributes.STYLE_ICON]: "task-hook-onComplete",
|
|
1334
|
+
[v3_1.SemanticInternalAttributes.COLLAPSED]: true,
|
|
1335
|
+
"chat.id": currentWirePayload.chatId,
|
|
1336
|
+
"chat.turn": turn + 1,
|
|
1337
|
+
},
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
// Write turn-complete control chunk — closes the frontend stream.
|
|
1341
|
+
const turnCompleteResult = await writeTurnCompleteChunk(currentWirePayload.chatId, turnAccessToken);
|
|
1342
|
+
// Fire onTurnComplete — stream is closed, use for persistence.
|
|
696
1343
|
if (onTurnComplete) {
|
|
697
1344
|
await tracer_js_1.tracer.startActiveSpan("onTurnComplete()", async () => {
|
|
698
1345
|
await onTurnComplete({
|
|
699
|
-
|
|
700
|
-
messages: accumulatedMessages,
|
|
701
|
-
uiMessages: accumulatedUIMessages,
|
|
702
|
-
newMessages: turnNewModelMessages,
|
|
703
|
-
newUIMessages: turnNewUIMessages,
|
|
704
|
-
responseMessage: capturedResponseMessage,
|
|
705
|
-
rawResponseMessage,
|
|
706
|
-
turn,
|
|
707
|
-
runId: currentRunId,
|
|
708
|
-
chatAccessToken: turnAccessToken,
|
|
1346
|
+
...turnCompleteEvent,
|
|
709
1347
|
lastEventId: turnCompleteResult.lastEventId,
|
|
710
|
-
clientData,
|
|
711
|
-
stopped: wasStopped,
|
|
712
|
-
continuation,
|
|
713
|
-
previousRunId,
|
|
714
|
-
preloaded,
|
|
715
1348
|
});
|
|
1349
|
+
// Check if onTurnComplete replaced messages (compaction)
|
|
1350
|
+
const turnCompleteOverride = locals_js_1.locals.get(chatOverrideMessagesKey);
|
|
1351
|
+
if (turnCompleteOverride) {
|
|
1352
|
+
locals_js_1.locals.set(chatOverrideMessagesKey, undefined);
|
|
1353
|
+
accumulatedUIMessages = [...turnCompleteOverride];
|
|
1354
|
+
accumulatedMessages = await toModelMessages(turnCompleteOverride);
|
|
1355
|
+
}
|
|
716
1356
|
}, {
|
|
717
1357
|
attributes: {
|
|
718
1358
|
[v3_1.SemanticInternalAttributes.STYLE_ICON]: "task-hook-onComplete",
|
|
@@ -726,6 +1366,10 @@ function chatTask(options) {
|
|
|
726
1366
|
"chat.messages.count": accumulatedMessages.length,
|
|
727
1367
|
"chat.response.parts.count": capturedResponseMessage?.parts?.length ?? 0,
|
|
728
1368
|
"chat.new_messages.count": turnNewUIMessages.length,
|
|
1369
|
+
...(turnUsage?.inputTokens != null ? { "gen_ai.usage.input_tokens": turnUsage.inputTokens } : {}),
|
|
1370
|
+
...(turnUsage?.outputTokens != null ? { "gen_ai.usage.output_tokens": turnUsage.outputTokens } : {}),
|
|
1371
|
+
...(turnUsage?.totalTokens != null ? { "gen_ai.usage.total_tokens": turnUsage.totalTokens } : {}),
|
|
1372
|
+
...(cumulativeUsage.totalTokens != null ? { "gen_ai.usage.cumulative_total_tokens": cumulativeUsage.totalTokens } : {}),
|
|
729
1373
|
},
|
|
730
1374
|
});
|
|
731
1375
|
}
|
|
@@ -734,11 +1378,11 @@ function chatTask(options) {
|
|
|
734
1378
|
currentWirePayload = pendingMessages[0];
|
|
735
1379
|
return "continue";
|
|
736
1380
|
}
|
|
737
|
-
// Wait for the next message — stay
|
|
738
|
-
const
|
|
1381
|
+
// Wait for the next message — stay idle briefly, then suspend
|
|
1382
|
+
const effectiveIdleTimeout = metadata_js_1.metadata.get(IDLE_TIMEOUT_METADATA_KEY) ?? idleTimeoutInSeconds;
|
|
739
1383
|
const effectiveTurnTimeout = metadata_js_1.metadata.get(TURN_TIMEOUT_METADATA_KEY) ?? turnTimeout;
|
|
740
|
-
const next = await messagesInput.
|
|
741
|
-
|
|
1384
|
+
const next = await messagesInput.waitWithIdleTimeout({
|
|
1385
|
+
idleTimeoutInSeconds: effectiveIdleTimeout,
|
|
742
1386
|
timeout: effectiveTurnTimeout,
|
|
743
1387
|
spanName: "waiting for next message",
|
|
744
1388
|
});
|
|
@@ -787,7 +1431,7 @@ function chatTask(options) {
|
|
|
787
1431
|
// Runtime configuration helpers
|
|
788
1432
|
// ---------------------------------------------------------------------------
|
|
789
1433
|
const TURN_TIMEOUT_METADATA_KEY = "chat.turnTimeout";
|
|
790
|
-
const
|
|
1434
|
+
const IDLE_TIMEOUT_METADATA_KEY = "chat.idleTimeout";
|
|
791
1435
|
/**
|
|
792
1436
|
* Override the turn timeout for subsequent turns in the current run.
|
|
793
1437
|
*
|
|
@@ -827,24 +1471,24 @@ function setTurnTimeoutInSeconds(seconds) {
|
|
|
827
1471
|
metadata_js_1.metadata.set(TURN_TIMEOUT_METADATA_KEY, `${seconds}s`);
|
|
828
1472
|
}
|
|
829
1473
|
/**
|
|
830
|
-
* Override the
|
|
1474
|
+
* Override the idle timeout for subsequent turns in the current run.
|
|
831
1475
|
*
|
|
832
|
-
* The
|
|
1476
|
+
* The idle timeout controls how long the run stays active (using compute)
|
|
833
1477
|
* after each turn, waiting for the next message. During this window,
|
|
834
1478
|
* responses are instant. After it expires, the run suspends.
|
|
835
1479
|
*
|
|
836
|
-
* @param seconds - Number of seconds to stay
|
|
1480
|
+
* @param seconds - Number of seconds to stay idle (0 to suspend immediately)
|
|
837
1481
|
*
|
|
838
1482
|
* @example
|
|
839
1483
|
* ```ts
|
|
840
1484
|
* run: async ({ messages, signal }) => {
|
|
841
|
-
* chat.
|
|
1485
|
+
* chat.setIdleTimeoutInSeconds(60);
|
|
842
1486
|
* return streamText({ model, messages, abortSignal: signal });
|
|
843
1487
|
* }
|
|
844
1488
|
* ```
|
|
845
1489
|
*/
|
|
846
|
-
function
|
|
847
|
-
metadata_js_1.metadata.set(
|
|
1490
|
+
function setIdleTimeoutInSeconds(seconds) {
|
|
1491
|
+
metadata_js_1.metadata.set(IDLE_TIMEOUT_METADATA_KEY, seconds);
|
|
848
1492
|
}
|
|
849
1493
|
/**
|
|
850
1494
|
* Override the `toUIMessageStream()` options for the current turn.
|
|
@@ -1097,13 +1741,17 @@ async function pipeChatAndCapture(source, options) {
|
|
|
1097
1741
|
class ChatMessageAccumulator {
|
|
1098
1742
|
modelMessages = [];
|
|
1099
1743
|
uiMessages = [];
|
|
1744
|
+
_compaction;
|
|
1745
|
+
constructor(options) {
|
|
1746
|
+
this._compaction = options?.compaction;
|
|
1747
|
+
}
|
|
1100
1748
|
/**
|
|
1101
1749
|
* Add incoming messages from the transport payload.
|
|
1102
1750
|
* Returns the full accumulated model messages for `streamText`.
|
|
1103
1751
|
*/
|
|
1104
1752
|
async addIncoming(messages, trigger, turn) {
|
|
1105
1753
|
const cleaned = messages.map((m) => m.role === "assistant" ? cleanupAbortedParts(m) : m);
|
|
1106
|
-
const model = await (
|
|
1754
|
+
const model = await toModelMessages(cleaned);
|
|
1107
1755
|
if (turn === 0 || trigger === "regenerate-message") {
|
|
1108
1756
|
this.modelMessages = model;
|
|
1109
1757
|
this.uiMessages = [...cleaned];
|
|
@@ -1118,25 +1766,100 @@ class ChatMessageAccumulator {
|
|
|
1118
1766
|
* Add the assistant's response to the accumulator.
|
|
1119
1767
|
* Call after `pipeAndCapture` with the captured response.
|
|
1120
1768
|
*/
|
|
1769
|
+
/**
|
|
1770
|
+
* Replace all accumulated messages (for compaction).
|
|
1771
|
+
* Converts UIMessages to ModelMessages internally.
|
|
1772
|
+
*/
|
|
1773
|
+
async setMessages(uiMessages) {
|
|
1774
|
+
this.uiMessages = [...uiMessages];
|
|
1775
|
+
this.modelMessages = await toModelMessages(uiMessages);
|
|
1776
|
+
}
|
|
1121
1777
|
async addResponse(response) {
|
|
1122
1778
|
if (!response.id) {
|
|
1123
1779
|
response = { ...response, id: (0, ai_1.generateId)() };
|
|
1124
1780
|
}
|
|
1125
1781
|
this.uiMessages.push(response);
|
|
1126
1782
|
try {
|
|
1127
|
-
const msgs = await (
|
|
1783
|
+
const msgs = await toModelMessages([stripProviderMetadata(response)]);
|
|
1128
1784
|
this.modelMessages.push(...msgs);
|
|
1129
1785
|
}
|
|
1130
1786
|
catch {
|
|
1131
1787
|
// Conversion failed — skip model message accumulation for this response
|
|
1132
1788
|
}
|
|
1133
1789
|
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Returns a `prepareStep` function for inner-loop compaction.
|
|
1792
|
+
* Only available when `compaction` was provided to the constructor.
|
|
1793
|
+
* Pass the result to `streamText({ prepareStep: conversation.prepareStep() })`.
|
|
1794
|
+
*/
|
|
1795
|
+
prepareStep() {
|
|
1796
|
+
if (!this._compaction)
|
|
1797
|
+
return undefined;
|
|
1798
|
+
const comp = this._compaction;
|
|
1799
|
+
return async ({ messages, steps }) => {
|
|
1800
|
+
const result = await chatCompact(messages, steps, {
|
|
1801
|
+
shouldCompact: comp.shouldCompact,
|
|
1802
|
+
summarize: (msgs) => comp.summarize({ messages: msgs, source: "inner" }),
|
|
1803
|
+
});
|
|
1804
|
+
return result.type === "skipped" ? undefined : result;
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Run outer-loop compaction if needed. Call after adding the response
|
|
1809
|
+
* and capturing usage. Applies `compactModelMessages` and `compactUIMessages`
|
|
1810
|
+
* callbacks if configured.
|
|
1811
|
+
*
|
|
1812
|
+
* @returns `true` if compaction was performed, `false` otherwise.
|
|
1813
|
+
*/
|
|
1814
|
+
async compactIfNeeded(usage, context) {
|
|
1815
|
+
if (!this._compaction || !usage)
|
|
1816
|
+
return false;
|
|
1817
|
+
const shouldTrigger = await this._compaction.shouldCompact({
|
|
1818
|
+
messages: this.modelMessages,
|
|
1819
|
+
totalTokens: usage.totalTokens,
|
|
1820
|
+
inputTokens: usage.inputTokens,
|
|
1821
|
+
outputTokens: usage.outputTokens,
|
|
1822
|
+
usage,
|
|
1823
|
+
totalUsage: context?.totalUsage,
|
|
1824
|
+
chatId: context?.chatId,
|
|
1825
|
+
turn: context?.turn,
|
|
1826
|
+
clientData: context?.clientData,
|
|
1827
|
+
source: "outer",
|
|
1828
|
+
});
|
|
1829
|
+
if (!shouldTrigger)
|
|
1830
|
+
return false;
|
|
1831
|
+
const summary = await this._compaction.summarize({
|
|
1832
|
+
messages: this.modelMessages,
|
|
1833
|
+
usage,
|
|
1834
|
+
totalUsage: context?.totalUsage,
|
|
1835
|
+
chatId: context?.chatId,
|
|
1836
|
+
turn: context?.turn,
|
|
1837
|
+
clientData: context?.clientData,
|
|
1838
|
+
source: "outer",
|
|
1839
|
+
});
|
|
1840
|
+
const compactEvent = {
|
|
1841
|
+
summary,
|
|
1842
|
+
uiMessages: this.uiMessages,
|
|
1843
|
+
modelMessages: this.modelMessages,
|
|
1844
|
+
chatId: context?.chatId ?? "",
|
|
1845
|
+
turn: context?.turn ?? 0,
|
|
1846
|
+
clientData: context?.clientData,
|
|
1847
|
+
source: "outer",
|
|
1848
|
+
};
|
|
1849
|
+
this.modelMessages = this._compaction.compactModelMessages
|
|
1850
|
+
? await this._compaction.compactModelMessages(compactEvent)
|
|
1851
|
+
: [{ role: "assistant", content: [{ type: "text", text: `[Conversation summary]\n\n${summary}` }] }];
|
|
1852
|
+
if (this._compaction.compactUIMessages) {
|
|
1853
|
+
this.uiMessages = await this._compaction.compactUIMessages(compactEvent);
|
|
1854
|
+
}
|
|
1855
|
+
return true;
|
|
1856
|
+
}
|
|
1134
1857
|
}
|
|
1135
1858
|
/**
|
|
1136
1859
|
* Create a chat session that yields turns as an async iterator.
|
|
1137
1860
|
*
|
|
1138
1861
|
* Handles: preload wait, stop signals, message accumulation, turn-complete
|
|
1139
|
-
* signaling, and
|
|
1862
|
+
* signaling, and idle/suspend between turns. You control: initialization,
|
|
1140
1863
|
* model/tool selection, persistence, and any custom per-turn logic.
|
|
1141
1864
|
*
|
|
1142
1865
|
* @example
|
|
@@ -1164,20 +1887,22 @@ class ChatMessageAccumulator {
|
|
|
1164
1887
|
* ```
|
|
1165
1888
|
*/
|
|
1166
1889
|
function createChatSession(payload, options) {
|
|
1167
|
-
const { signal: runSignal,
|
|
1890
|
+
const { signal: runSignal, idleTimeoutInSeconds = 30, timeout = "1h", maxTurns = 100, compaction: sessionCompaction, } = options;
|
|
1168
1891
|
return {
|
|
1169
1892
|
[Symbol.asyncIterator]() {
|
|
1170
1893
|
let currentPayload = payload;
|
|
1171
1894
|
let turn = -1;
|
|
1172
1895
|
const stop = createStopSignal();
|
|
1173
1896
|
const accumulator = new ChatMessageAccumulator();
|
|
1897
|
+
let previousTurnUsage;
|
|
1898
|
+
let cumulativeUsage = emptyUsage();
|
|
1174
1899
|
return {
|
|
1175
1900
|
async next() {
|
|
1176
1901
|
turn++;
|
|
1177
1902
|
// First turn: handle preload — wait for the first real message
|
|
1178
1903
|
if (turn === 0 && currentPayload.trigger === "preload") {
|
|
1179
|
-
const result = await messagesInput.
|
|
1180
|
-
|
|
1904
|
+
const result = await messagesInput.waitWithIdleTimeout({
|
|
1905
|
+
idleTimeoutInSeconds: currentPayload.idleTimeoutInSeconds ?? idleTimeoutInSeconds,
|
|
1181
1906
|
timeout,
|
|
1182
1907
|
spanName: "waiting for first message",
|
|
1183
1908
|
});
|
|
@@ -1189,8 +1914,8 @@ function createChatSession(payload, options) {
|
|
|
1189
1914
|
}
|
|
1190
1915
|
// Subsequent turns: wait for the next message
|
|
1191
1916
|
if (turn > 0) {
|
|
1192
|
-
const next = await messagesInput.
|
|
1193
|
-
|
|
1917
|
+
const next = await messagesInput.waitWithIdleTimeout({
|
|
1918
|
+
idleTimeoutInSeconds,
|
|
1194
1919
|
timeout,
|
|
1195
1920
|
spanName: "waiting for next message",
|
|
1196
1921
|
});
|
|
@@ -1215,11 +1940,16 @@ function createChatSession(payload, options) {
|
|
|
1215
1940
|
chatId: currentPayload.chatId,
|
|
1216
1941
|
trigger: currentPayload.trigger,
|
|
1217
1942
|
clientData: currentPayload.metadata,
|
|
1218
|
-
messages,
|
|
1219
|
-
uiMessages
|
|
1943
|
+
get messages() { return accumulator.modelMessages; },
|
|
1944
|
+
get uiMessages() { return accumulator.uiMessages; },
|
|
1220
1945
|
signal: combinedSignal,
|
|
1221
1946
|
get stopped() { return stop.signal.aborted && !runSignal.aborted; },
|
|
1222
1947
|
continuation: currentPayload.continuation ?? false,
|
|
1948
|
+
previousTurnUsage,
|
|
1949
|
+
totalUsage: cumulativeUsage,
|
|
1950
|
+
async setMessages(uiMessages) {
|
|
1951
|
+
await accumulator.setMessages(uiMessages);
|
|
1952
|
+
},
|
|
1223
1953
|
async complete(source) {
|
|
1224
1954
|
let response;
|
|
1225
1955
|
try {
|
|
@@ -1244,6 +1974,58 @@ function createChatSession(payload, options) {
|
|
|
1244
1974
|
: response;
|
|
1245
1975
|
await accumulator.addResponse(cleaned);
|
|
1246
1976
|
}
|
|
1977
|
+
// Capture token usage from the streamText result
|
|
1978
|
+
let turnUsage;
|
|
1979
|
+
if (typeof source.totalUsage?.then === "function") {
|
|
1980
|
+
try {
|
|
1981
|
+
const usage = await source.totalUsage;
|
|
1982
|
+
turnUsage = usage;
|
|
1983
|
+
previousTurnUsage = usage;
|
|
1984
|
+
cumulativeUsage = addUsage(cumulativeUsage, usage);
|
|
1985
|
+
}
|
|
1986
|
+
catch { /* non-fatal */ }
|
|
1987
|
+
}
|
|
1988
|
+
// Outer-loop compaction (same logic as chat.task)
|
|
1989
|
+
if (sessionCompaction && turnUsage && !turnObj.stopped) {
|
|
1990
|
+
const shouldTrigger = await sessionCompaction.shouldCompact({
|
|
1991
|
+
messages: accumulator.modelMessages,
|
|
1992
|
+
totalTokens: turnUsage.totalTokens,
|
|
1993
|
+
inputTokens: turnUsage.inputTokens,
|
|
1994
|
+
outputTokens: turnUsage.outputTokens,
|
|
1995
|
+
usage: turnUsage,
|
|
1996
|
+
totalUsage: cumulativeUsage,
|
|
1997
|
+
chatId: currentPayload.chatId,
|
|
1998
|
+
turn,
|
|
1999
|
+
clientData: currentPayload.metadata,
|
|
2000
|
+
source: "outer",
|
|
2001
|
+
});
|
|
2002
|
+
if (shouldTrigger) {
|
|
2003
|
+
const summary = await sessionCompaction.summarize({
|
|
2004
|
+
messages: accumulator.modelMessages,
|
|
2005
|
+
usage: turnUsage,
|
|
2006
|
+
totalUsage: cumulativeUsage,
|
|
2007
|
+
chatId: currentPayload.chatId,
|
|
2008
|
+
turn,
|
|
2009
|
+
clientData: currentPayload.metadata,
|
|
2010
|
+
source: "outer",
|
|
2011
|
+
});
|
|
2012
|
+
const compactEvent = {
|
|
2013
|
+
summary,
|
|
2014
|
+
uiMessages: accumulator.uiMessages,
|
|
2015
|
+
modelMessages: accumulator.modelMessages,
|
|
2016
|
+
chatId: currentPayload.chatId,
|
|
2017
|
+
turn,
|
|
2018
|
+
clientData: currentPayload.metadata,
|
|
2019
|
+
source: "outer",
|
|
2020
|
+
};
|
|
2021
|
+
accumulator.modelMessages = sessionCompaction.compactModelMessages
|
|
2022
|
+
? await sessionCompaction.compactModelMessages(compactEvent)
|
|
2023
|
+
: [{ role: "assistant", content: [{ type: "text", text: `[Conversation summary]\n\n${summary}` }] }];
|
|
2024
|
+
if (sessionCompaction.compactUIMessages) {
|
|
2025
|
+
accumulator.uiMessages = await sessionCompaction.compactUIMessages(compactEvent);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
1247
2029
|
await chatWriteTurnComplete();
|
|
1248
2030
|
return response;
|
|
1249
2031
|
},
|
|
@@ -1457,8 +2239,8 @@ exports.chat = {
|
|
|
1457
2239
|
setTurnTimeout,
|
|
1458
2240
|
/** Override the turn timeout at runtime (seconds). See {@link setTurnTimeoutInSeconds}. */
|
|
1459
2241
|
setTurnTimeoutInSeconds,
|
|
1460
|
-
/** Override the
|
|
1461
|
-
|
|
2242
|
+
/** Override the idle timeout at runtime. See {@link setIdleTimeoutInSeconds}. */
|
|
2243
|
+
setIdleTimeoutInSeconds,
|
|
1462
2244
|
/** Override toUIMessageStream() options for the current turn. See {@link setUIMessageStreamOptions}. */
|
|
1463
2245
|
setUIMessageStreamOptions,
|
|
1464
2246
|
/** Check if the current turn was stopped by the user. See {@link isStopped}. */
|
|
@@ -1481,6 +2263,33 @@ exports.chat = {
|
|
|
1481
2263
|
MessageAccumulator: ChatMessageAccumulator,
|
|
1482
2264
|
/** Create a chat session (async iterator). See {@link createChatSession}. */
|
|
1483
2265
|
createSession: createChatSession,
|
|
2266
|
+
/**
|
|
2267
|
+
* Store and retrieve a resolved prompt for the current run.
|
|
2268
|
+
*
|
|
2269
|
+
* - `chat.prompt.set(resolved)` — store a `ResolvedPrompt` or plain string
|
|
2270
|
+
* - `chat.prompt()` — read the stored prompt (throws if not set)
|
|
2271
|
+
*/
|
|
2272
|
+
prompt: Object.assign(getChatPrompt, { set: setChatPrompt }),
|
|
2273
|
+
/**
|
|
2274
|
+
* Returns an options object ready to spread into `streamText()`.
|
|
2275
|
+
* Reads the stored prompt and returns `{ system, experimental_telemetry, ...config }`.
|
|
2276
|
+
* Returns `{}` if no prompt has been set.
|
|
2277
|
+
*/
|
|
2278
|
+
toStreamTextOptions,
|
|
2279
|
+
/**
|
|
2280
|
+
* Replace the accumulated conversation messages for compaction.
|
|
2281
|
+
* Call from `onTurnStart` or `onTurnComplete`. Takes `UIMessage[]` and
|
|
2282
|
+
* converts to `ModelMessage[]` internally.
|
|
2283
|
+
*/
|
|
2284
|
+
setMessages: setChatMessages,
|
|
2285
|
+
/** Check if it's safe to compact messages (no in-flight tool calls). */
|
|
2286
|
+
isCompactionSafe,
|
|
2287
|
+
/** Returns a `prepareStep` function that handles context compaction automatically. */
|
|
2288
|
+
compactionStep: chatCompactionStep,
|
|
2289
|
+
/** Low-level compaction for use inside a custom `prepareStep`. */
|
|
2290
|
+
compact: chatCompact,
|
|
2291
|
+
/** Read the current compaction state (summary + base message count). */
|
|
2292
|
+
getCompactionState,
|
|
1484
2293
|
};
|
|
1485
2294
|
/**
|
|
1486
2295
|
* Writes a turn-complete control chunk to the chat output stream.
|