Skip to content

refactor(agents): remove AiSdkHarness; pi Channels adapter and projections in the package - #2545

Draft
cjol wants to merge 4 commits into
mainfrom
refactor/harness-conversation-shape
Draft

cjol wants to merge 4 commits into
mainfrom
refactor/harness-conversation-shape

Conversation

@cjol

@cjol cjol commented Oct 9, 2026

Copy link
Copy Markdown
Member

This PR removes AiSdkHarness, moves the pi Channels adapter out of the channels example into agents/harness/pi, splits each harness's message-format conversions into a projection, and moves message-id mapping from the pi adapter into Channels. It replaces the closed #2510.

Why

  • PiHarness is the flagship harness, but the code that serves it through Channels lived in an example, and it did work that belongs in Channels: it kept its own store of caller message ids and renamed pi's entries to match.
  • ThinkHarness now covers what AiSdkHarness did, so keeping both meant two AI SDK harnesses to maintain.
  • Each harness's message-format conversion was mixed into its adapter. Separating the pure conversion (a projection, which sees only the vendor's values) from the adapter (which may read the harness's storage) makes the conversion testable on recorded data.
  • The harness interface keeps the word "session", which every harness on main already uses (PiHarness, ThinkHarness, OpenCodeHarness). Renaming it to "conversation" would mean renaming all of them.

Review checklist

The commits are stages. Each list below is what that stage changes, so a reviewer can check the diff against it. Items are numbered across stages: A for the pi adapter, P for the pi projection, C for Channels.

Stage 1, beb174d: remove AiSdkHarness

  1. Delete AiSdkHarness, the agents/harness/ai-sdk entry point, createSendMessageTool, and their tests and test bindings.
  2. Move the AI SDK conversions to agents/experimental/channels/projections/ai-sdk: toResponseChunks, toResponseChunk, toTranscriptMessage, toUIMessageChunk, toUIMessage. AiSdkConversionOptions becomes AiSdkProjectionOptions.
  3. Move answerToolCall and ToolCallAnswer into harness/think/answers.ts, since ThinkHarness is the only user. Drop awaitsInput, which only AiSdkHarness used.
  4. The channels example serves a Think agent at /channels/think/... in place of the AI SDK one, with a v3 migration that deletes AiSdkAgent.
  5. Docs and comments that pointed at AiSdkHarness for convertToModelMessages(..., { tools }) now point at ThinkHarness, which does the same.

Stage 2, 46daff5: move the pi adapter into the package, behaviour unchanged

The adapter (harness/pi/channels.ts, exported from agents/harness/pi) makes these adaptations at this stage:

  • A1. Method shape: session, sessions.create, fork and list pass through to PiHarness. watch() wraps pi's events() (snapshot, start, stop, closed).
  • A2. Operation ids: a missing id is generated, and the id goes to pi as its operationId, stored as the submission's requestId.
  • A3. Tool results and approvals are rejected, since pi has neither.
  • A4. from is dropped, since pi has no participants.
  • A5. Caller message ids: the adapter records each operation's messageId in its own kv, and renames user entries to it, scanning pi's submissions for entries it has not matched yet.
  • A6. pi submission ids in run_start, run_end and the snapshot's run are looked up in pi's storage to get operation ids.
  • A7. The starting state comes from three reads: the events() snapshot, messages(), and pending().
  • A8. After every saved entry, the adapter re-reads the whole transcript and publishes the messages whose JSON changed.
  • A9. A pi.reset entry becomes a reset event, then the changed messages.
  • A10. Submission records become queued, placed, done and unanswered statuses.
  • A11. Dropped: tool_execution_start, tool_execution_update, inbox_update, auto_retry_*, a snapshot sent after an overflow, and pi's other events.

The pi projection (agents/experimental/channels/projections/pi) holds the pure conversions:

  • P1. toUserInput: text stays text, collapsed to a string when it is the only part. Data-URL images become pi images. Other files are dropped.
  • P2. toTranscriptMessages: entries become messages under their entry ids. A tool result folds into the assistant message that made the call (details if present, otherwise the text). A pi.reset entry becomes a system message, "Context reset". System-prompt entries are skipped. An assistant errorMessage becomes a text part.
  • P3. PiChunkProjection, one per run: text and reasoning chunks with part ids message:index. It emits text that only a fuller copy carries (the message_start partial, a whole block or message change, or the saved entry), as fix(examples): stream all of a Pi message's text in Channels #2519 did. Tool calls appear once, when their block completes or at the message end. Tool-call argument deltas are ignored.
  • P4. toToolOutputChunk: a tool's saved result becomes a tool-output or tool-error chunk.

The wait() result conversion stays in the adapter, because it maps PiHarness's result shape rather than pi's message format.

Stage 3, 0b84178: Channels maps message ids; a transcript event replaces reset

Interface (experimental/channels/harness.ts):

  • placed and done statuses can carry messageId: the id a harness gave the user message, when it differs from the caller's. placed must come before that message's message event.
  • { type: "transcript"; messages }, which replaces the whole active transcript, replaces { type: "reset" }.

Channels (harness-conversations.ts):

  • C1. Channels stores a mapping from harness message id to inbound message id, learned from placed and done, and shows held and published messages under the inbound id. A message held under the harness id before the mapping arrives is renamed and republished.
  • C2. A transcript event ends the live response, resets surfaces to an empty snapshot, then publishes the new transcript. This is what reset did, plus the messages.

pi adapter:

  • A5 removed. The kv option is gone: piChannelsHarness(pi).
  • A12. placed and done report the entry an operation placed. wait() adds it to a done result by scanning pi's submissions, for a caller that missed placed.
  • A13. pi publishes a commit's submission records after its entries, so each placed moves to just before the message it names. Nothing else is reordered. A test fails without this.
  • A9 changed: a pi.reset entry becomes a transcript event with the new transcript.

Think: its native reset becomes { type: "transcript", messages: [] }. Its native transcript (re-read) event is still not forwarded, as before.

Stage 4, 2252bc4: build the pi adapter's state from pi's snapshot

  • A7 changed: the starting state comes from pi's snapshot alone, which pi takes atomically with the watch. Messages come from its entries, queued operations from its inbox, and placed operations, with their entry ids, from its run's inputs.
  • A8 changed: the transcript is kept from the entries in later events, not re-read. It is re-read only when an entry moves the context's start or edits earlier entries, such as a compaction.
  • A9 changed: the adapter emits a transcript event only when published messages are gone from the transcript. Otherwise it emits message events, so a replacement that only adds messages does not reset surfaces.
  • A11 changed: a snapshot sent after an overflow replaces the transcript, and ends or starts the run where it differs from the one being followed.
  • The Channels glossary gains Client and Projection, and Harness adapter is narrowed to match.

Unchanged after all four stages: A1, A2, A3, A4, A6, A10, A12, A13, P1 to P4, and the rest of A11.

Known gaps, not addressed here

  • After an overflow snapshot, missed done or unanswered statuses are not replayed, so a turn can stay open until Channels attaches again.
  • auto_retry_* is still dropped, so chunks from a failed attempt are never withdrawn.
  • tool_execution_update is still dropped, so tools show no live output.
  • P1 still drops non-image files and images that are not data URLs without saying so.
  • Pending operations from the snapshot assume every placed, unsettled submission is in the run's inputs.
  • The Channels and AiSdkHarness integration tests are deleted with the harness. Channels' own tests use a scripted harness, and the new pi adapter tests run against PiHarness.

Compatibility

  • agents/harness/ai-sdk is gone, including AiSdkHarness and createSendMessageTool. Use ThinkHarness from agents/harness/think. The AI SDK conversions move to agents/experimental/channels/projections/ai-sdk.
  • Harness interface: a harness that emitted { type: "reset" } emits { type: "transcript", messages } instead.

cjol added 4 commits October 9, 2026 16:25
ThinkHarness replaces it. The AI SDK message conversions it was built on
become the AI SDK projection, agents/experimental/channels/projections/ai-sdk,
which ThinkHarness and the Web Channel's AI SDK transport use. The
tool-answer helper moves into ThinkHarness, its only user. The channels
example serves a Think agent in place of the AI SDK one.
piChannelsHarness moves from the channels example into the package, and
its message-format conversions become the pi projection,
agents/experimental/channels/projections/pi. Behaviour is unchanged,
including #2519's handling of text that only a fuller copy carries.
…vent replaces reset

A harness that gives a user message its own id reports it on placed and
done, and Channels shows the message under the inbound message's id. The
pi adapter therefore drops its own id store and its kv option, and moves
each placed status ahead of the message it names, since pi publishes a
commit's submissions after its entries.

A transcript event, which replaces the whole active transcript, replaces
the reset event: a message event cannot remove messages.
The pi adapter read its starting transcript and pending operations
separately from pi's event snapshot, so a commit between the reads could
be missed or seen twice. It now builds both from the snapshot, which pi
takes atomically with the watch, and keeps the transcript from the
entries that follow instead of re-reading it after every entry. It
re-reads only when an entry moves the context's start or edits earlier
entries, such as a compaction. A snapshot pi sends after an overflow
replaces the transcript and ends or starts the run where it differs.

The Channels glossary gains Client and Projection.
@changeset-bot

changeset-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2252bc4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Minor
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@agent-think

agent-think Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

🟡 agents import sizes: 1 entry point grew

Entry point Exports Largest gzip change Size now
🟡 agents/experimental/channels 1 resized +248 B (+2.92%) 8.5 KiB
🆕 agents/experimental/channels/projections/ai-sdk 5 new — 955 B
🆕 agents/experimental/channels/projections/pi 4 new — 1.1 KiB
🆕 agents/harness/pi 1 new — 3.8 KiB
➖ agents/harness/ai-sdk 9 removed — —
Changed exports (20)
Import Gzip change Size now
🟡 agents/experimental/channels#Channels +248 B (+2.92%) 8.5 KiB
🆕 agents/harness/pi#piChannelsHarness — 3.8 KiB
🆕 agents/experimental/channels/projections/pi#PiChunkProjection — 1.1 KiB
🆕 agents/experimental/channels/projections/ai-sdk#toResponseChunks — 955 B
🆕 agents/experimental/channels/projections/ai-sdk#toResponseChunk — 851 B
🆕 agents/experimental/channels/projections/ai-sdk#toTranscriptMessage — 783 B
🆕 agents/experimental/channels/projections/pi#toTranscriptMessages — 632 B
🆕 agents/experimental/channels/projections/ai-sdk#toUIMessage — 260 B
🆕 agents/experimental/channels/projections/pi#toUserInput — 235 B
🆕 agents/experimental/channels/projections/ai-sdk#toUIMessageChunk — 229 B
🆕 agents/experimental/channels/projections/pi#toToolOutputChunk — 221 B
➖ agents/harness/ai-sdk#AiSdkHarness — —
➖ agents/harness/ai-sdk#answerToolCall — —
➖ agents/harness/ai-sdk#awaitsInput — —
➖ agents/harness/ai-sdk#createSendMessageTool — —
➖ agents/harness/ai-sdk#toResponseChunk — —
➖ agents/harness/ai-sdk#toResponseChunks — —
➖ agents/harness/ai-sdk#toTranscriptMessage — —
➖ agents/harness/ai-sdk#toUIMessage — —
➖ agents/harness/ai-sdk#toUIMessageChunk — —
How this works

Each runtime export is bundled on its own, minified, and gzipped. Changes smaller than 100 B, or smaller than 1% and 1 KiB, are ignored. Growth over 10% or 5 KiB is marked 🔴. This report is informational and does not fail CI. The workflow artifact contains every measurement.

Compared 48d9c36f → 2252bc4f · workflow run · reported by agent-think[bot]

@pkg-pr-new

pkg-pr-new Bot commented Oct 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2545

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2545

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2545

hono-agents

npm i https://pkg.pr.new/hono-agents@2545

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2545

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2545

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2545

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2545

commit: 2252bc4

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