docs(agent): annotate stream invariants

This commit is contained in:
Peter Steinberger
2026-01-05 18:10:03 +00:00
parent 86ad703f53
commit cc790f2c84
4 changed files with 13 additions and 0 deletions

View File

@@ -48,6 +48,8 @@ export class EmbeddedBlockChunker {
}
drain(params: { force: boolean; emit: (chunk: string) => void }) {
// KNOWN: We cannot split inside fenced code blocks (Markdown breaks + UI glitches).
// When forced (maxChars), we close + reopen the fence to keep Markdown valid.
const { force, emit } = params;
const minChars = Math.max(1, Math.floor(this.#chunking.minChars));
const maxChars = Math.max(minChars, Math.floor(this.#chunking.maxChars));

View File

@@ -177,6 +177,9 @@ export function subscribeEmbeddedPiSession(params: {
const blockChunker = blockChunking
? new EmbeddedBlockChunker(blockChunking)
: null;
// KNOWN: Provider streams are not strictly once-only or perfectly ordered.
// `text_end` can repeat full content; late `text_end` can arrive after `message_end`.
// Tests: `src/agents/pi-embedded-subscribe.test.ts` (e.g. late text_end cases).
const shouldEmitToolResult = () =>
typeof params.shouldEmitToolResult === "function"
? params.shouldEmitToolResult()
@@ -231,6 +234,8 @@ export function subscribeEmbeddedPiSession(params: {
if (evt.type === "message_start") {
const msg = (evt as AgentEvent & { message: AgentMessage }).message;
if (msg?.role === "assistant") {
// KNOWN: Resetting at `text_end` is unsafe (late/duplicate end events).
// ASSUME: `message_start` is the only reliable boundary for “new assistant message begins”.
// Start-of-message is a safer reset point than message_end: some providers
// may deliver late text_end updates after message_end, which would
// otherwise re-trigger block replies.
@@ -387,6 +392,8 @@ export function subscribeEmbeddedPiSession(params: {
if (delta) {
chunk = delta;
} else if (content) {
// KNOWN: Some providers resend full content on `text_end`.
// We only append a suffix (or nothing) to keep output monotonic.
// Providers may resend full content on text_end; append only the suffix.
if (content.startsWith(deltaBuffer)) {
chunk = content.slice(deltaBuffer.length);

View File

@@ -24,6 +24,8 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
_ctx,
signal,
): Promise<AgentToolResult<unknown>> => {
// KNOWN: pi-coding-agent `ToolDefinition.execute` has a different signature/order
// than pi-agent-core `AgentTool.execute`. This adapter keeps our existing tools intact.
return tool.execute(toolCallId, params, signal, onUpdate);
},
} satisfies ToolDefinition;

View File

@@ -27,6 +27,8 @@ export function splitMediaFromOutput(raw: string): {
mediaUrls?: string[];
mediaUrl?: string; // legacy first item for backward compatibility
} {
// KNOWN: Leading whitespace is semantically meaningful in Markdown (lists, indented fences).
// We only trim the end; token cleanup below handles removing `MEDIA:` lines.
const trimmedRaw = raw.trimEnd();
if (!trimmedRaw.trim()) return { text: "" };