chore(vendor): import pi 0.85.1

Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
2026-09-14 10:48:47 -04:00
parent 5ebc7018c3
commit 4def357d09
1669 changed files with 397627 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
import { once } from "node:events";
import { createConnection, type Socket } from "node:net";
import type { JsonValue, ServiceCall } from "@earendil-works/chord";
import {
type ClientMessage,
encodeClientMessage,
PROTOCOL_VERSION,
type ResponseEnvelope,
type RpcTarget,
type ServerMessage,
ServerMessageDecoder,
} from "@earendil-works/pi-protocol";
import { Deferred } from "./host.ts";
interface MessageWaiter {
predicate: (message: ServerMessage) => boolean;
resolve: (message: ServerMessage) => void;
reject: (error: Error) => void;
}
export interface WireChannel {
send(chunk: Uint8Array): Promise<void>;
sendFragmented(chunk: Uint8Array, splitAt: number): Promise<void>;
close(): Promise<void>;
}
export class ProtocolTestClient {
readonly messages: ServerMessage[] = [];
private readonly channel: WireChannel;
private readonly decoder = new ServerMessageDecoder();
private readonly waiters = new Set<MessageWaiter>();
private readonly closedDeferred = new Deferred<void>();
private requestSequence = 0;
private attachment: { sessionId: string; attachmentId: string } | undefined;
private closedValue = false;
constructor(channel: WireChannel) {
this.channel = channel;
}
get closed(): boolean {
return this.closedValue;
}
hello(version: number = PROTOCOL_VERSION): Promise<ServerMessage> {
const response = this.next((message) => message.type === "hello" || message.type === "hello_error");
void this.sendMessage({ type: "hello", version });
return response;
}
async requestService(
target: RpcTarget,
call: ServiceCall,
id = `request-${++this.requestSequence}`,
): Promise<ResponseEnvelope> {
const response = this.next(
(message): message is ResponseEnvelope => message.type === "response" && message.id === id,
);
await this.sendMessage({ type: "request", id, target, call: call as unknown as JsonValue });
return (await response) as ResponseEnvelope;
}
attach(serverId: string, sessionId: string): Promise<ResponseEnvelope> {
return this.requestService(
{ serverId },
{ serviceId: "pi.session-management", member: "attach", args: [sessionId] },
);
}
requestSessionService(
serverId: string,
sessionId: string,
call: ServiceCall,
id?: string,
): Promise<ResponseEnvelope> {
const attachment = this.attachment;
const target: RpcTarget =
attachment === undefined || attachment.sessionId !== sessionId
? { serverId, sessionId, attachmentId: "missing-attachment" }
: { serverId, ...attachment };
return this.requestService(target, call, id);
}
sendMessage(message: ClientMessage): Promise<void> {
return this.channel.send(encodeClientMessage(message));
}
sendBytes(chunk: Uint8Array): Promise<void> {
return this.channel.send(chunk);
}
sendFragmentedMessage(message: ClientMessage, splitAt: number): Promise<void> {
return this.channel.sendFragmented(encodeClientMessage(message), splitAt);
}
next(predicate: (message: ServerMessage) => boolean): Promise<ServerMessage> {
return this.nextFrom(0, predicate);
}
nextFrom(index: number, predicate: (message: ServerMessage) => boolean): Promise<ServerMessage> {
const existing = this.messages.slice(index).find(predicate);
if (existing) return Promise.resolve(existing);
if (this.closedValue) return Promise.reject(new Error("Wire client is closed"));
return new Promise((resolve, reject) => this.waiters.add({ predicate, resolve, reject }));
}
waitForClose(): Promise<void> {
return this.closedValue ? Promise.resolve() : this.closedDeferred.promise;
}
close(): Promise<void> {
return this.channel.close();
}
receive(chunk: Uint8Array): void {
try {
for (const message of this.decoder.push(chunk)) {
if (message.type === "attachment") {
this.attachment =
message.attachment === null
? undefined
: {
sessionId: message.attachment.sessionId,
attachmentId: message.attachment.attachmentId,
};
}
this.messages.push(message);
for (const waiter of this.waiters) {
if (!waiter.predicate(message)) continue;
this.waiters.delete(waiter);
waiter.resolve(message);
}
}
} catch (error) {
this.fail(error instanceof Error ? error : new Error(String(error)));
}
}
markClosed(): void {
if (this.closedValue) return;
this.closedValue = true;
this.closedDeferred.resolve(undefined);
this.fail(new Error("Wire connection closed"));
}
fail(error: Error): void {
for (const waiter of this.waiters) waiter.reject(error);
this.waiters.clear();
}
}
export async function connectUnixTestClient(path: string): Promise<ProtocolTestClient> {
const socket = createConnection(path);
await once(socket, "connect");
const client = new ProtocolTestClient({
send: (chunk) => writeSocket(socket, chunk),
async sendFragmented(chunk, splitAt) {
await writeSocket(socket, chunk.subarray(0, splitAt));
await writeSocket(socket, chunk.subarray(splitAt));
},
async close() {
if (socket.destroyed) return;
const closed = once(socket, "close");
socket.destroy();
await closed;
},
});
socket.on("data", (chunk) => {
client.receive(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
});
socket.on("error", (error) => client.fail(error));
socket.once("close", () => client.markClosed());
return client;
}
function writeSocket(socket: Socket, chunk: Uint8Array): Promise<void> {
return new Promise<void>((resolve, reject) => {
socket.write(chunk, (error) => {
if (error) reject(error);
else resolve();
});
});
}

View File

@@ -0,0 +1,215 @@
import type { JsonValue, ServiceCall } from "@earendil-works/chord";
import type { Context, Session, SessionMetadata } from "@earendil-works/pi-agent-core";
import { BACKGROUND_CONTEXT, MemorySessionRepo } from "@earendil-works/pi-agent-core";
import { SessionAmbiguousError, SessionNotFoundError } from "../errors.ts";
import type { RoutedServerServiceHost, RoutedSessionHandle, ServerHost } from "../types.ts";
export class Deferred<T> {
readonly promise: Promise<T>;
private resolvePromise!: (value: T) => void;
constructor() {
this.promise = new Promise<T>((resolve) => {
this.resolvePromise = resolve;
});
}
resolve(value: T): void {
this.resolvePromise(value);
}
}
interface OpenGate {
entered: Deferred<void>;
release: Deferred<void>;
}
export class TestHarness {
readonly session: Session;
readonly closed = new Deferred<void>();
readonly #termination = new Deferred<Error | undefined>();
readonly terminated = this.#termination.promise;
attachedClients = 0;
attachmentReleaseCount = 0;
closeCount = 0;
readonly serviceCalls: ServiceCall[] = [];
failAttachmentRelease?: Error;
failClose?: Error;
nextServiceError?: Error;
nextServiceResult: JsonValue | undefined = { ok: true };
private nextCloseGate?: OpenGate;
private nextServiceGate?: OpenGate;
constructor(session: Session) {
this.session = session;
}
attachClient(_context: Context): {
invokeService: TestHarness["invokeService"];
release(context: Context): void;
} {
this.attachedClients += 1;
let released = false;
return {
invokeService: (call) => this.invokeService(call),
release: (_context) => {
if (released) return;
this.attachmentReleaseCount += 1;
if (this.failAttachmentRelease) throw this.failAttachmentRelease;
released = true;
this.attachedClients -= 1;
},
};
}
async invokeService(call: ServiceCall): Promise<JsonValue | undefined> {
this.serviceCalls.push(call);
if (this.nextServiceError) {
const error = this.nextServiceError;
this.nextServiceError = undefined;
throw error;
}
const gate = this.nextServiceGate;
if (gate) {
this.nextServiceGate = undefined;
gate.entered.resolve(undefined);
await gate.release.promise;
}
const result = this.nextServiceResult;
this.nextServiceResult = { ok: true };
return result;
}
async close(context: Context): Promise<void> {
this.closeCount += 1;
const gate = this.nextCloseGate;
if (gate) {
this.nextCloseGate = undefined;
gate.entered.resolve(undefined);
await gate.release.promise;
}
if (this.failClose) {
const error = this.failClose;
this.failClose = undefined;
throw error;
}
await this.session.close(context);
this.closed.resolve(undefined);
this.#termination.resolve(undefined);
}
async terminate(error: Error): Promise<void> {
await this.session.close(BACKGROUND_CONTEXT);
this.#termination.resolve(error);
}
gateNextClose(): OpenGate {
const gate = { entered: new Deferred<void>(), release: new Deferred<void>() };
this.nextCloseGate = gate;
return gate;
}
gateNextServiceCall(): OpenGate {
const gate = { entered: new Deferred<void>(), release: new Deferred<void>() };
this.nextServiceGate = gate;
return gate;
}
}
export function createTestServerServices(): RoutedServerServiceHost {
return {
attachClient(presentation) {
return {
async invokeService(call, _publish, context) {
if (
call.instance === undefined &&
call.serviceId === "pi.session-management" &&
call.member === "attach" &&
call.args.length === 1 &&
typeof call.args[0] === "string"
) {
await presentation.attachSession(call.args[0], context);
return null;
}
if (
call.instance === undefined &&
call.serviceId === "pi.session-management" &&
call.member === "detach" &&
call.args.length === 0
) {
await presentation.detachSession(context);
return null;
}
throw new Error(`Unsupported test server service ${call.serviceId}.${call.member}`);
},
release() {},
};
},
};
}
export class TestServerHost implements ServerHost {
readonly serverServices = createTestServerServices();
readonly repo = new MemorySessionRepo({ now: () => 1 });
readonly harnesses = new Map<string, TestHarness[]>();
openSessionCount = 0;
nextOpenSessionError?: Error;
nextHarnessCloseError?: Error;
private nextOpenSessionGate?: OpenGate;
async resolveSession(sessionId: string, context: Context): Promise<SessionMetadata> {
const matches = (await this.repo.list(undefined, context)).filter(({ id }) => id === sessionId);
if (matches.length === 0) throw new SessionNotFoundError(`Unknown session: ${sessionId}`);
if (matches.length > 1) throw new SessionAmbiguousError();
return matches[0]!;
}
async openSession(metadata: SessionMetadata, context: Context): Promise<RoutedSessionHandle> {
this.openSessionCount += 1;
const gate = this.nextOpenSessionGate;
if (gate) {
this.nextOpenSessionGate = undefined;
gate.entered.resolve(undefined);
await gate.release.promise;
}
const session = await this.repo.open(metadata, context);
try {
if (this.nextOpenSessionError) {
const error = this.nextOpenSessionError;
this.nextOpenSessionError = undefined;
throw error;
}
const harness = new TestHarness(session);
if (this.nextHarnessCloseError) {
harness.failClose = this.nextHarnessCloseError;
this.nextHarnessCloseError = undefined;
}
const harnesses = this.harnesses.get(metadata.id) ?? [];
harnesses.push(harness);
this.harnesses.set(metadata.id, harnesses);
return harness;
} catch (error) {
await session.close(context);
throw error;
}
}
async seed(id = "session-1", parentSessionId?: string): Promise<SessionMetadata> {
const session = await this.repo.create({ id, parentSessionId }, BACKGROUND_CONTEXT);
const metadata = session.metadata;
await session.close(BACKGROUND_CONTEXT);
return metadata;
}
gateNextOpenSession(): OpenGate {
const gate = { entered: new Deferred<void>(), release: new Deferred<void>() };
this.nextOpenSessionGate = gate;
return gate;
}
latestHarness(id: string): TestHarness {
const harnesses = this.harnesses.get(id);
if (!harnesses?.length) throw new Error(`No harness for ${id}`);
return harnesses.at(-1)!;
}
}

View File

@@ -0,0 +1,5 @@
export type { WireChannel } from "./client.ts";
export { connectUnixTestClient, ProtocolTestClient } from "./client.ts";
export { createTestServerServices, Deferred, TestHarness, TestServerHost } from "./host.ts";
export type { TestServer, TestServerOptions } from "./server.ts";
export { createTestServer } from "./server.ts";

View File

@@ -0,0 +1,28 @@
import { Server } from "../server.ts";
import type { ServerHost, ServerOptions } from "../types.ts";
import { TestServerHost } from "./host.ts";
export interface TestServerOptions extends Omit<ServerOptions, "serverId"> {
host?: ServerHost;
serverId?: string;
}
export interface TestServer {
server: Server;
host: ServerHost;
}
/** Create an unstarted Server with deterministic defaults for transport conformance tests. */
export function createTestServer(options: TestServerOptions): TestServer {
const host = options.host ?? new TestServerHost();
return {
server: new Server(host, {
listeners: options.listeners,
maxFrameLength: options.maxFrameLength,
handshakeTimeoutMs: options.handshakeTimeoutMs,
serverId: options.serverId ?? "00000000-0000-4000-8000-000000000001",
onError: options.onError,
}),
host,
};
}