@trigger.dev/sdk 4.5.0-rc.0 → 4.5.0-rc.2
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-shared.d.ts +70 -0
- package/dist/commonjs/v3/ai-shared.js +144 -0
- package/dist/commonjs/v3/ai-shared.js.map +1 -1
- package/dist/commonjs/v3/ai.d.ts +21 -5
- package/dist/commonjs/v3/ai.js +153 -17
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/auth.d.ts +1 -0
- package/dist/commonjs/v3/auth.js +1 -0
- package/dist/commonjs/v3/auth.js.map +1 -1
- package/dist/commonjs/v3/chat-client.js +8 -3
- package/dist/commonjs/v3/chat-client.js.map +1 -1
- package/dist/commonjs/v3/chat.js +10 -2
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/v3/index.d.ts +1 -0
- package/dist/commonjs/v3/index.js +3 -1
- package/dist/commonjs/v3/index.js.map +1 -1
- package/dist/commonjs/v3/shared.js +13 -7
- package/dist/commonjs/v3/shared.js.map +1 -1
- package/dist/commonjs/v3/triggerClient.d.ts +45 -0
- package/dist/commonjs/v3/triggerClient.js +104 -0
- package/dist/commonjs/v3/triggerClient.js.map +1 -0
- package/dist/commonjs/v3/triggerClient.test.d.ts +1 -0
- package/dist/commonjs/v3/triggerClient.test.js +226 -0
- package/dist/commonjs/v3/triggerClient.test.js.map +1 -0
- package/dist/commonjs/v3/triggerClient.types.test.d.ts +1 -0
- package/dist/commonjs/v3/triggerClient.types.test.js +113 -0
- package/dist/commonjs/v3/triggerClient.types.test.js.map +1 -0
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai-shared.d.ts +70 -0
- package/dist/esm/v3/ai-shared.js +142 -0
- package/dist/esm/v3/ai-shared.js.map +1 -1
- package/dist/esm/v3/ai.d.ts +21 -5
- package/dist/esm/v3/ai.js +152 -17
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/auth.d.ts +1 -0
- package/dist/esm/v3/auth.js +1 -0
- package/dist/esm/v3/auth.js.map +1 -1
- package/dist/esm/v3/chat-client.js +8 -3
- package/dist/esm/v3/chat-client.js.map +1 -1
- package/dist/esm/v3/chat.js +10 -2
- package/dist/esm/v3/chat.js.map +1 -1
- 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/shared.js +14 -8
- package/dist/esm/v3/shared.js.map +1 -1
- package/dist/esm/v3/triggerClient.d.ts +45 -0
- package/dist/esm/v3/triggerClient.js +77 -0
- package/dist/esm/v3/triggerClient.js.map +1 -0
- package/dist/esm/v3/triggerClient.test.d.ts +1 -0
- package/dist/esm/v3/triggerClient.test.js +224 -0
- package/dist/esm/v3/triggerClient.test.js.map +1 -0
- package/dist/esm/v3/triggerClient.types.test.d.ts +1 -0
- package/dist/esm/v3/triggerClient.types.test.js +88 -0
- package/dist/esm/v3/triggerClient.types.test.js.map +1 -0
- package/dist/esm/version.js +1 -1
- package/package.json +3 -2
package/dist/esm/v3/ai.d.ts
CHANGED
|
@@ -446,7 +446,7 @@ export type PendingMessagesOptions<TUIM extends UIMessage = UIMessage> = {
|
|
|
446
446
|
* between tool-call steps. The frontend can match on this to render
|
|
447
447
|
* injection points inline in the assistant response.
|
|
448
448
|
*/
|
|
449
|
-
export { PENDING_MESSAGE_INJECTED_TYPE } from "./ai-shared.js";
|
|
449
|
+
export { PENDING_MESSAGE_INJECTED_TYPE, upsertIncomingMessage } from "./ai-shared.js";
|
|
450
450
|
/**
|
|
451
451
|
* Event passed to the `prepareMessages` hook.
|
|
452
452
|
*/
|
|
@@ -1085,7 +1085,14 @@ export type HydrateMessagesEvent<TClientData = unknown, TUIM extends UIMessage =
|
|
|
1085
1085
|
* Event passed to the `onValidateMessages` callback.
|
|
1086
1086
|
*/
|
|
1087
1087
|
export type ValidateMessagesEvent<TUIM extends UIMessage = UIMessage> = {
|
|
1088
|
-
/**
|
|
1088
|
+
/**
|
|
1089
|
+
* The incoming UI messages for this turn (after cleanup of aborted tool parts).
|
|
1090
|
+
*
|
|
1091
|
+
* For HITL continuations the assistant entry is slim — `state` + `output` /
|
|
1092
|
+
* `errorText` / `approval` only, no `input` or other parts. Don't pass the
|
|
1093
|
+
* full `messages` array to `validateUIMessages` from `ai`; filter to user
|
|
1094
|
+
* messages (or your own subset) first.
|
|
1095
|
+
*/
|
|
1089
1096
|
messages: TUIM[];
|
|
1090
1097
|
/** The unique identifier for the chat session. */
|
|
1091
1098
|
chatId: string;
|
|
@@ -1534,8 +1541,13 @@ export type ChatAgentOptions<TIdentifier extends string, TClientDataSchema exten
|
|
|
1534
1541
|
*
|
|
1535
1542
|
* Return the validated messages array. Throw to abort the turn with an error.
|
|
1536
1543
|
*
|
|
1537
|
-
* This is the right place to call the AI SDK's `validateUIMessages`
|
|
1538
|
-
*
|
|
1544
|
+
* This is the right place to call the AI SDK's `validateUIMessages` on fresh
|
|
1545
|
+
* user input. For HITL continuations (`addToolOutput` /
|
|
1546
|
+
* `addToolApproveResponse`), the wire carries a slim assistant message — only
|
|
1547
|
+
* the resolved tool parts, with `state` + `output` / `errorText` / `approval`
|
|
1548
|
+
* and no `input`. `validateUIMessages` against the AI SDK schema rejects
|
|
1549
|
+
* that shape, so filter to user messages (or skip validation entirely) on
|
|
1550
|
+
* those turns.
|
|
1539
1551
|
*
|
|
1540
1552
|
* @example
|
|
1541
1553
|
* ```ts
|
|
@@ -1544,7 +1556,11 @@ export type ChatAgentOptions<TIdentifier extends string, TClientDataSchema exten
|
|
|
1544
1556
|
* chat.agent({
|
|
1545
1557
|
* id: "my-chat",
|
|
1546
1558
|
* onValidateMessages: async ({ messages }) => {
|
|
1547
|
-
*
|
|
1559
|
+
* const userMessages = messages.filter((m) => m.role === "user");
|
|
1560
|
+
* if (userMessages.length > 0) {
|
|
1561
|
+
* await validateUIMessages({ messages: userMessages, tools: chatTools });
|
|
1562
|
+
* }
|
|
1563
|
+
* return messages;
|
|
1548
1564
|
* },
|
|
1549
1565
|
* run: async ({ messages }) => {
|
|
1550
1566
|
* return streamText({ model, messages, tools: chatTools });
|
package/dist/esm/v3/ai.js
CHANGED
|
@@ -1521,6 +1521,107 @@ function extractNewToolResultsFromHistory(message, messages) {
|
|
|
1521
1521
|
}
|
|
1522
1522
|
return out;
|
|
1523
1523
|
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Per-turn merge of an incoming wire `UIMessage` onto the matching entry
|
|
1526
|
+
* a `hydrateMessages` hook (or the default accumulator) provides. Used
|
|
1527
|
+
* to fold tool-state advances from the client into the agent's
|
|
1528
|
+
* authoritative chain without trusting the wire copy for fields the
|
|
1529
|
+
* LLM consumes.
|
|
1530
|
+
*
|
|
1531
|
+
* `hydrated` is treated as the source of truth for everything outside
|
|
1532
|
+
* tool-state advancement: text, reasoning blobs, provider metadata,
|
|
1533
|
+
* and tool `input` all stay as hydrated had them. We only overlay
|
|
1534
|
+
* tool parts whose incoming state is wire-advanced — `output-available`
|
|
1535
|
+
* / `output-error` (HITL `addToolOutput`) or `approval-responded` /
|
|
1536
|
+
* `output-denied` (approval flow) — and only the corresponding
|
|
1537
|
+
* resolution fields (`output` / `errorText` / `approval`). Hydrated
|
|
1538
|
+
* `input` and everything else stay put.
|
|
1539
|
+
*
|
|
1540
|
+
* Without this, a slim wire copy (which `TriggerChatTransport` /
|
|
1541
|
+
* `AgentChat.sendRaw` ship by default on HITL continuations) would
|
|
1542
|
+
* clobber the hydrated assistant — the next LLM call would receive a
|
|
1543
|
+
* tool call with no `input` and 4xx.
|
|
1544
|
+
*
|
|
1545
|
+
* @internal
|
|
1546
|
+
*/
|
|
1547
|
+
function mergeIncomingIntoHydrated(hydrated, incoming) {
|
|
1548
|
+
const incomingAdvancedByCallId = new Map();
|
|
1549
|
+
for (const part of (incoming.parts ?? [])) {
|
|
1550
|
+
if (!isToolUIPart(part))
|
|
1551
|
+
continue;
|
|
1552
|
+
const toolCallId = part.toolCallId;
|
|
1553
|
+
if (typeof toolCallId !== "string" || toolCallId.length === 0)
|
|
1554
|
+
continue;
|
|
1555
|
+
if (!isWireAdvanceableToolState(part.state))
|
|
1556
|
+
continue;
|
|
1557
|
+
incomingAdvancedByCallId.set(toolCallId, part);
|
|
1558
|
+
}
|
|
1559
|
+
if (incomingAdvancedByCallId.size === 0)
|
|
1560
|
+
return hydrated;
|
|
1561
|
+
let mutated = false;
|
|
1562
|
+
const hydratedParts = (hydrated.parts ?? []);
|
|
1563
|
+
const mergedParts = hydratedParts.map((part) => {
|
|
1564
|
+
if (!isToolUIPart(part))
|
|
1565
|
+
return part;
|
|
1566
|
+
const toolCallId = part.toolCallId;
|
|
1567
|
+
if (typeof toolCallId !== "string" || toolCallId.length === 0)
|
|
1568
|
+
return part;
|
|
1569
|
+
const incomingPart = incomingAdvancedByCallId.get(toolCallId);
|
|
1570
|
+
if (!incomingPart)
|
|
1571
|
+
return part;
|
|
1572
|
+
// Terminal hydrated states (`output-available`, `output-error`,
|
|
1573
|
+
// `output-denied`) are authoritative — never regressed by a stale
|
|
1574
|
+
// wire arrival (replay, retry, out-of-order). `output-denied`
|
|
1575
|
+
// matters here because the wire's `approval-responded` could
|
|
1576
|
+
// otherwise overwrite a hydrated denial back to a non-terminal
|
|
1577
|
+
// state.
|
|
1578
|
+
if (isResolvedToolState(part.state) || part.state === "output-denied") {
|
|
1579
|
+
return part;
|
|
1580
|
+
}
|
|
1581
|
+
// Same state on both sides — no progression to apply.
|
|
1582
|
+
if (part.state === incomingPart.state)
|
|
1583
|
+
return part;
|
|
1584
|
+
mutated = true;
|
|
1585
|
+
if (incomingPart.state === "output-available") {
|
|
1586
|
+
return {
|
|
1587
|
+
...part,
|
|
1588
|
+
state: incomingPart.state,
|
|
1589
|
+
output: incomingPart.output,
|
|
1590
|
+
...(incomingPart.approval !== undefined ? { approval: incomingPart.approval } : {}),
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
if (incomingPart.state === "output-error") {
|
|
1594
|
+
return {
|
|
1595
|
+
...part,
|
|
1596
|
+
state: incomingPart.state,
|
|
1597
|
+
errorText: incomingPart.errorText,
|
|
1598
|
+
...(incomingPart.approval !== undefined ? { approval: incomingPart.approval } : {}),
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
// approval-responded / output-denied — overlay state + approval.
|
|
1602
|
+
return {
|
|
1603
|
+
...part,
|
|
1604
|
+
state: incomingPart.state,
|
|
1605
|
+
...(incomingPart.approval !== undefined ? { approval: incomingPart.approval } : {}),
|
|
1606
|
+
};
|
|
1607
|
+
});
|
|
1608
|
+
if (!mutated)
|
|
1609
|
+
return hydrated;
|
|
1610
|
+
return { ...hydrated, parts: mergedParts };
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Mirror of `slimSubmitMessageForWire`'s predicate. Kept here so the
|
|
1614
|
+
* agent runtime doesn't have to import from `ai-shared.ts` for a
|
|
1615
|
+
* one-liner. See that file for the full state-machine docs.
|
|
1616
|
+
*
|
|
1617
|
+
* @internal
|
|
1618
|
+
*/
|
|
1619
|
+
function isWireAdvanceableToolState(state) {
|
|
1620
|
+
return (state === "output-available" ||
|
|
1621
|
+
state === "output-error" ||
|
|
1622
|
+
state === "approval-responded" ||
|
|
1623
|
+
state === "output-denied");
|
|
1624
|
+
}
|
|
1524
1625
|
/**
|
|
1525
1626
|
* Imperative API for reading and modifying the accumulated message history.
|
|
1526
1627
|
*
|
|
@@ -1648,7 +1749,7 @@ const chatAgentCompactionKey = locals.create("chat.agentCompaction");
|
|
|
1648
1749
|
// React hooks (`@trigger.dev/sdk/chat/react`) can import it without
|
|
1649
1750
|
// dragging `ai.ts` into the browser graph. Re-exported here so
|
|
1650
1751
|
// `@trigger.dev/sdk/ai` consumers still see it.
|
|
1651
|
-
export { PENDING_MESSAGE_INJECTED_TYPE } from "./ai-shared.js";
|
|
1752
|
+
export { PENDING_MESSAGE_INJECTED_TYPE, upsertIncomingMessage } from "./ai-shared.js";
|
|
1652
1753
|
import { PENDING_MESSAGE_INJECTED_TYPE } from "./ai-shared.js";
|
|
1653
1754
|
/** @internal */
|
|
1654
1755
|
const chatPendingMessagesKey = locals.create("chat.pendingMessages");
|
|
@@ -3415,6 +3516,17 @@ function chatAgent(options) {
|
|
|
3415
3516
|
}));
|
|
3416
3517
|
}
|
|
3417
3518
|
if (hydrateMessages) {
|
|
3519
|
+
// Snapshot the ids the accumulator knew BEFORE this
|
|
3520
|
+
// turn ran — used below to decide whether an
|
|
3521
|
+
// incoming wire message is genuinely new or just a
|
|
3522
|
+
// state advance on an existing entry. We can't use
|
|
3523
|
+
// the post-`hydrateMessages` array for this because
|
|
3524
|
+
// the canonical hook pattern pushes the incoming
|
|
3525
|
+
// user message into the persisted chain and
|
|
3526
|
+
// returns it.
|
|
3527
|
+
const previouslyKnownMessageIds = new Set(accumulatedUIMessages
|
|
3528
|
+
.map((m) => m.id)
|
|
3529
|
+
.filter((id) => typeof id === "string"));
|
|
3418
3530
|
// Backend hydration: load the full message history from the user's
|
|
3419
3531
|
// backend, replacing the built-in accumulator entirely. With slim
|
|
3420
3532
|
// wire, `incomingMessages` is consistently 0-or-1-length — what
|
|
@@ -3441,29 +3553,50 @@ function chatAgent(options) {
|
|
|
3441
3553
|
"chat.incoming_messages.count": cleanedUIMessages.length,
|
|
3442
3554
|
},
|
|
3443
3555
|
});
|
|
3444
|
-
//
|
|
3445
|
-
//
|
|
3446
|
-
//
|
|
3556
|
+
// Per-turn merge of incoming wire messages onto the hydrated
|
|
3557
|
+
// chain. Hydrated stays authoritative for text, reasoning
|
|
3558
|
+
// blobs, provider metadata, and tool `input`; we only
|
|
3559
|
+
// overlay tool-part state/output/errorText for tool calls
|
|
3560
|
+
// the wire copy has just resolved. Apps that slim the wire
|
|
3561
|
+
// copy to fit the .in/append cap (or drop fields they
|
|
3562
|
+
// re-source from their own DB) get the hydrated copy
|
|
3563
|
+
// through unchanged.
|
|
3447
3564
|
const merged = [...hydrated];
|
|
3448
3565
|
for (const incoming of cleanedUIMessages) {
|
|
3449
3566
|
if (!incoming.id)
|
|
3450
3567
|
continue;
|
|
3451
3568
|
const idx = merged.findIndex((m) => m.id === incoming.id);
|
|
3452
3569
|
if (idx !== -1) {
|
|
3453
|
-
merged[idx] = incoming;
|
|
3570
|
+
merged[idx] = mergeIncomingIntoHydrated(merged[idx], incoming);
|
|
3454
3571
|
}
|
|
3455
3572
|
}
|
|
3456
3573
|
accumulatedUIMessages = merged;
|
|
3457
3574
|
accumulatedMessages = await toModelMessages(merged);
|
|
3458
3575
|
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
3459
|
-
// Track new messages for onTurnComplete.newUIMessages
|
|
3576
|
+
// Track new messages for onTurnComplete.newUIMessages.
|
|
3577
|
+
// Only push for genuinely new ids — HITL continuations
|
|
3578
|
+
// whose incoming wire id matches an existing entry are
|
|
3579
|
+
// state advances on an old message, not new messages.
|
|
3580
|
+
// We compare against `previouslyKnownMessageIds`
|
|
3581
|
+
// captured BEFORE hydration, not against `hydrated`:
|
|
3582
|
+
// the canonical hydrate pattern pushes the incoming
|
|
3583
|
+
// user message into the persisted chain and returns
|
|
3584
|
+
// it, so the new id IS in `hydrated`, which would
|
|
3585
|
+
// wrongly drop every fresh user turn from
|
|
3586
|
+
// `newUIMessages`. The non-hydrate branch below has
|
|
3587
|
+
// the same "push only on append" semantic via its
|
|
3588
|
+
// own append-vs-replace path.
|
|
3460
3589
|
if (currentWirePayload.trigger === "submit-message" &&
|
|
3461
3590
|
cleanedUIMessages.length > 0) {
|
|
3462
3591
|
const lastUI = cleanedUIMessages[cleanedUIMessages.length - 1];
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
if (
|
|
3466
|
-
|
|
3592
|
+
const matchedExisting = lastUI.id !== undefined &&
|
|
3593
|
+
previouslyKnownMessageIds.has(lastUI.id);
|
|
3594
|
+
if (!matchedExisting) {
|
|
3595
|
+
turnNewUIMessages.push(lastUI);
|
|
3596
|
+
const lastModel = (await toModelMessages([lastUI]))[0];
|
|
3597
|
+
if (lastModel)
|
|
3598
|
+
turnNewModelMessages.push(lastModel);
|
|
3599
|
+
}
|
|
3467
3600
|
}
|
|
3468
3601
|
}
|
|
3469
3602
|
else {
|
|
@@ -3489,15 +3622,17 @@ function chatAgent(options) {
|
|
|
3489
3622
|
else if (cleanedUIMessages.length > 0) {
|
|
3490
3623
|
// Submit-message (and the special-cased
|
|
3491
3624
|
// handover-prepare → submit-message rewrite earlier in
|
|
3492
|
-
// this scope):
|
|
3493
|
-
//
|
|
3625
|
+
// this scope): merge-or-append for the single delta
|
|
3626
|
+
// message.
|
|
3494
3627
|
//
|
|
3495
3628
|
// Tool approval responses arrive as a single assistant
|
|
3496
3629
|
// message whose id collides with the existing assistant
|
|
3497
|
-
// in the accumulator — we
|
|
3498
|
-
//
|
|
3499
|
-
//
|
|
3500
|
-
// `
|
|
3630
|
+
// in the accumulator — we merge the resolved tool-part
|
|
3631
|
+
// resolutions onto the existing entry, keeping text,
|
|
3632
|
+
// reasoning, and tool `input` from the prior snapshot.
|
|
3633
|
+
// The fallback for HITL `addToolOutput` continuations
|
|
3634
|
+
// where AI SDK regenerates the id (TRI-9137) still
|
|
3635
|
+
// applies via `rewriteIncomingIdViaToolCallMap`.
|
|
3501
3636
|
let replaced = false;
|
|
3502
3637
|
for (const raw of cleanedUIMessages) {
|
|
3503
3638
|
let incoming = raw;
|
|
@@ -3510,7 +3645,7 @@ function chatAgent(options) {
|
|
|
3510
3645
|
}
|
|
3511
3646
|
}
|
|
3512
3647
|
if (idx !== -1) {
|
|
3513
|
-
accumulatedUIMessages[idx] = incoming;
|
|
3648
|
+
accumulatedUIMessages[idx] = mergeIncomingIntoHydrated(accumulatedUIMessages[idx], incoming);
|
|
3514
3649
|
replaced = true;
|
|
3515
3650
|
}
|
|
3516
3651
|
else {
|