fix(references): repair ai-chat typecheck against current wire shape (#3685)

## Summary

Pre-existing typecheck errors in `references/ai-chat` against the
current SDK shape. Unblocks `pnpm exec tsc --noEmit` in the reference
project.

## What changed

Three categories of fixes inside `references/ai-chat`. No SDK changes.

### 1. `payload.messages` → `payload.message`

The wire payload is now delta-only — one new message per trigger,
optional. Old code in two raw-task files reads `payload.messages`
(plural array) which no longer exists.

```ts
// before
const messages = await conversation.addIncoming(currentPayload.messages, ...);

// after
const messages = await conversation.addIncoming(
  currentPayload.message ? [currentPayload.message] : [],
  ...
);
```

Same fix to the `chat.messages.on` handler, reading `msg.message`
(singular) instead of `msg.messages[length - 1]`.

### 2. `clientData` non-null assertion in `cf-trust-test`

`ChatTurnContext.clientData` is typed as `?: TClientData` on
`onTurnStart` / `run` event objects even when the agent declares a
`clientDataSchema`. The runtime validates against the schema before the
hook fires, so it's structurally non-null — but TypeScript can't know
that. Non-null assert for now.

Follow-up worth filing: narrow `ChatTurnContext.clientData` to
non-optional when the agent has a `clientDataSchema`. Same friction the
docs friction-test subagent flagged.

### 3. `stress-emit.parseConfig` retyped against `ModelMessage[]`

The `run` callback hands `messages: ModelMessage[]`, not `UIMessage[]`.
Update `parseConfig` to accept `ModelMessage[]` and pull text from
`content` (string or array-of-parts).

## Test plan

- [x] `pnpm exec tsc --noEmit` in `references/ai-chat` passes (was 8
errors, now 0)
This commit is contained in:
Eric Allam
2026-05-21 12:41:18 +01:00
committed by GitHub
parent 9ff410bfa4
commit 89d085a496
3 changed files with 15 additions and 10 deletions
@@ -272,7 +272,7 @@ export const orchestratorAgent = chat
stop.reset();
const messages = await conversation.addIncoming(
currentPayload.messages,
currentPayload.message ? [currentPayload.message] : [],
currentPayload.trigger,
turn
);
+4 -5
View File
@@ -659,7 +659,7 @@ export const aiChatRaw = chat.customAgent({
stop.reset();
const messages = await conversation.addIncoming(
currentPayload.messages,
currentPayload.message ? [currentPayload.message] : [],
currentPayload.trigger,
turn
);
@@ -678,8 +678,7 @@ export const aiChatRaw = chat.customAgent({
const combinedSignal = AbortSignal.any([runSignal, stop.signal]);
const steeringSub = chat.messages.on(async (msg) => {
const lastMsg = msg.messages?.[msg.messages.length - 1];
if (lastMsg) await conversation.steerAsync(lastMsg);
if (msg.message) await conversation.steerAsync(msg.message);
});
const result = streamText({
@@ -1049,10 +1048,10 @@ export const cfTrustTestAgent = chat
id: "cf-trust-test",
idleTimeoutInSeconds: 60,
onTurnStart: async ({ turn, clientData }) => {
logger.info("cf-trust-test turn", { turn, cf: clientData.__cf, userId: clientData.userId });
logger.info("cf-trust-test turn", { turn, cf: clientData!.__cf, userId: clientData!.userId });
},
run: async ({ messages, clientData, signal }) => {
const cf = clientData.__cf;
const cf = clientData!.__cf;
return streamText({
model: openai("gpt-4o-mini"),
system:
+10 -4
View File
@@ -10,7 +10,7 @@
// Defaults: 1000 chunks × 10 chars, single message.
import { chat } from "@trigger.dev/sdk/ai";
import { type UIMessage, simulateReadableStream, streamText } from "ai";
import { type ModelMessage, simulateReadableStream, streamText } from "ai";
import { MockLanguageModelV3 } from "ai/test";
import type { LanguageModelV3StreamPart } from "@ai-sdk/provider";
@@ -20,10 +20,16 @@ type StressConfig = {
manyMessages: boolean;
};
function parseConfig(messages: UIMessage[]): StressConfig {
function parseConfig(messages: ModelMessage[]): StressConfig {
const lastUser = [...messages].reverse().find((m) => m.role === "user");
const text =
lastUser?.parts?.[0]?.type === "text" ? lastUser.parts[0].text.trim() : "";
const content = lastUser?.content;
let text = "";
if (typeof content === "string") {
text = content.trim();
} else if (Array.isArray(content)) {
const textPart = content.find((p) => p.type === "text");
text = textPart && "text" in textPart ? textPart.text.trim() : "";
}
const parts = text.split(/\s+/);
const chunkCount = Number(parts[0]);
const chunkSize = Number(parts[1]);