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:
+27
-12
@@ -39,7 +39,8 @@
|
||||
*/
|
||||
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { isToolUIPart, type UIMessage } from "ai";
|
||||
import type { LhMessage } from "./native-messages.ts";
|
||||
import type { ToolResultMessage } from "@earendil-works/pi-ai";
|
||||
import type { CoverageEntry } from "./contracts/graph.ts";
|
||||
import type {
|
||||
AuditResult,
|
||||
@@ -85,7 +86,7 @@ function newGraphNodeId(): string {
|
||||
* fresh understanding observations land as new events; the notes path
|
||||
* passes neither and keeps the whole-file checkpoint. */
|
||||
export interface MergeSource {
|
||||
messages: UIMessage[] | null;
|
||||
messages: LhMessage[] | null;
|
||||
foldedAt: string;
|
||||
/** The batch being merged (chat path): one entry per message in
|
||||
* `messages`, same order — undefined (notes path / coverage-less
|
||||
@@ -97,7 +98,11 @@ export interface MergeSource {
|
||||
|
||||
// ─── transcript scanning (shared by the merger + trigger) ─────────────────────
|
||||
|
||||
/** A flattened, order-preserving view of every tool part in a transcript. */
|
||||
/** A flattened, order-preserving view of every tool call (+ its result
|
||||
* state) in a native transcript. Wave D: the pairing is native — calls
|
||||
* come from assistant content blocks; a result map built from the
|
||||
* toolResult messages carries the outcome (a dangling call is
|
||||
* input-available, an error result is output-error). */
|
||||
interface ToolPartView {
|
||||
name: string;
|
||||
toolCallId: string;
|
||||
@@ -106,17 +111,27 @@ interface ToolPartView {
|
||||
output: unknown;
|
||||
}
|
||||
|
||||
function toolParts(messages: UIMessage[]): ToolPartView[] {
|
||||
function toolParts(messages: LhMessage[]): ToolPartView[] {
|
||||
const results = new Map<string, ToolResultMessage>();
|
||||
for (const m of messages) {
|
||||
if ((m as { role?: string }).role === "toolResult") {
|
||||
results.set((m as ToolResultMessage).toolCallId, m as ToolResultMessage);
|
||||
}
|
||||
}
|
||||
const out: ToolPartView[] = [];
|
||||
for (const message of messages) {
|
||||
for (const part of message.parts) {
|
||||
if (!isToolUIPart(part)) continue;
|
||||
for (const m of 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; name?: string; arguments?: unknown }>) {
|
||||
if (block.type !== "toolCall" || typeof block.id !== "string") continue;
|
||||
const result = results.get(block.id);
|
||||
out.push({
|
||||
name: part.type.slice("tool-".length),
|
||||
toolCallId: part.toolCallId,
|
||||
state: part.state,
|
||||
input: part.input,
|
||||
output: part.output,
|
||||
name: block.name ?? "",
|
||||
toolCallId: block.id,
|
||||
state: result ? (result.isError ? "output-error" : "output-available") : "input-available",
|
||||
input: block.arguments,
|
||||
output: result && !result.isError ? result.details : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user