docs: document acp translator bridge

This commit is contained in:
Peter Steinberger
2026-06-04 05:31:15 -04:00
parent 61fdc7bf34
commit e112fb939a
26 changed files with 52 additions and 0 deletions
@@ -1,3 +1,4 @@
/** Shared ACP translator bridge request/event fixtures and assertions. */
import type {
LoadSessionRequest,
NewSessionRequest,
@@ -21,6 +22,7 @@ export function createNewSessionRequest(cwd = "/tmp"): NewSessionRequest {
} as unknown as NewSessionRequest;
}
/** Builds a minimal ACP load-session request for translator tests. */
export function createLoadSessionRequest(sessionId: string, cwd = "/tmp"): LoadSessionRequest {
return {
sessionId,
@@ -30,6 +32,7 @@ export function createLoadSessionRequest(sessionId: string, cwd = "/tmp"): LoadS
} as unknown as LoadSessionRequest;
}
/** Builds a minimal ACP prompt request for translator tests. */
export function createPromptRequest(
sessionId: string,
text: string,
@@ -42,6 +45,7 @@ export function createPromptRequest(
} as unknown as PromptRequest;
}
/** Builds a minimal ACP set-session-mode request for translator tests. */
export function createSetSessionModeRequest(
sessionId: string,
modeId: string,
@@ -53,6 +57,7 @@ export function createSetSessionModeRequest(
} as unknown as SetSessionModeRequest;
}
/** Builds a minimal ACP set-session-config-option request for translator tests. */
export function createSetSessionConfigOptionRequest(
sessionId: string,
configId: string,
@@ -66,6 +71,7 @@ export function createSetSessionConfigOptionRequest(
} as unknown as SetSessionConfigOptionRequest;
}
/** Builds a Gateway tool event fixture for translator tests. */
export function createToolEvent(params: {
sessionKey: string;
phase: "start" | "update" | "result";
@@ -94,6 +100,7 @@ export function createToolEvent(params: {
} as unknown as EventFrame;
}
/** Builds a Gateway final chat event fixture for translator tests. */
export function createChatFinalEvent(sessionKey: string): EventFrame {
return {
event: "chat",
@@ -1,3 +1,4 @@
/** Tests prompt cancellation scoping across concurrent ACP sessions and Gateway runs. */
import type { CancelNotification, PromptRequest, PromptResponse } from "@agentclientprotocol/sdk";
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
+1
View File
@@ -1,3 +1,4 @@
/** Tests Gateway errorKind to ACP stopReason mapping. */
import { describe, expect, it } from "vitest";
import {
createChatEvent,
+1
View File
@@ -1,3 +1,4 @@
/** Tests ACP translator replay ledger recording and load-session replay behavior. */
import type {
LoadSessionRequest,
NewSessionRequest,
@@ -1,3 +1,4 @@
/** Tests final Gateway snapshots are emitted before ACP prompt resolution. */
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
import type { EventFrame } from "../../packages/gateway-protocol/src/index.js";
+1
View File
@@ -1,3 +1,4 @@
/** Tests ACP translator initialize/session lifecycle and prompt bridge behavior. */
import type {
CloseSessionRequest,
InitializeRequest,
@@ -1,3 +1,4 @@
/** Tests ACP translator permission relay for Gateway exec approvals. */
import type { CancelNotification } from "@agentclientprotocol/sdk";
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
+7
View File
@@ -1,3 +1,4 @@
/** Builds ACP session presentation, metadata, usage, and config-option snapshots. */
import type {
InitializeRequest,
SessionConfigOption,
@@ -23,6 +24,7 @@ export const ACP_ELEVATED_LEVEL_CONFIG_ID = "elevated_level";
export const ACP_TIMEOUT_CONFIG_ID = "timeout";
export const ACP_TIMEOUT_SECONDS_CONFIG_ID = "timeout_seconds";
/** Normalized ACP client capability flags used to choose session controls. */
export type ClientCapabilityState = {
readTextFile: boolean;
writeTextFile: boolean;
@@ -61,27 +63,32 @@ export type GatewaySessionPresentationRow = Pick<
| "contextTokens"
>;
/** ACP session controls and modes shown to the client. */
export type SessionPresentation = {
configOptions: SessionConfigOption[];
modes: SessionModeState;
};
/** ACP session metadata plus lineage information. */
export type SessionMetadata = {
title?: string | null;
updatedAt?: string | null;
_meta?: AcpSessionLineageMeta;
};
/** Context/token usage snapshot for ACP clients that expose progress meters. */
export type SessionUsageSnapshot = {
size: number;
used: number;
};
/** Full session snapshot sent after load/list/prompt completion. */
export type SessionSnapshot = SessionPresentation & {
metadata?: SessionMetadata;
usage?: SessionUsageSnapshot;
};
/** Normalizes optional ACP initialize capabilities into stable booleans. */
export function normalizeClientCapabilities(
capabilities: InitializeRequest["clientCapabilities"] | undefined,
): ClientCapabilityState {
@@ -1,3 +1,4 @@
/** Prompt harness helpers for ACP translator lifecycle/cancel/stop-reason tests. */
import type { PromptRequest } from "@agentclientprotocol/sdk";
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { expect, vi } from "vitest";
@@ -17,6 +18,7 @@ const DEFAULT_SESSION_ID = "session-1";
export const DEFAULT_SESSION_KEY = "agent:main:main";
const DEFAULT_PROMPT_TEXT = "hello";
/** Creates an ACP translator instance with one preloaded session. */
export function createSessionAgentHarness(
request: GatewayClient["request"],
options: { sessionId?: string; sessionKey?: string; cwd?: string } = {},
@@ -41,6 +43,7 @@ export function createSessionAgentHarness(
};
}
/** Starts a prompt against a translator test agent. */
export function promptAgent(
agent: AcpGatewayAgent,
sessionId = DEFAULT_SESSION_ID,
@@ -53,6 +56,7 @@ export function promptAgent(
} as unknown as PromptRequest);
}
/** Observes prompt promise settlement without awaiting it immediately. */
export function observeSettlement(promise: ReturnType<AcpGatewayAgent["prompt"]>) {
const settleSpy = vi.fn();
void promise.then(
@@ -62,6 +66,7 @@ export function observeSettlement(promise: ReturnType<AcpGatewayAgent["prompt"]>
return settleSpy;
}
/** Starts a prompt that remains pending until tests inject Gateway events. */
export async function createPendingPromptHarness(): Promise<PendingPromptHarness> {
let runId: string | undefined;
const request = vi.fn(async (method: string, params?: Record<string, unknown>) => {
@@ -86,6 +91,7 @@ export async function createPendingPromptHarness(): Promise<PendingPromptHarness
};
}
/** Builds a Gateway chat event fixture for pending-prompt tests. */
export function createChatEvent(payload: Record<string, unknown>): EventFrame {
return {
type: "event",
+1
View File
@@ -1,3 +1,4 @@
/** Tests ACP prompt cwd-prefix provenance behavior. */
import os from "node:os";
import path from "node:path";
import type { PromptRequest } from "@agentclientprotocol/sdk";
+1
View File
@@ -1,3 +1,4 @@
/** Tests ACP translator prompt-size hardening. */
import { describe, it, vi } from "vitest";
import { expectOversizedPromptRejected } from "./translator.bridge-test-helpers.js";
+1
View File
@@ -1,3 +1,4 @@
/** Tests transcript replay conversion into ACP session update chunks. */
import { describe, expect, it } from "vitest";
import { extractReplayChunks } from "./translator.replay.js";
@@ -1,3 +1,4 @@
/** Tests ACP setSessionMode and setSessionConfigOption Gateway bridge behavior. */
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
import type { GatewayClient } from "../gateway/client.js";
@@ -1,3 +1,4 @@
/** Tests ACP session lineage metadata in list/load session responses. */
import type { ListSessionsRequest, LoadSessionRequest } from "@agentclientprotocol/sdk";
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
+1
View File
@@ -1,3 +1,4 @@
/** Tests ACP translator session-list cursor and page-size helpers. */
import { describe, expect, it } from "vitest";
import {
ACP_LIST_SESSIONS_MAX_FETCH_LIMIT,
+6
View File
@@ -1,3 +1,4 @@
/** Cursor and pagination helpers for ACP session/list requests. */
import path from "node:path";
import { readNumber } from "@openclaw/acp-core/meta";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
@@ -10,15 +11,18 @@ const ACP_LIST_SESSIONS_MAX_CURSOR_OFFSET = 10_000;
export const ACP_LIST_SESSIONS_MAX_FETCH_LIMIT =
ACP_LIST_SESSIONS_MAX_CURSOR_OFFSET + ACP_LIST_SESSIONS_MAX_PAGE_SIZE + 1;
/** Opaque cursor payload used to page ACP session list results. */
export type ListSessionsCursor = {
offset: number;
cwd?: string;
};
/** Encodes an ACP session-list cursor as base64url JSON. */
export function encodeListSessionsCursor(cursor: ListSessionsCursor): string {
return Buffer.from(JSON.stringify({ v: 1, ...cursor }), "utf8").toString("base64url");
}
/** Decodes and validates an ACP session-list cursor, defaulting to the first page. */
export function decodeListSessionsCursor(value: string | null | undefined): ListSessionsCursor {
if (!value) {
return { offset: 0 };
@@ -51,12 +55,14 @@ export function decodeListSessionsCursor(value: string | null | undefined): List
};
}
/** Throws when an ACP method receives a relative cwd filter/path. */
export function assertAbsoluteCwd(cwd: string, method: string): void {
if (!path.isAbsolute(cwd)) {
throw new Error(`ACP ${method} requires an absolute cwd.`);
}
}
/** Resolves requested ACP session-list page size with bridge limits. */
export function resolveListSessionsPageSize(
meta: Record<string, unknown> | null | undefined,
): number {
@@ -1,3 +1,4 @@
/** Tests ACP translator session creation rate limiting. */
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
import {
+1
View File
@@ -1,3 +1,4 @@
/** Tests ACP translator session setup constraints and initial updates. */
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
import type { GatewayClient } from "../gateway/client.js";
@@ -1,3 +1,4 @@
/** Tests ACP session metadata and usage snapshots after prompts. */
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
import type { GatewayClient } from "../gateway/client.js";
+3
View File
@@ -1,3 +1,4 @@
/** Emits ACP session updates and mirrors replayable updates into the event ledger. */
import type {
AgentSideConnection,
AvailableCommand,
@@ -6,6 +7,7 @@ import type {
} from "@agentclientprotocol/sdk";
import type { AcpEventLedger, AcpEventLedgerReplay } from "./event-ledger.js";
/** Session identity used when emitting and recording ACP translator updates. */
export type AcpTranslatorSessionRef = {
sessionId: string;
sessionKey: string;
@@ -28,6 +30,7 @@ function resolveLedgerSessionId(session: { sessionId: string; ledgerSessionId?:
return session.ledgerSessionId ?? session.sessionId;
}
/** Helper that keeps ACP client updates and replay ledger writes in sync. */
export class AcpTranslatorSessionUpdates {
constructor(private options: AcpTranslatorSessionUpdatesOptions) {}
@@ -1,3 +1,4 @@
/** Tests ACP setSessionMode request translation and error propagation. */
import type { SetSessionModeRequest } from "@agentclientprotocol/sdk";
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it } from "vitest";
+1
View File
@@ -1,3 +1,4 @@
/** Tests Gateway final/error states to ACP prompt stopReason mapping. */
import type { PromptRequest } from "@agentclientprotocol/sdk";
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
+2
View File
@@ -1,3 +1,4 @@
/** Shared mocked ACP connection and Gateway client helpers for translator tests. */
import type { AgentSideConnection } from "@agentclientprotocol/sdk";
import { vi } from "vitest";
import type { GatewayClient } from "../gateway/client.js";
@@ -24,6 +25,7 @@ export function createAcpConnection(
} as unknown as TestAcpConnection;
}
/** Creates a mocked Gateway client for translator tests. */
export function createAcpGateway(
request: GatewayClient["request"] = vi.fn(async () => ({ ok: true })) as GatewayClient["request"],
): GatewayClient {
@@ -1,3 +1,4 @@
/** Tests Gateway tool streaming to ACP tool-call update mapping. */
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
import { describe, expect, it, vi } from "vitest";
import type { GatewayClient } from "../gateway/client.js";
+2
View File
@@ -1,3 +1,4 @@
/** Agent Client Protocol bridge that translates ACP sessions/prompts to Gateway chat sessions. */
import { randomUUID } from "node:crypto";
import os from "node:os";
import type {
@@ -222,6 +223,7 @@ function hasExplicitSessionRouting(
);
}
/** ACP Agent implementation backed by the OpenClaw Gateway and replay ledger. */
export class AcpGatewayAgent implements Agent {
private connection: AgentSideConnection;
private gateway: GatewayClient;
+1
View File
@@ -1,3 +1,4 @@
/** ACP server option re-exports and OpenClaw agent identity metadata. */
export type { AcpProvenanceMode, AcpServerOptions, AcpSession } from "@openclaw/acp-core/types";
export { normalizeAcpProvenanceMode } from "@openclaw/acp-core/types";
import { VERSION } from "../version.js";