Repository navigation
feat(agents): add "agents/harnesses/pi" - #2424
Conversation
🦋 Changeset detectedLatest commit: 2ddae8c The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
🟢 agents import sizes: 1 entry point changed, no growth
Changed exports (6)
How this worksEach 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 |
| export { | ||
| openPiSessionStore, | ||
| type PiSessionStoreOptions | ||
| } from "./session-store"; |
There was a problem hiding this comment.
🔍 Upstream SQL changes need compatibility coverage
The published store derives its identifier rewrite from pi migrations. Its conformance tests cover the current release, but the peer range allows newer pi releases with different SQL shapes. Consider testing compatible releases or constraining the supported range.
Was this helpful? React with 👍 or 👎 to provide feedback.
| export { | ||
| WORKERS_AI_PROVIDER, | ||
| workersAI, | ||
| type WorkersAIModelOptions, | ||
| type WorkersAIOptions | ||
| } from "./workers-ai"; |
There was a problem hiding this comment.
| if (!sawFinishReason) { | ||
| if (sawDone || this.blocks.length > 0) { | ||
| this.output.stopReason = hasToolCalls ? "toolUse" : "stop"; | ||
| } else { | ||
| this.output.stopReason = "error"; | ||
| this.output.errorMessage = | ||
| "The stream ended before the model produced any output."; | ||
| } |
There was a problem hiding this comment.
🔴 Truncated streams become completed answers
When an SSE connection drops after text, finish marks the incomplete answer as stopped without receiving a finish reason or [DONE]. Callers receive truncated text as a successful assistant turn.
Learn more
The chat-completions wire reads SSE chunks and passes sawFinishReason and sawDone into CompletionAssembler.finish. A connection that ends after emitting content but before a terminal chunk has both flags false. The current condition treats the existing content as proof of completion, sets stopReason to stop, and publishes a done event. A dropped connection therefore turns an incomplete response into a successful one.
Example: A model emits data: {"choices":[{"delta":{"content":"The answer is"}}]} and the socket closes without another event. The result contains The answer is and reports stop rather than an error.
Recommended fix: For the SSE path, distinguish a clean terminal marker or explicit finish reason from a mere partial block. Set an error stop reason when neither is observed, while retaining handling for providers that legitimately omit [DONE] but send a finish reason.
Was this helpful? React with 👍 or 👎 to provide feedback.
| committed = true; | ||
| if (pendingStart !== undefined) outer.push(pendingStart); | ||
| pendingStart = undefined; |
There was a problem hiding this comment.
🟡 Empty stream starts block model fallback
When a leg emits text_start before failing, streamWithFallback commits to it despite receiving no content. The fallback models never run, leaving the caller with an error instead of a possible answer.
Learn more
The fallback dispatcher holds each leg's initial start event until it can commit to that leg. CompletionAssembler.pushText emits text_start before text_delta, and tool and reasoning blocks likewise begin before their content. streamWithFallback commits on the first event other than start, so a failure after an empty block start is treated as a committed failure. That bypasses later models even though no answer content was delivered.
Example: A leg emits start, text_start, then error when its upstream stream breaks. The second leg is not tried, although the first emitted no text delta.
Recommended fix: Buffer block-start events along with start and commit only on a content-bearing delta, completed tool call, or successful terminal event. Discard buffered events when an uncommitted leg fails, and preserve their order when committing.
Was this helpful? React with 👍 or 👎 to provide feedback.
| import { createModels } from "@earendil-works/pi-ai/models"; | ||
| import { routeAgentRequest } from "agents"; | ||
| import { PiHarness } from "agents/harnesses/pi"; | ||
| import { Lifecycle } from "agents/lifecycle"; |
There was a problem hiding this comment.
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
Move the pi harness example's core into the agents package as an experimental entry point. agents/harnesses/pi exports PiHarness, PiSession, PiSessions, the Durable Object session store, addSkills, and the harness's own types. @earendil-works/pi-durable becomes an optional peer dependency, so the main agents entry does not load pi. The harness does not depend on @earendil-works/chord. pi-durable takes a chord Context on every call without re-exporting it, and the harness needs only a background context and withAbortSignal, so context.ts inlines both against chord's structural Context interface. chord's own derived contexts read the abort signal through a private key, so a chord withContextValue around one of these would not see its signal; pi 0.99.2 never wraps a caller's context that way. Transcripts are pi's own entries: messages() and prompt()'s messages return EntryRecord[]. The display projection (PiMessage, PiMessagePart) moves to the example as src/transcript.ts, next to the view that uses it. The harness, options and session store tests move with the code into their own harnesses-pi vitest project, with pi-ai's faux provider on a plain Models registry. The example keeps its socket tests, and the test that folds a run's events into the same view a late joiner gets moves to its view.test.ts. Models come from agents/models/pi-ai.
This adds a new "agents/harnesses/pi" entrypoint that provides a
PiHarnessthat uses the "agents/models/pi-ai" model provider.