mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix: remove dead experimental protocol code
This commit is contained in:
parent
6f6fa205ca
commit
fbf2db6353
16 changed files with 46 additions and 85 deletions
|
|
@ -5,10 +5,10 @@ import {
|
|||
defineFacet,
|
||||
type FacetHost,
|
||||
type FacetLoader,
|
||||
type JsonValue,
|
||||
type LoadedFacets,
|
||||
} from "@earendil-works/chord";
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import type { JsonValue } from "@earendil-works/pi-protocol";
|
||||
import {
|
||||
CombinedAutocompleteProvider,
|
||||
type Component,
|
||||
|
|
@ -331,7 +331,7 @@ export class ExperimentalClientTui implements Component {
|
|||
this.#selectedServerId = feature.serverId;
|
||||
this.#sessionId = prepared.summary.sessionId;
|
||||
this.#updateAutocomplete();
|
||||
await this.#openLane(feature, prepared.summary.sessionId);
|
||||
await this.#openLane(feature);
|
||||
this.#screen = "chat";
|
||||
this.#status = "";
|
||||
this.#rebuild();
|
||||
|
|
@ -464,9 +464,8 @@ export class ExperimentalClientTui implements Component {
|
|||
#handleAttachmentState(feature: SessionFeature, state: SessionAttachmentState): void {
|
||||
if (this.#closed || this.#selectedServerId !== feature.serverId || this.#sessionId === undefined) return;
|
||||
if (state.status === "attached" && state.sessionId === this.#sessionId) {
|
||||
const sessionId = this.#sessionId;
|
||||
this.#queueRecovery(async () => {
|
||||
if (this.#laneReplica === undefined) await this.#openLane(feature, sessionId);
|
||||
if (this.#laneReplica === undefined) await this.#openLane(feature);
|
||||
this.#busy = false;
|
||||
this.#status = "";
|
||||
this.#rebuild();
|
||||
|
|
@ -493,9 +492,9 @@ export class ExperimentalClientTui implements Component {
|
|||
});
|
||||
}
|
||||
|
||||
async #openLane(feature: SessionFeature, sessionId: string): Promise<void> {
|
||||
async #openLane(feature: SessionFeature): Promise<void> {
|
||||
await this.#closeLane();
|
||||
const replica = await openLaneReplica(feature.transcript, sessionId);
|
||||
const replica = await openLaneReplica(feature.transcript);
|
||||
const view = new ExperimentalChatView(this.#ui, process.cwd());
|
||||
view.apply(replica.state());
|
||||
this.#laneReplica = replica;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export async function runClient(command: ClientCommand, options: RunClientOption
|
|||
|
||||
const agent = match.agent;
|
||||
const completedText = new Map<string, string>();
|
||||
const replica = await openLaneReplica(match.transcript, sessionId, async (event) => {
|
||||
const replica = await openLaneReplica(match.transcript, async (event) => {
|
||||
if (event.type === "message_end" && event.runId !== undefined && event.message.role === "assistant") {
|
||||
completedText.set(event.runId, messageText(event.message));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import type { LaneSnapshot, LaneWatchEvent } from "@earendil-works/pi-agent-core
|
|||
import type { Transcript } from "./services/transcript.ts";
|
||||
|
||||
export interface LaneReplica {
|
||||
readonly sessionId: string;
|
||||
state(): LaneSnapshot;
|
||||
subscribe(listener: () => void): () => void;
|
||||
close(): Promise<void>;
|
||||
|
|
@ -11,7 +10,6 @@ export interface LaneReplica {
|
|||
/** Open a presentation-local replica from the Session's Transcript state. */
|
||||
export async function openLaneReplica(
|
||||
transcript: Transcript,
|
||||
sessionId: string,
|
||||
onEvent?: (event: LaneWatchEvent) => void | Promise<void>,
|
||||
): Promise<LaneReplica> {
|
||||
const listeners = new Set<() => void>();
|
||||
|
|
@ -51,7 +49,6 @@ export async function openLaneReplica(
|
|||
}
|
||||
|
||||
return {
|
||||
sessionId,
|
||||
state() {
|
||||
if (snapshot === undefined) throw new Error("Transcript replica has no snapshot");
|
||||
return snapshot;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { combineFacetLoaders, type FacetLoader } from "@earendil-works/chord";
|
||||
import { combineFacetLoaders, type FacetLoader, type JsonValue } from "@earendil-works/chord";
|
||||
import {
|
||||
createFacetBundleArtifactLoader,
|
||||
createFacetBundleLoader,
|
||||
type FacetBundleArtifact,
|
||||
readFacetBundleManifest,
|
||||
} from "@earendil-works/chord/node";
|
||||
import type { JsonValue } from "@earendil-works/pi-protocol";
|
||||
|
||||
const PRESENTATION_FACET_BUNDLES_KEY = "presentationFacetBundles";
|
||||
const PI_PLUGIN_API = "@earendil-works/pi-coding-agent/experimental/plugin";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { type Context, defineService } from "@earendil-works/chord";
|
||||
import type { JsonValue } from "@earendil-works/pi-protocol";
|
||||
import { type Context, defineService, type JsonValue } from "@earendil-works/chord";
|
||||
|
||||
/** Server-built plugin generations available to presentations. */
|
||||
export interface PresentationPlugins {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { type Context, RemoteServiceProvider, replicatedState, type ServiceSubscription } from "@earendil-works/chord";
|
||||
import {
|
||||
type Context,
|
||||
type JsonValue,
|
||||
RemoteServiceProvider,
|
||||
replicatedState,
|
||||
type ServiceSubscription,
|
||||
} from "@earendil-works/chord";
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { decodeServiceControlCall, type JsonValue } from "@earendil-works/pi-protocol";
|
||||
import { decodeServiceControlCall } from "@earendil-works/pi-protocol";
|
||||
import type { RoutedServerServiceAttachment, RoutedServerServiceHost } from "@earendil-works/pi-server";
|
||||
import { PresentationPlugins } from "./plugins.ts";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { defineFacet, type Facet } from "@earendil-works/chord";
|
||||
import { defineFacet, type Facet, type JsonValue } from "@earendil-works/chord";
|
||||
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
||||
import type { JsonValue } from "@earendil-works/pi-protocol";
|
||||
import { AgentController } from "./agent-controller.ts";
|
||||
import { type ModelSummary, Models, type Models as ModelsService } from "./models.ts";
|
||||
import { PresentationPlugins, SessionPlugins } from "./plugins.ts";
|
||||
|
|
|
|||
|
|
@ -5,19 +5,14 @@ import {
|
|||
defineFacet,
|
||||
type FacetHost,
|
||||
type FacetLoader,
|
||||
type ServiceCatalogueEntry,
|
||||
isJsonValue,
|
||||
type JsonValue,
|
||||
type ServiceProviderUpdate,
|
||||
type ServiceSubscription,
|
||||
} from "@earendil-works/chord";
|
||||
import type { AgentHarness, AgentLane } from "@earendil-works/pi-agent-core";
|
||||
import {
|
||||
decodeServiceControlCall,
|
||||
type JsonValue,
|
||||
JsonValueSchema,
|
||||
type ProtocolRpcCall,
|
||||
} from "@earendil-works/pi-protocol";
|
||||
import { decodeServiceControlCall, type ProtocolRpcCall } from "@earendil-works/pi-protocol";
|
||||
import Type, { type Static } from "typebox";
|
||||
import { Check } from "typebox/value";
|
||||
import type { ModelRuntime } from "../../core/model-runtime.ts";
|
||||
import type { SettingsManager } from "../../core/settings-manager.ts";
|
||||
import { AgentController } from "./agent-controller.ts";
|
||||
|
|
@ -26,8 +21,10 @@ import { createModelsServiceFacet } from "./models-provider.ts";
|
|||
import { SessionPlugins } from "./plugins.ts";
|
||||
import { createTranscriptServiceFacet } from "./transcript-provider.ts";
|
||||
|
||||
const OpaqueJsonValueSchema = Type.Unsafe<JsonValue>(Type.Unknown());
|
||||
|
||||
export const ServiceOperationResultSchema = Type.Object(
|
||||
{ result: Type.Optional(JsonValueSchema) },
|
||||
{ result: Type.Optional(OpaqueJsonValueSchema) },
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
export type ServiceOperationResult = Static<typeof ServiceOperationResultSchema>;
|
||||
|
|
@ -51,10 +48,8 @@ interface WorkerServiceSubscription {
|
|||
}
|
||||
|
||||
export interface SessionWorkerServices {
|
||||
readonly catalogue: readonly ServiceCatalogueEntry[];
|
||||
invoke(call: ProtocolRpcCall, scope: WorkerServiceScope, context: Context): Promise<JsonValue | undefined>;
|
||||
removeSubscriptions(matches: (scope: WorkerServiceScope) => boolean): void;
|
||||
reload(): Promise<void>;
|
||||
dispose(): Promise<void>;
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +125,6 @@ export async function createSessionWorkerServices(options: {
|
|||
};
|
||||
|
||||
return {
|
||||
catalogue: provider.catalogue,
|
||||
async invoke(call, scope, context) {
|
||||
const controlCall = decodeServiceControlCall(call);
|
||||
if (controlCall?.type === "catalogue") return toProtocolJsonValue(provider.catalogue);
|
||||
|
|
@ -155,7 +149,6 @@ export async function createSessionWorkerServices(options: {
|
|||
return provider.invoke(call, context);
|
||||
},
|
||||
removeSubscriptions,
|
||||
reload: () => reloadPlugins(),
|
||||
async dispose() {
|
||||
removeSubscriptions(() => true);
|
||||
await reloadTail;
|
||||
|
|
@ -178,6 +171,6 @@ function scopedSubscriptionKey(scope: WorkerServiceScope, subscriptionId: string
|
|||
}
|
||||
|
||||
function toProtocolJsonValue(value: unknown): JsonValue {
|
||||
if (!Check(JsonValueSchema, value)) throw new Error("Service produced invalid JSON");
|
||||
if (!isJsonValue(value)) throw new Error("Service produced invalid JSON");
|
||||
return value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ describe("experimental lane replica", () => {
|
|||
const staleEvent = { type: "run_start", lane: "main", runId: "stale", startedAt: 1 } as const;
|
||||
const state = replicatedState<TranscriptState>({ snapshot: snapshot(), event: staleEvent });
|
||||
const onEvent = vi.fn();
|
||||
const replica = await openLaneReplica({ state }, "session-1", onEvent);
|
||||
const replica = await openLaneReplica({ state }, onEvent);
|
||||
const changed = vi.fn();
|
||||
replica.subscribe(changed);
|
||||
expect(onEvent).not.toHaveBeenCalled();
|
||||
|
|
@ -57,7 +57,7 @@ describe("experimental lane replica", () => {
|
|||
|
||||
test("waits for the first initialized transcript value", async () => {
|
||||
const state = replicatedState<TranscriptState>({ snapshot: null, event: null });
|
||||
const opening = openLaneReplica({ state }, "session-1");
|
||||
const opening = openLaneReplica({ state });
|
||||
state.state.snapshot = snapshot("ready");
|
||||
state.publish(BACKGROUND_CONTEXT);
|
||||
const replica = await opening;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { defineFacet, type FacetLoader } from "@earendil-works/chord";
|
||||
import type { AgentLane, LaneSnapshot } from "@earendil-works/pi-agent-core";
|
||||
import { type AgentLane, BACKGROUND_CONTEXT, type LaneSnapshot } from "@earendil-works/pi-agent-core";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { SessionPlugins } from "../src/experimental/services/plugins.ts";
|
||||
import { createSessionWorkerServices } from "../src/experimental/services/worker.ts";
|
||||
|
||||
describe("experimental plugin reload", () => {
|
||||
|
|
@ -78,7 +79,11 @@ describe("experimental plugin reload", () => {
|
|||
});
|
||||
try {
|
||||
expect(activations).toEqual([1]);
|
||||
await services.reload();
|
||||
await services.invoke(
|
||||
{ serviceId: SessionPlugins.id, member: "reload", args: [] },
|
||||
{ serverConnectionId: "server-1", attachmentId: "attachment-1" },
|
||||
BACKGROUND_CONTEXT,
|
||||
);
|
||||
expect(activations).toEqual([1, 2]);
|
||||
expect(disposals).toEqual([1]);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
import { isJsonValue } from "@earendil-works/chord";
|
||||
import { Check } from "typebox/value";
|
||||
import { decodeCbor, encodeCbor } from "./cbor/index.ts";
|
||||
import {
|
||||
assertCompleteFrame,
|
||||
DEFAULT_MAX_FRAME_LENGTH,
|
||||
encodeFrame,
|
||||
FrameDecoder,
|
||||
type FrameDecoderOptions,
|
||||
} from "./framing.ts";
|
||||
import { DEFAULT_MAX_FRAME_LENGTH, encodeFrame, FrameDecoder, type FrameDecoderOptions } from "./framing.ts";
|
||||
import {
|
||||
type ClientMessage,
|
||||
ClientMessageSchema,
|
||||
|
|
@ -51,9 +45,7 @@ function encodeProtocolMessage<T>(
|
|||
const validated = parse(value);
|
||||
try {
|
||||
const maxFrameLength = options?.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;
|
||||
const frame = encodeFrame(encodeCbor(validated, { maxByteLength: maxFrameLength }));
|
||||
assertCompleteFrame(frame, { maxFrameLength });
|
||||
return frame;
|
||||
return encodeFrame(encodeCbor(validated, { maxByteLength: maxFrameLength }));
|
||||
} catch (error) {
|
||||
if (error instanceof ProtocolValidationError) throw error;
|
||||
throw new ProtocolValidationError(`Unable to encode ${kind} protocol message: ${boundedErrorMessage(error)}`);
|
||||
|
|
|
|||
|
|
@ -38,20 +38,6 @@ export function encodeFrame(payload: Uint8Array): Uint8Array {
|
|||
return frame;
|
||||
}
|
||||
|
||||
/** Validates that bytes contain exactly one complete frame within the configured limit. */
|
||||
export function assertCompleteFrame(frame: Uint8Array, options?: FrameDecoderOptions): void {
|
||||
if (!(frame instanceof Uint8Array)) throw new TypeError("Frame must be a Uint8Array");
|
||||
if (frame.byteLength < FRAME_HEADER_LENGTH) throw new FrameError("Frame does not contain a complete length prefix");
|
||||
const length = frame[0]! * 0x1_000_000 + frame[1]! * 0x1_0000 + frame[2]! * 0x100 + frame[3]!;
|
||||
const maxFrameLength = resolveMaxFrameLength(options);
|
||||
if (length > maxFrameLength) {
|
||||
throw new FrameError(`Frame length ${length} exceeds configured limit of ${maxFrameLength}`);
|
||||
}
|
||||
if (frame.byteLength !== FRAME_HEADER_LENGTH + length) {
|
||||
throw new FrameError("Frame must contain exactly one complete payload");
|
||||
}
|
||||
}
|
||||
|
||||
type DecoderState = "open" | "ended" | "failed";
|
||||
|
||||
/** Incrementally splits arbitrary byte chunks into length-prefixed payloads. */
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export * from "./cbor/index.ts";
|
||||
export * from "./codec.ts";
|
||||
export * from "./framing.ts";
|
||||
export * from "./json-value.ts";
|
||||
export {
|
||||
type AttachmentEnvelope,
|
||||
type CancelEnvelope,
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import type { JsonValue } from "@earendil-works/chord";
|
||||
import Type from "typebox";
|
||||
|
||||
/** Static JSON contract. Protocol codecs perform recursive validation at the wire boundary. */
|
||||
export const JsonValueSchema = Type.Unsafe<JsonValue>(Type.Unknown());
|
||||
|
||||
export type { JsonValue } from "@earendil-works/chord";
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import { isJsonValue } from "@earendil-works/chord";
|
||||
import { isJsonValue, type JsonValue } from "@earendil-works/chord";
|
||||
import type { WireOp } from "@earendil-works/chord/delta";
|
||||
import Type, { type Static } from "typebox";
|
||||
import { Check } from "typebox/value";
|
||||
import { type JsonValue, JsonValueSchema } from "./json-value.ts";
|
||||
|
||||
export const PROTOCOL_VERSION = 8 as const;
|
||||
|
||||
const OpaqueJsonValueSchema = Type.Unsafe<JsonValue>(Type.Unknown());
|
||||
|
||||
const IdSchema = Type.String({ minLength: 1 });
|
||||
const StrictObject = <const T extends Parameters<typeof Type.Object>[0]>(properties: T) =>
|
||||
Type.Object(properties, { additionalProperties: false });
|
||||
|
|
@ -45,7 +46,7 @@ export const ProtocolRpcCallSchema = StrictObject({
|
|||
serviceId: Type.String({ minLength: 1 }),
|
||||
instance: Type.Optional(ServiceInstanceAddressSchema),
|
||||
member: Type.String({ minLength: 1 }),
|
||||
args: Type.Array(JsonValueSchema),
|
||||
args: Type.Array(OpaqueJsonValueSchema),
|
||||
});
|
||||
export type ProtocolRpcCall = Static<typeof ProtocolRpcCallSchema>;
|
||||
export type ProtocolRpcResult = JsonValue | undefined;
|
||||
|
|
@ -58,10 +59,10 @@ const DeltaNonEmptyPathRefSchema = Type.Union([DeltaNonEmptyPathSchema, Type.Int
|
|||
const DeltaOpsSchema = Type.Array(
|
||||
Type.Unsafe<WireOp>(
|
||||
Type.Union([
|
||||
Type.Tuple([Type.Literal("r"), JsonValueSchema]),
|
||||
Type.Tuple([Type.Literal("r"), OpaqueJsonValueSchema]),
|
||||
Type.Tuple([Type.Literal("#"), Type.Integer({ minimum: 0 }), DeltaPathSchema]),
|
||||
Type.Tuple([Type.Literal("s"), DeltaNonEmptyPathRefSchema, JsonValueSchema]),
|
||||
Type.Tuple([Type.Literal("s"), JsonValueSchema]),
|
||||
Type.Tuple([Type.Literal("s"), DeltaNonEmptyPathRefSchema, OpaqueJsonValueSchema]),
|
||||
Type.Tuple([Type.Literal("s"), OpaqueJsonValueSchema]),
|
||||
Type.Tuple([Type.Literal("d"), DeltaNonEmptyPathRefSchema]),
|
||||
Type.Tuple([Type.Literal("d")]),
|
||||
Type.Tuple([Type.Literal("a"), DeltaNonEmptyPathRefSchema, Type.String()]),
|
||||
|
|
@ -73,13 +74,13 @@ const DeltaOpsSchema = Type.Array(
|
|||
DeltaPathRefSchema,
|
||||
Type.Integer({ minimum: 0 }),
|
||||
Type.Integer({ minimum: 0 }),
|
||||
Type.Array(JsonValueSchema),
|
||||
Type.Array(OpaqueJsonValueSchema),
|
||||
]),
|
||||
Type.Tuple([
|
||||
Type.Literal("p"),
|
||||
Type.Integer({ minimum: 0 }),
|
||||
Type.Integer({ minimum: 0 }),
|
||||
Type.Array(JsonValueSchema),
|
||||
Type.Array(OpaqueJsonValueSchema),
|
||||
]),
|
||||
]),
|
||||
),
|
||||
|
|
@ -279,7 +280,7 @@ const ResponseEnvelopeSchema = Type.Union([
|
|||
type: Type.Literal("response"),
|
||||
id: IdSchema,
|
||||
ok: Type.Literal(true),
|
||||
result: Type.Optional(JsonValueSchema),
|
||||
result: Type.Optional(OpaqueJsonValueSchema),
|
||||
}),
|
||||
StrictObject({
|
||||
type: Type.Literal("response"),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { assertCompleteFrame, DEFAULT_MAX_FRAME_LENGTH, encodeFrame, FrameDecoder, FrameError } from "../src/index.ts";
|
||||
import { DEFAULT_MAX_FRAME_LENGTH, encodeFrame, FrameDecoder, FrameError } from "../src/index.ts";
|
||||
|
||||
function concatenate(...chunks: Uint8Array[]): Uint8Array {
|
||||
const result = new Uint8Array(chunks.reduce((length, chunk) => length + chunk.byteLength, 0));
|
||||
|
|
@ -19,13 +19,6 @@ describe("binary framing", () => {
|
|||
expect(encodeFrame(new Uint8Array())).toEqual(new Uint8Array([0, 0, 0, 0]));
|
||||
});
|
||||
|
||||
test("validates one complete bounded frame without accepting trailing or partial bytes", () => {
|
||||
expect(() => assertCompleteFrame(new Uint8Array([0, 0, 0, 2, 1, 2]), { maxFrameLength: 2 })).not.toThrow();
|
||||
expect(() => assertCompleteFrame(new Uint8Array([0, 0, 0, 2, 1]))).toThrow(/complete/i);
|
||||
expect(() => assertCompleteFrame(new Uint8Array([0, 0, 0, 1, 1, 2]))).toThrow(/exactly/i);
|
||||
expect(() => assertCompleteFrame(new Uint8Array([0, 0, 0, 3, 1, 2, 3]), { maxFrameLength: 2 })).toThrow(/limit/i);
|
||||
});
|
||||
|
||||
test("decodes fragmented, coalesced, and empty frames in order", () => {
|
||||
const wire = concatenate(
|
||||
encodeFrame(new Uint8Array([1, 2, 3])),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue