Skip to content

feat(chat): hand run() a streamText with the managed options already applied - #1

Open
anurag6569201 wants to merge 1 commit into
qa/agent-triggerdotdev-trigger-dev/pr-01-4884/basefrom
qa/agent-triggerdotdev-trigger-dev/pr-01-4884/head
Open

anurag6569201 wants to merge 1 commit into
qa/agent-triggerdotdev-trigger-dev/pr-01-4884/basefrom
qa/agent-triggerdotdev-trigger-dev/pr-01-4884/head

Conversation

@anurag6569201

Copy link
Copy Markdown

Summary

Every run() had to spread chat.toStreamTextOptions(), and leaving it out dropped six things with no error: the managed prompt and its cache control, the registry-resolved model, the prompt's sampling config, telemetry, the skill tools, and the prepareStep that delivers steering, compaction and injected context.

Before:

import { chat } from "@trigger.dev/sdk/ai";
import { streamText, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

export const myChat = chat.agent({
  id: "my-chat",
  tools: { myTool },
  run: async ({ messages, tools, signal }) =>
    streamText({
      ...chat.toStreamTextOptions({ registry, tools }),
      model: anthropic("claude-sonnet-4-5"),
      system: "You are a helpful assistant.",
      messages,
      abortSignal: signal,
      stopWhen: stepCountIs(15),
    }),
});

After:

import { chat } from "@trigger.dev/sdk/ai";
import { stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

export const myChat = chat.agent({
  id: "my-chat",
  system: "You are a helpful assistant.",
  registry,
  tools: { myTool },
  run: async ({ messages, tools, signal, streamText }) =>
    streamText({
      model: anthropic("claude-sonnet-4-5"),
      messages,
      tools,
      abortSignal: signal,
      stopWhen: stepCountIs(15),
    }),
});

streamText comes from run's argument and shadows the one imported from ai, so the correct call is now the shorter one and the managed options cannot be lost by omission. chat.toStreamTextOptions() is unchanged and still supported, and is still the only option in a custom agent.

What changes when your options collide with the managed ones

Spread order decides the outcome today, and losing is silent:

streamText({ ...chat.toStreamTextOptions(), tools: myTools })       // skill tools dropped
streamText({ ...chat.toStreamTextOptions(), prepareStep: mine })    // steering, compaction and injection off

The managed streamText merges instead. tools are passed into the helper so skill tools survive, and a prepareStep you pass runs after the managed one rather than replacing it. Everything else you name is left alone and wins, telemetry included.

system is the exception: it can be set on chat.agent({ system }), through chat.prompt.set(), or at the call site, but only in one of them. Two at once throws and names the one that already owns it. No shape merges two system values across every supported AI SDK version, since v5 rejects an array of blocks and a structured block carries the provider options that make prompt caching work.

chat.headStart and chat.startHeadStart

buildStreamTextOptions supplies messages, stopWhen: stepCountIs(1) and abortSignal. Step 1 belongs to the route handler and step 2 onward to the agent, so re-setting stopWhen after a spread hands over a stream that has already run past step 1.

Before:

import { streamText, stepCountIs } from "ai";

export const POST = chat.headStart({
  agentId: "my-chat",
  run: async ({ chat: helper }) =>
    streamText({
      ...helper.toStreamTextOptions({ tools: headStartTools }),
      model: anthropic("claude-sonnet-4-6"),
      system: "You are a helpful assistant.",
    }),
});

After:

export const POST = chat.headStart({
  agentId: "my-chat",
  run: async ({ streamText }) =>
    streamText({
      model: anthropic("claude-sonnet-4-6"),
      system: "You are a helpful assistant.",
      tools: headStartTools,
    }),
});

Passing messages, prompt, stopWhen or abortSignal to that streamText is a type error, with a runtime throw behind it for JavaScript callers. tools is yours to pass. The old shape only warned in prose.

Also in here

  • chat.agent() takes system, registry, cacheControl and systemProviderOptions, so a managed prompt's model and its cache breakpoint no longer have to be passed at the call site.
  • ChatStreamText is exported for typing a loop factored out of run.

The signature is taken from the AI SDK's own declaration:

import type { streamText as aiStreamTextSignature } from "ai";
type AiStreamTextFn = typeof aiStreamTextSignature;

The peer range spans ai v5, v6 and v7, whose options differ. typeof resolves to whichever version is installed, so generics and tool inference are the caller's own and a v8 option needs no change here.

Actions. onAction no longer receives streamText or tools: an action is a state edit, and one that returns chat.turn() (added in triggerdotdev#4816) is followed by run(), which already has both. The action docs on this branch describe that model. chat.toStreamTextOptions() now also applies chat.agent's system, registry, cacheControl and systemProviderOptions, so the spread form is equivalent to the streamText handed to run(), as the docs say; previously an agent's system prompt was silently dropped on that path. Those options are published on every boot, including for a hydrateMessages agent, which skips the snapshot boot block where they were first set.

Verification

Typecheck and the full suite pass on both ai@6.0.116 and ai@7.0.66. The option merge is a pure function so the merged object can be asserted directly, which is how experimental_telemetry being dropped was caught: most streamText options never reach the provider, so a test that observes the model cannot see them.

Run end to end against a deployed agent with every run rewritten to the new form and no spread anywhere: steering, undo across a cold boot, and regenerate all still pass, a caller's own prepareStep runs while managed steering still fires inside the turn, and consecutive injections arrive one per turn. The handover-owned options are pinned by @ts-expect-error assertions in a typechecked test rather than only by the runtime throw.

Source merge-base: f8aacacb8fa05d5044aa3853dce829eb71f61c48
Source head: bf66457dfc4e8e9fbd6f06a1ee7b2cc6a0877395

@shipwright-agent

Copy link
Copy Markdown

⚠️ Shipwright · Approve with conditions

Recommendation: approve PR #1 with conditions · Tier T3
Checks: 0 total · 0 needing attention

Next step: an authorized approver must satisfy the approval condition.

Findings (6)

  • HIGH The docs repeatedly show 'run: async ({ messages, signal, streamText }) => streamText({...})' without explaining that 'streamText' here shadows the 'ai' import. · docs/ai-chat/actions.mdx:47
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The changeset for 'persist-action-history-mutations' claims rollback persistence now survives a run ending, but the diff contains no implementation code for this fix — only the cha · .changeset/persist-action-history-mutations.md:5
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The managed 'streamText' throws when 'system' is set in two places, but the diff only documents this in changesets and docs. · .changeset/managed-streamtext-in-run.md:11
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The steering tests rely on 'sessionStreams.lastSeqNum' cast through 'as unknown as SeqReader' to observe internal sequence numbers. · packages/trigger-sdk/test/steering-error-path.test.ts:63
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The 'useChatActions' convenience sends arbitrary 'action' objects through 'useChat' request bodies. · .changeset/use-chat-actions.md:5
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • LOW The changeset for 'inject-instructions-shape' notes that a cached system prompt gives up its cache entry while an injection is live, but does not quantify the cost or suggest a mit · .changeset/inject-instructions-shape.md:5
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Conditions

  • human approval required (T3): apply the approval label

Fireworks usage: 56,661 input · 664 output · 57,325 total tokens · $0.0129 · 14s · 0 fix iteration(s)

Open the Shipwright check for full evidence and the audit bundle. Use /shipwright rerun to verify again.

Comment thread docs/ai-chat/actions.mdx
},

run: async ({ messages, signal }) => {
run: async ({ messages, signal, streamText }) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · HIGH

The docs repeatedly show 'run: async ({ messages, signal, streamText }) => streamText({...})' without explaining that 'streamText' here shadows the 'ai' import.

Impact: The docs repeatedly show 'run: async ({ messages, signal, streamText }) => streamText({...})' without explaining that 'streamText' here shadows the 'ai' import. A reader who imports 'streamText' from 'ai' and also destructures it will be confused about which one is managed; the note exists in backend.mdx but is easy to miss in the other examples.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

"@trigger.dev/sdk": patch
---

Undo, edit and regenerate now survive a run ending. History rolled back from `onAction` was only kept in the running worker's memory, so the rollback held while that worker stayed warm and then reverted on the next continuation. The undone messages came back, minutes later, with no error. This also holds when the turn before the action failed: the rollback used to be written against the cursor from before that turn, so a continuation could replay output the failed turn had already superseded.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · HIGH

The changeset for 'persist-action-history-mutations' claims rollback persistence now survives a run ending, but the diff contains no implementation code for this fix — only the cha

Impact: The changeset for 'persist-action-history-mutations' claims rollback persistence now survives a run ending, but the diff contains no implementation code for this fix — only the changeset and tests. If the runtime change is in a separate package not shown in this diff, the release is incomplete; if it is supposed to be here, the fix is missing entirely.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

run: async ({ messages, signal, streamText }) =>
streamText({ model, messages, abortSignal: signal });
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · HIGH

The managed 'streamText' throws when 'system' is set in two places, but the diff only documents this in changesets and docs.

Impact: The managed 'streamText' throws when 'system' is set in two places, but the diff only documents this in changesets and docs. There is no test in the visible diff that exercises the throw path, so a regression where the throw is skipped or the wrong value wins would ship silently.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

const chunks: LanguageModelV3StreamPart[] = [
{ type: "text-start", id: "t1" },
{ type: "text-delta", id: "t1", delta: "partial" },
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · HIGH

The steering tests rely on 'sessionStreams.lastSeqNum' cast through 'as unknown as SeqReader' to observe internal sequence numbers.

Impact: The steering tests rely on 'sessionStreams.lastSeqNum' cast through 'as unknown as SeqReader' to observe internal sequence numbers. This couples tests to an internal API shape; if 'sessionStreams' changes, the tests break without testing the actual behavior.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

"@trigger.dev/sdk": minor
---

Actions are sent through `useChat` so a turn that follows one renders like any turn. `TriggerChatTransport` recognises `body.action` on a `useChat` request and sends it as an action, so `sendMessage(undefined, { body: { action } })` or `regenerate({ body: { action } })` sends the action and `useChat` owns the response: it streams into the message list, `status` and `error` behave as for a message, and `stop` works. `useChatActions({ sendMessage })` in `@trigger.dev/sdk/chat/react` is a two-line convenience over that.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · HIGH

The 'useChatActions' convenience sends arbitrary 'action' objects through 'useChat' request bodies.

Impact: The 'useChatActions' convenience sends arbitrary 'action' objects through 'useChat' request bodies. The changeset says the backend validates against 'actionSchema', but the diff does not show the validation path for the new 'body.action' transport route. If validation is bypassed or the schema is permissive, a client could inject action types the server did not intend to expose.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

"@trigger.dev/sdk": patch
---

Injected system context is merged into a single instruction block, so it works on every supported AI SDK version. Note that a cached system prompt gives up its cache entry for as long as an injection is live, since the cached prefix has changed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · LOW

The changeset for 'inject-instructions-shape' notes that a cached system prompt gives up its cache entry while an injection is live, but does not quantify the cost or suggest a mit

Impact: The changeset for 'inject-instructions-shape' notes that a cached system prompt gives up its cache entry while an injection is live, but does not quantify the cost or suggest a mitigation. A maintainer tuning prompt caching later will not know whether this is a minor or major performance regression.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant