@trigger.dev/sdk 0.0.0-prerelease-20260324161542 → 0.0.0-prerelease-20260325143642
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 +125 -4
- package/dist/commonjs/v3/ai.js +315 -35
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-react.d.ts +89 -1
- package/dist/commonjs/v3/chat-react.js +164 -0
- package/dist/commonjs/v3/chat-react.js.map +1 -1
- package/dist/commonjs/v3/chat.d.ts +17 -0
- package/dist/commonjs/v3/chat.js +38 -0
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +125 -4
- package/dist/esm/v3/ai.js +314 -34
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-react.d.ts +89 -1
- package/dist/esm/v3/chat-react.js +164 -1
- package/dist/esm/v3/chat-react.js.map +1 -1
- package/dist/esm/v3/chat.d.ts +17 -0
- package/dist/esm/v3/chat.js +38 -0
- package/dist/esm/v3/chat.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +2 -2
package/dist/commonjs/v3/ai.d.ts
CHANGED
|
@@ -279,6 +279,84 @@ export type ChatTaskCompactionOptions = {
|
|
|
279
279
|
*/
|
|
280
280
|
compactModelMessages?: (event: CompactMessagesEvent) => ModelMessage[] | Promise<ModelMessage[]>;
|
|
281
281
|
};
|
|
282
|
+
/**
|
|
283
|
+
* Event passed to `shouldInject` and `prepareMessages` callbacks.
|
|
284
|
+
*/
|
|
285
|
+
export type PendingMessagesBatchEvent = {
|
|
286
|
+
/** All pending UI messages that arrived during streaming (batch). */
|
|
287
|
+
messages: UIMessage[];
|
|
288
|
+
/** Current model messages in the conversation. */
|
|
289
|
+
modelMessages: ModelMessage[];
|
|
290
|
+
/** Completed steps so far. */
|
|
291
|
+
steps: CompactionStep[];
|
|
292
|
+
/** Current step number (0-indexed). */
|
|
293
|
+
stepNumber: number;
|
|
294
|
+
/** Chat session ID. */
|
|
295
|
+
chatId: string;
|
|
296
|
+
/** Current turn number (0-indexed). */
|
|
297
|
+
turn: number;
|
|
298
|
+
/** Custom data from the frontend. */
|
|
299
|
+
clientData?: unknown;
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Event passed to `onReceived` callback (per-message, as they arrive).
|
|
303
|
+
*/
|
|
304
|
+
export type PendingMessageReceivedEvent = {
|
|
305
|
+
/** The UI message that arrived during streaming. */
|
|
306
|
+
message: UIMessage;
|
|
307
|
+
/** Chat session ID. */
|
|
308
|
+
chatId: string;
|
|
309
|
+
/** Current turn number (0-indexed). */
|
|
310
|
+
turn: number;
|
|
311
|
+
};
|
|
312
|
+
/**
|
|
313
|
+
* Event passed to `onInjected` callback (batch, after injection).
|
|
314
|
+
*/
|
|
315
|
+
export type PendingMessagesInjectedEvent = {
|
|
316
|
+
/** All UI messages that were injected. */
|
|
317
|
+
messages: UIMessage[];
|
|
318
|
+
/** The model messages that were injected. */
|
|
319
|
+
injectedModelMessages: ModelMessage[];
|
|
320
|
+
/** Chat session ID. */
|
|
321
|
+
chatId: string;
|
|
322
|
+
/** Current turn number (0-indexed). */
|
|
323
|
+
turn: number;
|
|
324
|
+
/** Step number where injection occurred. */
|
|
325
|
+
stepNumber: number;
|
|
326
|
+
};
|
|
327
|
+
/**
|
|
328
|
+
* Options for the `pendingMessages` field on `chat.task()`, `chat.createSession()`,
|
|
329
|
+
* or `ChatMessageAccumulator`.
|
|
330
|
+
*
|
|
331
|
+
* Configures how messages that arrive during streaming are handled. When
|
|
332
|
+
* `shouldInject` is provided and returns `true`, the full batch of pending
|
|
333
|
+
* messages is injected between tool-call steps via `prepareStep`.
|
|
334
|
+
* Otherwise, messages queue for the next turn.
|
|
335
|
+
*/
|
|
336
|
+
export type PendingMessagesOptions = {
|
|
337
|
+
/**
|
|
338
|
+
* Decide whether to inject pending messages between tool-call steps.
|
|
339
|
+
* Called once per step boundary with the full batch of pending messages.
|
|
340
|
+
* If absent, no injection happens — messages only queue for the next turn.
|
|
341
|
+
*/
|
|
342
|
+
shouldInject?: (event: PendingMessagesBatchEvent) => boolean | Promise<boolean>;
|
|
343
|
+
/**
|
|
344
|
+
* Transform the batch of pending messages before injection.
|
|
345
|
+
* Return the model messages to inject.
|
|
346
|
+
* Default: convert each UI message via `convertToModelMessages`.
|
|
347
|
+
*/
|
|
348
|
+
prepare?: (event: PendingMessagesBatchEvent) => ModelMessage[] | Promise<ModelMessage[]>;
|
|
349
|
+
/** Called when a message arrives during streaming (per-message). */
|
|
350
|
+
onReceived?: (event: PendingMessageReceivedEvent) => void | Promise<void>;
|
|
351
|
+
/** Called after a batch of messages is injected via `prepareStep`. */
|
|
352
|
+
onInjected?: (event: PendingMessagesInjectedEvent) => void | Promise<void>;
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* The data part type used to signal that pending messages were injected
|
|
356
|
+
* between tool-call steps. The frontend can match on this to render
|
|
357
|
+
* injection points inline in the assistant response.
|
|
358
|
+
*/
|
|
359
|
+
export declare const PENDING_MESSAGE_INJECTED_TYPE: "data-pending-message-injected";
|
|
282
360
|
/**
|
|
283
361
|
* Event passed to the `prepareMessages` hook.
|
|
284
362
|
*/
|
|
@@ -888,7 +966,22 @@ export type ChatTaskOptions<TIdentifier extends string, TClientDataSchema extend
|
|
|
888
966
|
*/
|
|
889
967
|
compaction?: ChatTaskCompactionOptions;
|
|
890
968
|
/**
|
|
891
|
-
*
|
|
969
|
+
* Configure how messages that arrive during streaming are handled.
|
|
970
|
+
*
|
|
971
|
+
* By default, messages queue for the next turn. When `shouldInject` is provided
|
|
972
|
+
* and returns `true`, messages are injected between tool-call steps via
|
|
973
|
+
* `prepareStep` — allowing users to steer the agent mid-execution.
|
|
974
|
+
*
|
|
975
|
+
* @example
|
|
976
|
+
* ```ts
|
|
977
|
+
* pendingMessages: {
|
|
978
|
+
* shouldInject: ({ steps }) => steps.length > 0,
|
|
979
|
+
* onReceived: ({ message }) => logger.info("Steering message received"),
|
|
980
|
+
* },
|
|
981
|
+
* ```
|
|
982
|
+
*/
|
|
983
|
+
pendingMessages?: PendingMessagesOptions;
|
|
984
|
+
/**
|
|
892
985
|
* conversation to your database after each assistant response.
|
|
893
986
|
*
|
|
894
987
|
* @example
|
|
@@ -1249,8 +1342,11 @@ declare class ChatMessageAccumulator {
|
|
|
1249
1342
|
modelMessages: ModelMessage[];
|
|
1250
1343
|
uiMessages: UIMessage[];
|
|
1251
1344
|
private _compaction?;
|
|
1345
|
+
private _pendingMessages?;
|
|
1346
|
+
private _steeringQueue;
|
|
1252
1347
|
constructor(options?: {
|
|
1253
1348
|
compaction?: ChatTaskCompactionOptions;
|
|
1349
|
+
pendingMessages?: PendingMessagesOptions;
|
|
1254
1350
|
});
|
|
1255
1351
|
/**
|
|
1256
1352
|
* Add incoming messages from the transport payload.
|
|
@@ -1268,9 +1364,21 @@ declare class ChatMessageAccumulator {
|
|
|
1268
1364
|
setMessages(uiMessages: UIMessage[]): Promise<void>;
|
|
1269
1365
|
addResponse(response: UIMessage): Promise<void>;
|
|
1270
1366
|
/**
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
|
|
1367
|
+
* Queue a message for injection via `prepareStep`. Call from a
|
|
1368
|
+
* `messagesInput.on()` listener when a message arrives during streaming.
|
|
1369
|
+
*/
|
|
1370
|
+
steer(message: UIMessage, modelMessages?: ModelMessage[]): void;
|
|
1371
|
+
/**
|
|
1372
|
+
* Queue a message for injection, converting to model messages automatically.
|
|
1373
|
+
*/
|
|
1374
|
+
steerAsync(message: UIMessage): Promise<void>;
|
|
1375
|
+
/**
|
|
1376
|
+
* Get and clear unconsumed steering messages.
|
|
1377
|
+
*/
|
|
1378
|
+
drainSteering(): UIMessage[];
|
|
1379
|
+
/**
|
|
1380
|
+
* Returns a `prepareStep` function that handles both compaction and
|
|
1381
|
+
* pending message injection. Pass to `streamText({ prepareStep: conversation.prepareStep() })`.
|
|
1274
1382
|
*/
|
|
1275
1383
|
prepareStep(): ((args: {
|
|
1276
1384
|
messages: ModelMessage[];
|
|
@@ -1303,6 +1411,8 @@ export type ChatSessionOptions = {
|
|
|
1303
1411
|
maxTurns?: number;
|
|
1304
1412
|
/** Automatic context compaction — same options as `chat.task({ compaction })`. */
|
|
1305
1413
|
compaction?: ChatTaskCompactionOptions;
|
|
1414
|
+
/** Configure mid-execution message injection — same options as `chat.task({ pendingMessages })`. */
|
|
1415
|
+
pendingMessages?: PendingMessagesOptions;
|
|
1306
1416
|
};
|
|
1307
1417
|
export type ChatTurn = {
|
|
1308
1418
|
/** Turn number (0-indexed). */
|
|
@@ -1348,6 +1458,17 @@ export type ChatTurn = {
|
|
|
1348
1458
|
* Use with `chat.pipeAndCapture` when you need control between pipe and done.
|
|
1349
1459
|
*/
|
|
1350
1460
|
addResponse(response: UIMessage): Promise<void>;
|
|
1461
|
+
/**
|
|
1462
|
+
* Returns a `prepareStep` function that handles both compaction and
|
|
1463
|
+
* pending message injection. Pass to `streamText({ prepareStep: turn.prepareStep() })`.
|
|
1464
|
+
* Only needed when not using `chat.toStreamTextOptions()` (which auto-injects it).
|
|
1465
|
+
*/
|
|
1466
|
+
prepareStep(): ((args: {
|
|
1467
|
+
messages: ModelMessage[];
|
|
1468
|
+
steps: CompactionStep[];
|
|
1469
|
+
}) => Promise<{
|
|
1470
|
+
messages: ModelMessage[];
|
|
1471
|
+
} | undefined>) | undefined;
|
|
1351
1472
|
};
|
|
1352
1473
|
/**
|
|
1353
1474
|
* Create a chat session that yields turns as an async iterator.
|
package/dist/commonjs/v3/ai.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.chat = exports.CHAT_STOP_STREAM_ID = exports.CHAT_MESSAGES_STREAM_ID = exports.CHAT_STREAM_KEY = exports.ai = void 0;
|
|
3
|
+
exports.chat = exports.PENDING_MESSAGE_INJECTED_TYPE = exports.CHAT_STOP_STREAM_ID = exports.CHAT_MESSAGES_STREAM_ID = exports.CHAT_STREAM_KEY = exports.ai = void 0;
|
|
4
4
|
const v3_1 = require("@trigger.dev/core/v3");
|
|
5
5
|
const ai_1 = require("ai");
|
|
6
6
|
const api_1 = require("@opentelemetry/api");
|
|
@@ -281,6 +281,18 @@ const chatOnCompactedKey = locals_js_1.locals.create("chat.onCompacted");
|
|
|
281
281
|
const chatPrepareMessagesKey = locals_js_1.locals.create("chat.prepareMessages");
|
|
282
282
|
/** @internal */
|
|
283
283
|
const chatTaskCompactionKey = locals_js_1.locals.create("chat.taskCompaction");
|
|
284
|
+
/**
|
|
285
|
+
* The data part type used to signal that pending messages were injected
|
|
286
|
+
* between tool-call steps. The frontend can match on this to render
|
|
287
|
+
* injection points inline in the assistant response.
|
|
288
|
+
*/
|
|
289
|
+
exports.PENDING_MESSAGE_INJECTED_TYPE = "data-pending-message-injected";
|
|
290
|
+
/** @internal */
|
|
291
|
+
const chatPendingMessagesKey = locals_js_1.locals.create("chat.pendingMessages");
|
|
292
|
+
/** @internal */
|
|
293
|
+
const chatSteeringQueueKey = locals_js_1.locals.create("chat.steeringQueue");
|
|
294
|
+
/** @internal — IDs of messages that were successfully injected via prepareStep */
|
|
295
|
+
const chatInjectedMessageIdsKey = locals_js_1.locals.create("chat.injectedMessageIds");
|
|
284
296
|
/**
|
|
285
297
|
* Check that no tool calls are in-flight in a step's content.
|
|
286
298
|
* Used before compaction to avoid losing tool state mid-execution.
|
|
@@ -514,6 +526,111 @@ function chatCompactionStep(options) {
|
|
|
514
526
|
};
|
|
515
527
|
}
|
|
516
528
|
// ---------------------------------------------------------------------------
|
|
529
|
+
// Steering queue drain — shared by toStreamTextOptions, session, accumulator
|
|
530
|
+
// ---------------------------------------------------------------------------
|
|
531
|
+
/**
|
|
532
|
+
* Drain the steering queue as a batch. Calls `shouldInject` once with all
|
|
533
|
+
* pending messages. If it returns true, calls `prepareMessages` once to
|
|
534
|
+
* transform the batch, then clears the queue.
|
|
535
|
+
* Returns the model messages to inject (empty if none).
|
|
536
|
+
* @internal
|
|
537
|
+
*/
|
|
538
|
+
async function drainSteeringQueue(config, messages, steps, queueOverride) {
|
|
539
|
+
const queue = queueOverride ?? locals_js_1.locals.get(chatSteeringQueueKey);
|
|
540
|
+
if (!queue || queue.length === 0)
|
|
541
|
+
return [];
|
|
542
|
+
const ctx = locals_js_1.locals.get(chatTurnContextKey);
|
|
543
|
+
const stepNumber = steps.length - 1;
|
|
544
|
+
const uiMessages = queue.map((e) => e.uiMessage);
|
|
545
|
+
const batchEvent = {
|
|
546
|
+
messages: uiMessages,
|
|
547
|
+
modelMessages: messages,
|
|
548
|
+
steps,
|
|
549
|
+
stepNumber,
|
|
550
|
+
chatId: ctx?.chatId ?? "",
|
|
551
|
+
turn: ctx?.turn ?? 0,
|
|
552
|
+
clientData: ctx?.clientData,
|
|
553
|
+
};
|
|
554
|
+
// Call shouldInject once for the whole batch
|
|
555
|
+
const shouldInject = config.shouldInject
|
|
556
|
+
? await config.shouldInject(batchEvent)
|
|
557
|
+
: false;
|
|
558
|
+
if (!shouldInject)
|
|
559
|
+
return [];
|
|
560
|
+
// Extract message texts for span attributes
|
|
561
|
+
const messageTexts = uiMessages.map((m) => (m.parts ?? []).filter((p) => p.type === "text").map((p) => p.text).join("") || "");
|
|
562
|
+
const previewText = messageTexts.length === 1
|
|
563
|
+
? messageTexts[0].slice(0, 80)
|
|
564
|
+
: `${queue.length} messages`;
|
|
565
|
+
return tracer_js_1.tracer.startActiveSpan("pending message injected", async () => {
|
|
566
|
+
// Transform the batch — default: concatenate all pre-converted model messages
|
|
567
|
+
const injected = config.prepare
|
|
568
|
+
? await config.prepare(batchEvent)
|
|
569
|
+
: queue.flatMap((e) => e.modelMessages);
|
|
570
|
+
// Clear the queue and record injected IDs
|
|
571
|
+
queue.length = 0;
|
|
572
|
+
const injectedIds = locals_js_1.locals.get(chatInjectedMessageIdsKey);
|
|
573
|
+
if (injectedIds) {
|
|
574
|
+
for (const m of uiMessages)
|
|
575
|
+
injectedIds.add(m.id);
|
|
576
|
+
}
|
|
577
|
+
// Write injection confirmation chunk to the stream so the frontend
|
|
578
|
+
// knows which messages were injected and where in the response.
|
|
579
|
+
if (injected.length > 0) {
|
|
580
|
+
try {
|
|
581
|
+
const { waitUntilComplete } = streams_js_1.streams.writer(exports.CHAT_STREAM_KEY, {
|
|
582
|
+
collapsed: true,
|
|
583
|
+
execute: ({ write }) => {
|
|
584
|
+
write({
|
|
585
|
+
type: exports.PENDING_MESSAGE_INJECTED_TYPE,
|
|
586
|
+
id: (0, ai_1.generateId)(),
|
|
587
|
+
data: {
|
|
588
|
+
messageIds: uiMessages.map((m) => m.id),
|
|
589
|
+
messages: uiMessages.map((m, idx) => ({
|
|
590
|
+
id: m.id,
|
|
591
|
+
text: messageTexts[idx] ?? "",
|
|
592
|
+
})),
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
await waitUntilComplete();
|
|
598
|
+
}
|
|
599
|
+
catch { /* non-fatal — stream write failed */ }
|
|
600
|
+
}
|
|
601
|
+
// Fire onInjected callback
|
|
602
|
+
if (config.onInjected && injected.length > 0) {
|
|
603
|
+
try {
|
|
604
|
+
await config.onInjected({
|
|
605
|
+
messages: uiMessages,
|
|
606
|
+
injectedModelMessages: injected,
|
|
607
|
+
chatId: ctx?.chatId ?? "",
|
|
608
|
+
turn: ctx?.turn ?? 0,
|
|
609
|
+
stepNumber,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
catch { /* non-fatal */ }
|
|
613
|
+
}
|
|
614
|
+
return injected;
|
|
615
|
+
}, {
|
|
616
|
+
attributes: {
|
|
617
|
+
[v3_1.SemanticInternalAttributes.STYLE_ICON]: "tabler-message-forward",
|
|
618
|
+
"pending.message_count": uiMessages.length,
|
|
619
|
+
"pending.step_number": stepNumber,
|
|
620
|
+
"pending.messages": messageTexts,
|
|
621
|
+
...(ctx?.chatId ? { "pending.chat_id": ctx.chatId } : {}),
|
|
622
|
+
...(ctx?.turn != null ? { "pending.turn": ctx.turn } : {}),
|
|
623
|
+
...(0, v3_1.accessoryAttributes)({
|
|
624
|
+
items: [
|
|
625
|
+
{ text: `${uiMessages.length} message${uiMessages.length === 1 ? "" : "s"}`, variant: "normal" },
|
|
626
|
+
{ text: `between steps ${stepNumber} and ${stepNumber + 1}`, variant: "normal" },
|
|
627
|
+
],
|
|
628
|
+
style: "codepath",
|
|
629
|
+
}),
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
// ---------------------------------------------------------------------------
|
|
517
634
|
// chat.isCompactionSafe — check if it's safe to compact messages
|
|
518
635
|
// ---------------------------------------------------------------------------
|
|
519
636
|
/**
|
|
@@ -600,29 +717,42 @@ function toStreamTextOptions(options) {
|
|
|
600
717
|
// Add telemetry (forward additional metadata from caller)
|
|
601
718
|
const telemetry = prompt.toAISDKTelemetry(options?.telemetry);
|
|
602
719
|
Object.assign(result, telemetry);
|
|
603
|
-
// Auto-inject prepareStep when
|
|
604
|
-
// We build a custom prepareStep instead of using chatCompactionStep so we
|
|
605
|
-
// can pass the enriched SummarizeEvent to taskCompaction.summarize.
|
|
720
|
+
// Auto-inject prepareStep when compaction or pendingMessages is configured.
|
|
606
721
|
const taskCompaction = locals_js_1.locals.get(chatTaskCompactionKey);
|
|
607
|
-
|
|
722
|
+
const taskPendingMessages = locals_js_1.locals.get(chatPendingMessagesKey);
|
|
723
|
+
if (taskCompaction || taskPendingMessages) {
|
|
608
724
|
result.prepareStep = async ({ messages, steps }) => {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
725
|
+
let resultMessages;
|
|
726
|
+
// 1. Compaction
|
|
727
|
+
if (taskCompaction) {
|
|
728
|
+
const compactResult = await chatCompact(messages, steps, {
|
|
729
|
+
shouldCompact: taskCompaction.shouldCompact,
|
|
730
|
+
summarize: (msgs) => {
|
|
731
|
+
const ctx = locals_js_1.locals.get(chatTurnContextKey);
|
|
732
|
+
const lastStep = steps.at(-1);
|
|
733
|
+
return taskCompaction.summarize({
|
|
734
|
+
messages: msgs,
|
|
735
|
+
usage: lastStep?.usage,
|
|
736
|
+
source: "inner",
|
|
737
|
+
stepNumber: steps.length - 1,
|
|
738
|
+
chatId: ctx?.chatId,
|
|
739
|
+
turn: ctx?.turn,
|
|
740
|
+
clientData: ctx?.clientData,
|
|
741
|
+
});
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
if (compactResult.type !== "skipped") {
|
|
745
|
+
resultMessages = compactResult.messages;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// 2. Pending message injection (steering)
|
|
749
|
+
if (taskPendingMessages) {
|
|
750
|
+
const injected = await drainSteeringQueue(taskPendingMessages, resultMessages ?? messages, steps);
|
|
751
|
+
if (injected.length > 0) {
|
|
752
|
+
resultMessages = [...(resultMessages ?? messages), ...injected];
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return resultMessages ? { messages: resultMessages } : undefined;
|
|
626
756
|
};
|
|
627
757
|
}
|
|
628
758
|
return result;
|
|
@@ -733,7 +863,7 @@ async function pipeChat(source, options) {
|
|
|
733
863
|
* ```
|
|
734
864
|
*/
|
|
735
865
|
function chatTask(options) {
|
|
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;
|
|
866
|
+
const { run: userRun, clientDataSchema, onPreload, onChatStart, onTurnStart, onBeforeTurnComplete, onCompacted, compaction, pendingMessages: pendingMessagesConfig, prepareMessages, onTurnComplete, maxTurns = 100, turnTimeout = "1h", idleTimeoutInSeconds = 30, chatAccessTokenTTL = "1h", preloadIdleTimeoutInSeconds, preloadTimeout, uiMessageStreamOptions, ...restOptions } = options;
|
|
737
867
|
const parseClientData = clientDataSchema
|
|
738
868
|
? (0, v3_1.getSchemaParseFn)(clientDataSchema)
|
|
739
869
|
: undefined;
|
|
@@ -759,6 +889,9 @@ function chatTask(options) {
|
|
|
759
889
|
if (compaction) {
|
|
760
890
|
locals_js_1.locals.set(chatTaskCompactionKey, compaction);
|
|
761
891
|
}
|
|
892
|
+
if (pendingMessagesConfig) {
|
|
893
|
+
locals_js_1.locals.set(chatPendingMessagesKey, pendingMessagesConfig);
|
|
894
|
+
}
|
|
762
895
|
let currentWirePayload = payload;
|
|
763
896
|
const continuation = payload.continuation ?? false;
|
|
764
897
|
const previousRunId = payload.previousRunId;
|
|
@@ -876,6 +1009,8 @@ function chatTask(options) {
|
|
|
876
1009
|
locals_js_1.locals.set(chatPipeCountKey, 0);
|
|
877
1010
|
locals_js_1.locals.set(chatDeferKey, new Set());
|
|
878
1011
|
locals_js_1.locals.set(chatCompactionStateKey, undefined);
|
|
1012
|
+
locals_js_1.locals.set(chatSteeringQueueKey, []);
|
|
1013
|
+
locals_js_1.locals.set(chatInjectedMessageIdsKey, new Set());
|
|
879
1014
|
// Store chat context for auto-detection by ai.tool subtasks
|
|
880
1015
|
locals_js_1.locals.set(chatTurnContextKey, {
|
|
881
1016
|
chatId: currentWirePayload.chatId,
|
|
@@ -893,7 +1028,39 @@ function chatTask(options) {
|
|
|
893
1028
|
const combinedSignal = AbortSignal.any([runSignal, stopController.signal]);
|
|
894
1029
|
// Buffer messages that arrive during streaming
|
|
895
1030
|
const pendingMessages = [];
|
|
896
|
-
const
|
|
1031
|
+
const pmConfig = locals_js_1.locals.get(chatPendingMessagesKey);
|
|
1032
|
+
const msgSub = messagesInput.on(async (msg) => {
|
|
1033
|
+
// If pendingMessages is configured, route to the steering queue
|
|
1034
|
+
// instead of the wire buffer. The frontend handles re-sending
|
|
1035
|
+
// non-injected messages via sendMessage on turn complete.
|
|
1036
|
+
if (pmConfig) {
|
|
1037
|
+
const lastUIMessage = msg.messages?.[msg.messages.length - 1];
|
|
1038
|
+
if (lastUIMessage) {
|
|
1039
|
+
if (pmConfig.onReceived) {
|
|
1040
|
+
try {
|
|
1041
|
+
await pmConfig.onReceived({
|
|
1042
|
+
message: lastUIMessage,
|
|
1043
|
+
chatId: currentWirePayload.chatId,
|
|
1044
|
+
turn,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
catch { /* non-fatal */ }
|
|
1048
|
+
}
|
|
1049
|
+
try {
|
|
1050
|
+
const queue = locals_js_1.locals.get(chatSteeringQueueKey) ?? [];
|
|
1051
|
+
// Deduplicate by message ID — guards against double-sends
|
|
1052
|
+
if (lastUIMessage.id && queue.some((e) => e.uiMessage.id === lastUIMessage.id)) {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const modelMsgs = await toModelMessages([lastUIMessage]);
|
|
1056
|
+
queue.push({ uiMessage: lastUIMessage, modelMessages: modelMsgs });
|
|
1057
|
+
locals_js_1.locals.set(chatSteeringQueueKey, queue);
|
|
1058
|
+
}
|
|
1059
|
+
catch { /* conversion failed — skip steering queue */ }
|
|
1060
|
+
}
|
|
1061
|
+
return; // Don't add to wire buffer — frontend handles non-injected case
|
|
1062
|
+
}
|
|
1063
|
+
// No pendingMessages config — standard wire buffer for next turn
|
|
897
1064
|
pendingMessages.push(msg);
|
|
898
1065
|
});
|
|
899
1066
|
// Clean up any incomplete tool parts in the incoming history.
|
|
@@ -1373,7 +1540,8 @@ function chatTask(options) {
|
|
|
1373
1540
|
},
|
|
1374
1541
|
});
|
|
1375
1542
|
}
|
|
1376
|
-
// If messages arrived during streaming
|
|
1543
|
+
// If messages arrived during streaming (without pendingMessages config),
|
|
1544
|
+
// use the first one immediately as the next turn.
|
|
1377
1545
|
if (pendingMessages.length > 0) {
|
|
1378
1546
|
currentWirePayload = pendingMessages[0];
|
|
1379
1547
|
return "continue";
|
|
@@ -1742,8 +1910,11 @@ class ChatMessageAccumulator {
|
|
|
1742
1910
|
modelMessages = [];
|
|
1743
1911
|
uiMessages = [];
|
|
1744
1912
|
_compaction;
|
|
1913
|
+
_pendingMessages;
|
|
1914
|
+
_steeringQueue = [];
|
|
1745
1915
|
constructor(options) {
|
|
1746
1916
|
this._compaction = options?.compaction;
|
|
1917
|
+
this._pendingMessages = options?.pendingMessages;
|
|
1747
1918
|
}
|
|
1748
1919
|
/**
|
|
1749
1920
|
* Add incoming messages from the transport payload.
|
|
@@ -1788,20 +1959,63 @@ class ChatMessageAccumulator {
|
|
|
1788
1959
|
}
|
|
1789
1960
|
}
|
|
1790
1961
|
/**
|
|
1791
|
-
*
|
|
1792
|
-
*
|
|
1793
|
-
|
|
1962
|
+
* Queue a message for injection via `prepareStep`. Call from a
|
|
1963
|
+
* `messagesInput.on()` listener when a message arrives during streaming.
|
|
1964
|
+
*/
|
|
1965
|
+
steer(message, modelMessages) {
|
|
1966
|
+
if (modelMessages) {
|
|
1967
|
+
this._steeringQueue.push({ uiMessage: message, modelMessages });
|
|
1968
|
+
}
|
|
1969
|
+
else {
|
|
1970
|
+
// Defer conversion — will be done in prepareStep if needed
|
|
1971
|
+
this._steeringQueue.push({ uiMessage: message, modelMessages: [] });
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Queue a message for injection, converting to model messages automatically.
|
|
1976
|
+
*/
|
|
1977
|
+
async steerAsync(message) {
|
|
1978
|
+
const modelMsgs = await toModelMessages([message]);
|
|
1979
|
+
this._steeringQueue.push({ uiMessage: message, modelMessages: modelMsgs });
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Get and clear unconsumed steering messages.
|
|
1983
|
+
*/
|
|
1984
|
+
drainSteering() {
|
|
1985
|
+
const result = this._steeringQueue.map((e) => e.uiMessage);
|
|
1986
|
+
this._steeringQueue = [];
|
|
1987
|
+
return result;
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Returns a `prepareStep` function that handles both compaction and
|
|
1991
|
+
* pending message injection. Pass to `streamText({ prepareStep: conversation.prepareStep() })`.
|
|
1794
1992
|
*/
|
|
1795
1993
|
prepareStep() {
|
|
1796
|
-
if (!this._compaction)
|
|
1994
|
+
if (!this._compaction && !this._pendingMessages)
|
|
1797
1995
|
return undefined;
|
|
1798
1996
|
const comp = this._compaction;
|
|
1997
|
+
const pm = this._pendingMessages;
|
|
1998
|
+
const queue = this._steeringQueue;
|
|
1799
1999
|
return async ({ messages, steps }) => {
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2000
|
+
let resultMessages;
|
|
2001
|
+
// 1. Compaction
|
|
2002
|
+
if (comp) {
|
|
2003
|
+
const result = await chatCompact(messages, steps, {
|
|
2004
|
+
shouldCompact: comp.shouldCompact,
|
|
2005
|
+
summarize: (msgs) => comp.summarize({ messages: msgs, source: "inner" }),
|
|
2006
|
+
});
|
|
2007
|
+
if (result.type !== "skipped") {
|
|
2008
|
+
resultMessages = result.messages;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
// 2. Pending message injection
|
|
2012
|
+
if (pm && queue.length > 0) {
|
|
2013
|
+
const injected = await drainSteeringQueue(pm, resultMessages ?? messages, steps, queue);
|
|
2014
|
+
if (injected.length > 0) {
|
|
2015
|
+
resultMessages = [...(resultMessages ?? messages), ...injected];
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
return resultMessages ? { messages: resultMessages } : undefined;
|
|
1805
2019
|
};
|
|
1806
2020
|
}
|
|
1807
2021
|
/**
|
|
@@ -1887,7 +2101,7 @@ class ChatMessageAccumulator {
|
|
|
1887
2101
|
* ```
|
|
1888
2102
|
*/
|
|
1889
2103
|
function createChatSession(payload, options) {
|
|
1890
|
-
const { signal: runSignal, idleTimeoutInSeconds = 30, timeout = "1h", maxTurns = 100, compaction: sessionCompaction, } = options;
|
|
2104
|
+
const { signal: runSignal, idleTimeoutInSeconds = 30, timeout = "1h", maxTurns = 100, compaction: sessionCompaction, pendingMessages: sessionPendingMessages, } = options;
|
|
1891
2105
|
return {
|
|
1892
2106
|
[Symbol.asyncIterator]() {
|
|
1893
2107
|
let currentPayload = payload;
|
|
@@ -1932,6 +2146,44 @@ function createChatSession(payload, options) {
|
|
|
1932
2146
|
}
|
|
1933
2147
|
// Reset stop signal for this turn
|
|
1934
2148
|
stop.reset();
|
|
2149
|
+
// Set up steering queue and pending messages config in locals
|
|
2150
|
+
// so toStreamTextOptions() auto-injects prepareStep for steering
|
|
2151
|
+
const turnSteeringQueue = [];
|
|
2152
|
+
locals_js_1.locals.set(chatSteeringQueueKey, turnSteeringQueue);
|
|
2153
|
+
if (sessionPendingMessages) {
|
|
2154
|
+
locals_js_1.locals.set(chatPendingMessagesKey, sessionPendingMessages);
|
|
2155
|
+
}
|
|
2156
|
+
locals_js_1.locals.set(chatTurnContextKey, {
|
|
2157
|
+
chatId: currentPayload.chatId,
|
|
2158
|
+
turn,
|
|
2159
|
+
continuation: currentPayload.continuation ?? false,
|
|
2160
|
+
clientData: currentPayload.metadata,
|
|
2161
|
+
});
|
|
2162
|
+
// Listen for messages during streaming (steering + next-turn buffer)
|
|
2163
|
+
const sessionPendingWire = [];
|
|
2164
|
+
const sessionMsgSub = messagesInput.on(async (msg) => {
|
|
2165
|
+
sessionPendingWire.push(msg);
|
|
2166
|
+
if (sessionPendingMessages) {
|
|
2167
|
+
const lastUIMessage = msg.messages?.[msg.messages.length - 1];
|
|
2168
|
+
if (lastUIMessage) {
|
|
2169
|
+
if (sessionPendingMessages.onReceived) {
|
|
2170
|
+
try {
|
|
2171
|
+
await sessionPendingMessages.onReceived({
|
|
2172
|
+
message: lastUIMessage,
|
|
2173
|
+
chatId: currentPayload.chatId,
|
|
2174
|
+
turn,
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
catch { /* non-fatal */ }
|
|
2178
|
+
}
|
|
2179
|
+
try {
|
|
2180
|
+
const modelMsgs = await toModelMessages([lastUIMessage]);
|
|
2181
|
+
turnSteeringQueue.push({ uiMessage: lastUIMessage, modelMessages: modelMsgs });
|
|
2182
|
+
}
|
|
2183
|
+
catch { /* non-fatal */ }
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
1935
2187
|
// Accumulate messages
|
|
1936
2188
|
const messages = await accumulator.addIncoming(currentPayload.messages, currentPayload.trigger, turn);
|
|
1937
2189
|
const combinedSignal = AbortSignal.any([runSignal, stop.signal]);
|
|
@@ -1959,6 +2211,7 @@ function createChatSession(payload, options) {
|
|
|
1959
2211
|
if (error instanceof Error && error.name === "AbortError") {
|
|
1960
2212
|
if (runSignal.aborted) {
|
|
1961
2213
|
// Full cancel — don't accumulate
|
|
2214
|
+
sessionMsgSub.off();
|
|
1962
2215
|
await chatWriteTurnComplete();
|
|
1963
2216
|
return undefined;
|
|
1964
2217
|
}
|
|
@@ -2026,6 +2279,7 @@ function createChatSession(payload, options) {
|
|
|
2026
2279
|
}
|
|
2027
2280
|
}
|
|
2028
2281
|
}
|
|
2282
|
+
sessionMsgSub.off();
|
|
2029
2283
|
await chatWriteTurnComplete();
|
|
2030
2284
|
return response;
|
|
2031
2285
|
},
|
|
@@ -2033,8 +2287,34 @@ function createChatSession(payload, options) {
|
|
|
2033
2287
|
await accumulator.addResponse(response);
|
|
2034
2288
|
},
|
|
2035
2289
|
async done() {
|
|
2290
|
+
sessionMsgSub.off();
|
|
2036
2291
|
await chatWriteTurnComplete();
|
|
2037
2292
|
},
|
|
2293
|
+
prepareStep() {
|
|
2294
|
+
const hasCompaction = !!sessionCompaction;
|
|
2295
|
+
const hasPending = !!sessionPendingMessages;
|
|
2296
|
+
if (!hasCompaction && !hasPending)
|
|
2297
|
+
return undefined;
|
|
2298
|
+
return async ({ messages: stepMsgs, steps }) => {
|
|
2299
|
+
let resultMessages;
|
|
2300
|
+
if (sessionCompaction) {
|
|
2301
|
+
const compactResult = await chatCompact(stepMsgs, steps, {
|
|
2302
|
+
shouldCompact: sessionCompaction.shouldCompact,
|
|
2303
|
+
summarize: (msgs) => sessionCompaction.summarize({ messages: msgs, source: "inner" }),
|
|
2304
|
+
});
|
|
2305
|
+
if (compactResult.type !== "skipped") {
|
|
2306
|
+
resultMessages = compactResult.messages;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
if (sessionPendingMessages) {
|
|
2310
|
+
const injected = await drainSteeringQueue(sessionPendingMessages, resultMessages ?? stepMsgs, steps, turnSteeringQueue);
|
|
2311
|
+
if (injected.length > 0) {
|
|
2312
|
+
resultMessages = [...(resultMessages ?? stepMsgs), ...injected];
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
return resultMessages ? { messages: resultMessages } : undefined;
|
|
2316
|
+
};
|
|
2317
|
+
},
|
|
2038
2318
|
};
|
|
2039
2319
|
return { done: false, value: turnObj };
|
|
2040
2320
|
},
|