docs(agent): annotate stream invariants
This commit is contained in:
@@ -48,6 +48,8 @@ export class EmbeddedBlockChunker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drain(params: { force: boolean; emit: (chunk: string) => void }) {
|
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 { force, emit } = params;
|
||||||
const minChars = Math.max(1, Math.floor(this.#chunking.minChars));
|
const minChars = Math.max(1, Math.floor(this.#chunking.minChars));
|
||||||
const maxChars = Math.max(minChars, Math.floor(this.#chunking.maxChars));
|
const maxChars = Math.max(minChars, Math.floor(this.#chunking.maxChars));
|
||||||
|
|||||||
@@ -177,6 +177,9 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
const blockChunker = blockChunking
|
const blockChunker = blockChunking
|
||||||
? new EmbeddedBlockChunker(blockChunking)
|
? new EmbeddedBlockChunker(blockChunking)
|
||||||
: null;
|
: 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 = () =>
|
const shouldEmitToolResult = () =>
|
||||||
typeof params.shouldEmitToolResult === "function"
|
typeof params.shouldEmitToolResult === "function"
|
||||||
? params.shouldEmitToolResult()
|
? params.shouldEmitToolResult()
|
||||||
@@ -231,6 +234,8 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
if (evt.type === "message_start") {
|
if (evt.type === "message_start") {
|
||||||
const msg = (evt as AgentEvent & { message: AgentMessage }).message;
|
const msg = (evt as AgentEvent & { message: AgentMessage }).message;
|
||||||
if (msg?.role === "assistant") {
|
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
|
// Start-of-message is a safer reset point than message_end: some providers
|
||||||
// may deliver late text_end updates after message_end, which would
|
// may deliver late text_end updates after message_end, which would
|
||||||
// otherwise re-trigger block replies.
|
// otherwise re-trigger block replies.
|
||||||
@@ -387,6 +392,8 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
if (delta) {
|
if (delta) {
|
||||||
chunk = delta;
|
chunk = delta;
|
||||||
} else if (content) {
|
} 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.
|
// Providers may resend full content on text_end; append only the suffix.
|
||||||
if (content.startsWith(deltaBuffer)) {
|
if (content.startsWith(deltaBuffer)) {
|
||||||
chunk = content.slice(deltaBuffer.length);
|
chunk = content.slice(deltaBuffer.length);
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
|||||||
_ctx,
|
_ctx,
|
||||||
signal,
|
signal,
|
||||||
): Promise<AgentToolResult<unknown>> => {
|
): 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);
|
return tool.execute(toolCallId, params, signal, onUpdate);
|
||||||
},
|
},
|
||||||
} satisfies ToolDefinition;
|
} satisfies ToolDefinition;
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export function splitMediaFromOutput(raw: string): {
|
|||||||
mediaUrls?: string[];
|
mediaUrls?: string[];
|
||||||
mediaUrl?: string; // legacy first item for backward compatibility
|
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();
|
const trimmedRaw = raw.trimEnd();
|
||||||
if (!trimmedRaw.trim()) return { text: "" };
|
if (!trimmedRaw.trim()) return { text: "" };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user