import type { JsonValue } from "@getpaseo/protocol/agent-types"; import type { ProviderCatalog, ProviderCommand, ProviderConfigState, ProviderNotice, ProviderRegistration, ProviderTimelineItem, } from "./provider.js"; import { createAcpProviderConnection } from "./acp-internal/connection.js"; interface RunAcpProviderBaseOptions { id: string; label: string; description?: string; icon?: string; acpOptions?: AcpOptions; transformers?: readonly AcpTransformer[]; } export type RunAcpProviderOptions = RunAcpProviderBaseOptions & ( | { command: readonly [string, ...string[]]; connector?: never } | { connector: AcpConnector; command?: never } ); export type AcpStreamMessage = | { jsonrpc: "2.0"; id: string | number | null; method: string; params?: unknown } | { jsonrpc: "2.0"; method: string; params?: unknown } | { jsonrpc: "2.0"; id: string | number | null; result: unknown } | { jsonrpc: "2.0"; id: string | number | null; error: { code: number; message: string; data?: unknown }; }; export interface AcpStream { writable: WritableStream; readable: ReadableStream; } export type AcpConnector = () => AcpStream | Promise; export interface AcpOptions { protocol?: "auto" | "v1"; startupTimeoutMs?: number; waitForInitialCommands?: boolean; initialCommandsTimeoutMs?: number; } export interface AcpContext { sessionId: string; } export interface AcpConfigAccess { read(): Promise>>; set(id: string, value: JsonValue): Promise; } export interface AcpDiscoveryContext extends AcpContext { config: AcpConfigAccess; } export interface AcpConfigureContext extends AcpContext { config: AcpConfigAccess; } export type AcpConfigChange = | { target: "model"; value: string | null } | { target: "mode"; value: string | null } | { target: "thinking"; value: string | null } | { target: "setting"; id: string; value: JsonValue }; export interface AcpToolCallSnapshot { id: string; name?: string; title: string; kind?: string; status: "pending" | "in_progress" | "completed" | "failed"; input: JsonValue; output: JsonValue; locations: readonly string[]; } export type AcpVendorUpdate = | { type: "commands"; commands: readonly ProviderCommand[] } | { type: "config"; config: ProviderConfigState } | { type: "timeline"; item: ProviderTimelineItem } | { type: "notice"; notice: ProviderNotice }; export interface AcpTransformer { discover?( catalog: ProviderCatalog, context: AcpDiscoveryContext, ): Promise | ProviderCatalog; configure?(change: AcpConfigChange, context: AcpConfigureContext): Promise<"handled" | "pass">; notification?( notification: { method: string; params: JsonValue }, context: AcpContext, ): AcpVendorUpdate | readonly AcpVendorUpdate[] | null; toolCall?(toolCall: AcpToolCallSnapshot, context: AcpContext): AcpToolCallSnapshot; } export function runAcpProvider(options: RunAcpProviderOptions): ProviderRegistration { const hasCommand = options.command !== undefined; const hasConnector = options.connector !== undefined; if (hasCommand === hasConnector) { throw new Error("ACP provider requires exactly one command or SDK connector"); } if (options.command && (options.command.length === 0 || options.command[0].trim().length === 0)) { throw new Error("ACP provider command must contain an executable"); } return { id: options.id, label: options.label, description: options.description, icon: options.icon, command: options.command, connect(request) { return createAcpProviderConnection(options, request); }, }; }