Skip to content

Commit 0eb932f

Browse files
committed
fix(chat,sdk): drop dead claim-kind docs and quiet the drain on known kinds
Removes a JSDoc block and a `{@link}` reference to a claim-kinds helper that is not part of this change, which also left `chat.createStopSignal` carrying two doc comments. The drain also warned on every record it discarded, including a stop that the stop facade had already handled: all handlers are invoked for a record regardless of whether an earlier one consumed it, so a stop with an active stop signal is both aborted and drained. A known kind with no active consumer is an expected state, so the warning is now limited to kinds this SDK version does not recognise, which is the case that indicates a newer server. Also corrects the docs and changeset, which claimed `next()` returning `undefined` always means the mailbox is idle. A control record that does have its own consumer can sit at the head while `next()` times out.
1 parent 89bcf85 commit 0eb932f

3 files changed

Lines changed: 22 additions & 17 deletions

File tree

.changeset/tidy-mailboxes-wait.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
Custom agent loops can now inspect pending chat input without consuming it and consume one mailbox record at a time with `chat.messages.hasPending()` and `chat.messages.next()`. Mailbox records include stable identifiers for tracing and redelivery.
77

8-
A control record that nothing on the run consumes is now discarded rather than left at the head of the `.in` channel, where it would have made every message queued behind it undeliverable. `chat.messages.next()` returning `undefined` now always means the mailbox is idle.
8+
A control record that nothing on the run consumes is now discarded rather than left at the head of the `.in` channel, where it would have made every message queued behind it undeliverable. `chat.messages.next()` returning `undefined` means no message became consumable before the timeout.

docs/ai-chat/custom-agents.mdx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,11 @@ becomes pending.
269269

270270
A control record that nothing on the run consumes is discarded rather than left
271271
at the head of the channel. `hasPending()` and `next()` only look at the head, so
272-
a record parked there would make every message behind it undeliverable. This
273-
means `next()` returning `undefined` always means the mailbox is idle.
272+
a record parked there would make every message behind it undeliverable.
273+
274+
`next()` still returns `undefined` whenever no message became consumable before
275+
the timeout, including while a control record that does have its own consumer
276+
sits at the head.
274277

275278
A complete loop:
276279

packages/trigger-sdk/src/v3/ai.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,6 +1920,19 @@ const chatClaimedKindsKey = locals.create<Set<string>>("chat.claimedKinds");
19201920
/** Kinds carried on `.in` that are not user messages. @internal */
19211921
const CHAT_HANDOVER_KINDS = ["handover", "handover-skip"] as const;
19221922

1923+
/**
1924+
* Every `ChatInputChunk` kind this SDK version knows about. A known kind with
1925+
* no active consumer on this boot is an expected, documented state; an unknown
1926+
* one means a newer server is sending something this worker cannot handle,
1927+
* which is worth surfacing.
1928+
* @internal
1929+
*/
1930+
const KNOWN_CHAT_INPUT_KINDS: ReadonlySet<string> = new Set([
1931+
"message",
1932+
"stop",
1933+
...CHAT_HANDOVER_KINDS,
1934+
]);
1935+
19231936
/** The run's attached drain subscription, so it can be re-offered the buffer. @internal */
19241937
const chatInputDrainKey = locals.create<{ off: () => void }>("chat.inputDrain");
19251938

@@ -1957,9 +1970,9 @@ function attachUnclaimedChatInputDrain(): { off: () => void } {
19571970
return true;
19581971
}
19591972
if (chatClaimedKinds().has(kind)) return undefined;
1960-
logger.warn("chat: discarded a session.in record that no consumer handled on this boot", {
1961-
kind,
1962-
});
1973+
if (!KNOWN_CHAT_INPUT_KINDS.has(kind)) {
1974+
logger.warn("chat: discarded a session.in record of an unrecognised kind", { kind });
1975+
}
19631976
return true;
19641977
});
19651978
}
@@ -1987,16 +2000,6 @@ function releaseChatInputKinds(kinds: readonly string[]): void {
19872000
locals.set(chatInputDrainKey, attachUnclaimedChatInputDrain());
19882001
}
19892002

1990-
/**
1991-
* Declare that this run will consume the given `session.in` record kinds
1992-
* itself (via raw `session.in` reads). Claimed kinds are never discarded by
1993-
* the unclaimed-control drain: they stay buffered, block
1994-
* `chat.messages.next()` at the head of the channel, and hold the resume
1995-
* cursor behind them until consumed. Call `release()` when the loop stops
1996-
* consuming them — any still-buffered records of those kinds are then
1997-
* discarded and the cursor advances.
1998-
*/
1999-
20002003
/**
20012004
* Per-turn deferred promises. Registered via `chat.defer()`, awaited
20022005
* before `onTurnComplete` fires. Reset each turn.
@@ -10861,7 +10864,6 @@ export const chat = {
1086110864
response: chatResponse,
1086210865
/** Pre-built input stream for receiving messages from the transport. */
1086310866
messages: messagesInput,
10864-
/** Declare `session.in` record kinds this run consumes itself. See {@link chatClaimInputKinds}. */
1086510867
/** Create a managed stop signal wired to the stop input stream. See {@link createStopSignal}. */
1086610868
createStopSignal,
1086710869
/** Signal the frontend that the current turn is complete. See {@link chatWriteTurnComplete}. */

0 commit comments

Comments
 (0)