Wave D: native AgentEvent wire + client session rewrite (SDK purged)

Server: ui-stream.ts is verbatim pi AgentEvent passthrough (SSE data
lines + [DONE]); only app-level events are the loop-level error and
turn-settled terminal; onError carries raw provider text (owner ruling).

Client: useChat + the AI-SDK transport replaced by components/session-core.ts
(SessionModel: 409 identity-retry, optimistic c_ appends, absorbed()
adoption, needsContinuation, shared consume for POST + reattach) and the
session.ts hook; the golden-master grouping canon moved client-side into
core/view.ts (projectView); scanners read the native record (phaseAt3Lh,
danglingQuestionOf, graph toolParts, plan-blocks, runner, extraction).

Coverage: the native message is the unit (fork 3) with the call+result
inseparability bond rule; view-line-less messages (markers, error
results) are invisible to coverage and never checkpointed.

Deletes: chat-transport, projection, ui-message, their tests (ported to
tests/session-core.test.ts), and the ai + @ai-sdk/react dependencies —
the Vercel SDK is fully out of the tree. Wire pins in chat-flow rewritten
onto the AgentEvent dialect (eventsOf parser).

Gates: tsc clean, 994/994 tests, eslint clean, build green; live smoke
on PORT=3004 (verbatim wire → native record).
This commit is contained in:
amosblomqvist
2026-09-26 01:52:47 +03:00
parent 7c0e8d9a50
commit f3003d357a
44 changed files with 2299 additions and 2205 deletions
+10 -17
View File
@@ -1,4 +1,3 @@
import { isToolUIPart } from "ai";
import type { PiEngine } from "../models.ts";
import type { SearchProvider } from "../../core/search.ts";
import {
@@ -23,8 +22,6 @@ import { registerJobAbort, unregisterJobAbort } from "./aborts.ts";
import type { ModelRole } from "../../../config.ts";
import { ROLE_SLOT, modelsNotConfigured, type ModelSlotName, type ProviderKeyMissing } from "../../core/settings.ts";
import type { ChatStore, JobStore } from "../../core/ports.ts";
import { projectNative } from "../../core/projection.ts";
import type { UIMessage } from "ai";
import { runDemoBuilder } from "./demo-builder.ts";
import { runPdfReaderAgent } from "./pdf-reader.ts";
import { runResearcherAgent } from "./researcher.ts";
@@ -276,22 +273,18 @@ export function createJobRunner(deps: JobRunnerDeps): JobRunner {
if (input) return input;
const chat = chatStore.getChat(job.chatId);
if (!chat) return undefined;
// Wave B: the record is native history — the transcript scan reads
// through the projection (same shape the old record held).
return (findToolPart(projectNative(chat.messages), job.toolCallId)?.input ?? undefined) as
| JobToolInput
| undefined;
}
// ─── shared plumbing ────────────────────────────────────────────────────
function findToolPart(messages: UIMessage[], toolCallId: string) {
for (const m of messages) {
for (const part of m.parts) {
if (isToolUIPart(part) && part.toolCallId === toolCallId) return part;
// Wave D: the record is native history — the scan reads the native
// assistant blocks directly (a toolCall's arguments are the input).
for (const m of chat.messages) {
if ((m as { role?: string }).role !== "assistant") continue;
const content = (m as { content?: unknown }).content;
if (!Array.isArray(content)) continue;
for (const block of content as Array<{ type?: string; id?: string; arguments?: unknown }>) {
if (block.type !== "toolCall" || block.id !== job.toolCallId) continue;
return block.arguments as JobToolInput | undefined;
}
}
return null;
return undefined;
}
/**