Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default [
"*.min.js",
"*.config.js",
".github/",
"src/schema.ts",
],
},
js.configs.recommended,
Expand Down
2,412 changes: 817 additions & 1,595 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"scripts": {
"clean": "rm -rf dist tsconfig.tsbuildinfo",
"test": "vitest run",
"generate": "node scripts/generate.js && npm run format",
"generate": "node scripts/generate.js",
"build": "npm run generate && tsc",
"format": "prettier --write .",
"format:check": "prettier --check .",
Expand All @@ -43,24 +43,24 @@
"docs:ts:dev": "concurrently \"cd src && typedoc --watch --preserveWatchOutput\" \"npx http-server src/docs -p 8081\"",
"docs:ts:verify": "cd src && typedoc --emit none && echo 'TypeDoc verification passed'"
},
"dependencies": {
"zod": "^3.0.0"
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
"@types/node": "^24.9.1",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"@hey-api/openapi-ts": "^0.88.0",
"@types/node": "^24.10.1",
"@typescript-eslint/eslint-plugin": "^8.48.0",
"@typescript-eslint/parser": "^8.48.0",
"concurrently": "^9.2.1",
"eslint": "^9.38.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"http-server": "^14.1.1",
"json-schema-to-typescript": "^15.0.4",
"prettier": "^3.6.2",
"ts-to-zod": "^3.15.0",
"prettier": "^3.7.1",
"tsx": "^4.20.6",
"typedoc": "^0.28.14",
"typedoc-github-theme": "^0.3.1",
"typescript": "^5.9.3",
"vitest": "^4.0.2"
"vitest": "^4.0.14",
"zod": "^3.25.0 || ^4.0.0"
}
}
1 change: 1 addition & 0 deletions schema/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"authenticate": "authenticate",
"initialize": "initialize",
"session_cancel": "session/cancel",
"session_list": "session/list",
"session_load": "session/load",
"session_new": "session/new",
"session_prompt": "session/prompt",
Expand Down
2,123 changes: 1,295 additions & 828 deletions schema/schema.json

Large diffs are not rendered by default.

139 changes: 74 additions & 65 deletions scripts/generate.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,75 @@
#!/usr/bin/env node

import { compile } from "json-schema-to-typescript";
import { generate } from "ts-to-zod";
import { createClient, defineConfig } from "@hey-api/openapi-ts";
import * as fs from "fs/promises";
import { dirname } from "path";
import * as prettier from "prettier";

const CURRENT_SCHEMA_RELEASE = "v0.6.3";
const CURRENT_SCHEMA_RELEASE = "v0.8.0";

await downloadSchemas(CURRENT_SCHEMA_RELEASE);
await main();

async function main() {
if (!process.argv.includes("--skip-download")) {
await downloadSchemas(CURRENT_SCHEMA_RELEASE);
}

const metadata = JSON.parse(await fs.readFile("./schema/meta.json", "utf8"));

const schemaSrc = await fs.readFile("./schema/schema.json", "utf8");
const jsonSchema = JSON.parse(
schemaSrc.replaceAll("#/$defs/", "#/components/schemas/"),
);
await createClient({
input: {
openapi: "3.1.0",
info: {
title: "Agent Client Protocol",
version: "1.0.0",
},
components: {
schemas: jsonSchema.$defs,
},
},
output: {
path: "./src/schema",
format: "prettier",
},
plugins: ["@hey-api/transformers", "@hey-api/typescript", "zod"],
});

const zodPath = "./src/schema/zod.gen.ts";
const zodSrc = await fs.readFile(zodPath, "utf8");
await fs.writeFile(
zodPath,
updateDocs(zodSrc.replace(`from "zod"`, `from "zod/v4"`)),
);

const tsPath = "./src/schema/types.gen.ts";
const tsSrc = await fs.readFile(tsPath, "utf8");
await fs.writeFile(
tsPath,
updateDocs(
tsSrc.replace(
`export type ClientOptions`,
`// eslint-disable-next-line @typescript-eslint/no-unused-vars\ntype ClientOptions`,
),
),
);

const meta = await prettier.format(
`export const AGENT_METHODS = ${JSON.stringify(metadata.agentMethods, null, 2)} as const;

export const CLIENT_METHODS = ${JSON.stringify(metadata.clientMethods, null, 2)} as const;

export const PROTOCOL_VERSION = ${metadata.version};
`,
{ parser: "typescript" },
);
const indexPath = "./src/schema/index.ts";
const indexSrc = await fs.readFile(indexPath, "utf8");
await fs.writeFile(indexPath, `${indexSrc}\n${meta}`);
}

/**
* Downloads a file from a URL to a local path
Expand Down Expand Up @@ -39,8 +101,8 @@ async function downloadFile(url, outputPath) {
async function downloadSchemas(tag) {
const baseUrl = `https://github.com/agentclientprotocol/agent-client-protocol/releases/download/${tag}`;
const files = [
{ url: `${baseUrl}/schema.json`, path: "./schema/schema.json" },
{ url: `${baseUrl}/meta.json`, path: "./schema/meta.json" },
{ url: `${baseUrl}/schema.unstable.json`, path: "./schema/schema.json" },
{ url: `${baseUrl}/meta.unstable.json`, path: "./schema/meta.json" },
];

console.log(`Downloading schemas from release ${tag}...`);
Expand All @@ -52,67 +114,14 @@ async function downloadSchemas(tag) {
console.log("Schema files downloaded successfully\n");
}

const jsonSchema = JSON.parse(
await fs.readFile("./schema/schema.json", "utf8"),
);
const metadata = JSON.parse(await fs.readFile("./schema/meta.json", "utf8"));

const tsSrc = await compile(jsonSchema, "Agent Client Protocol", {
additionalProperties: false,
bannerComment: false,
});

const zodGenerator = generate({
sourceText: tsSrc,
bannerComment: false,
keepComments: false,
});
const zodSchemas = zodGenerator.getZodSchemasFile();

const schemaTs = `
export const AGENT_METHODS = ${JSON.stringify(metadata.agentMethods, null, 2)} as const;

export const CLIENT_METHODS = ${JSON.stringify(metadata.clientMethods, null, 2)} as const;

export const PROTOCOL_VERSION = ${metadata.version};

import { z } from "zod";

${markSpecificTypesAsInternal(tsSrc)}

${markZodSchemasAsInternal(fixGeneratedZod(zodSchemas))}
`;

function fixGeneratedZod(src) {
return src
.replace(`// Generated by ts-to-zod\nimport { z } from "zod";\n`, "")
.replace(`import * as generated from "./zod";\n`, "")
.replace(/typeof generated./g, "typeof ");
}

function markSpecificTypesAsInternal(src) {
const typesToExclude = [
"AgentRequest",
"AgentResponse",
"AgentNotification",
"ClientRequest",
"ClientResponse",
"ClientNotification",
];

function updateDocs(src) {
let result = src;

for (const typeName of typesToExclude) {
const regex = new RegExp(`(export type ${typeName}\\b)`, "g");
result = result.replace(regex, "/** @internal */\n$1");
}
// Replace UNSTABLE comments with @experimental at the end of the comment block
result = result.replace(
/(\/\*\*[\s\S]*?\*\*UNSTABLE\*\*[\s\S]*?)(\n\s*)\*\//g,
"$1$2*$2* @experimental$2*/",
);

return result;
}

function markZodSchemasAsInternal(src) {
// Mark all zod schemas as internal - they're implementation details
return src.replace(/(export const \w+Schema = )/g, "/** @internal */\n$1");
}

await fs.writeFile("src/schema.ts", schemaTs, "utf8");
54 changes: 26 additions & 28 deletions src/acp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { z } from "zod";
import * as schema from "./schema.js";
export * from "./schema.js";
import * as schema from "./schema";
import * as validate from "./schema/zod.gen.js";
export * from "./schema";
export * from "./stream.js";

import type { Stream } from "./stream.js";
Expand Down Expand Up @@ -48,45 +49,43 @@ export class AgentSideConnection {
): Promise<unknown> => {
switch (method) {
case schema.AGENT_METHODS.initialize: {
const validatedParams = schema.initializeRequestSchema.parse(params);
const validatedParams = validate.zInitializeRequest.parse(params);
return agent.initialize(validatedParams);
}
case schema.AGENT_METHODS.session_new: {
const validatedParams = schema.newSessionRequestSchema.parse(params);
const validatedParams = validate.zNewSessionRequest.parse(params);
return agent.newSession(validatedParams);
}
case schema.AGENT_METHODS.session_load: {
if (!agent.loadSession) {
throw RequestError.methodNotFound(method);
}
const validatedParams = schema.loadSessionRequestSchema.parse(params);
const validatedParams = validate.zLoadSessionRequest.parse(params);
return agent.loadSession(validatedParams);
}
case schema.AGENT_METHODS.session_set_mode: {
if (!agent.setSessionMode) {
throw RequestError.methodNotFound(method);
}
const validatedParams =
schema.setSessionModeRequestSchema.parse(params);
const validatedParams = validate.zSetSessionModeRequest.parse(params);
const result = await agent.setSessionMode(validatedParams);
return result ?? {};
}
case schema.AGENT_METHODS.authenticate: {
const validatedParams =
schema.authenticateRequestSchema.parse(params);
const validatedParams = validate.zAuthenticateRequest.parse(params);
const result = await agent.authenticate(validatedParams);
return result ?? {};
}
case schema.AGENT_METHODS.session_prompt: {
const validatedParams = schema.promptRequestSchema.parse(params);
const validatedParams = validate.zPromptRequest.parse(params);
return agent.prompt(validatedParams);
}
case schema.AGENT_METHODS.session_set_model: {
if (!agent.setSessionModel) {
throw RequestError.methodNotFound(method);
}
const validatedParams =
schema.setSessionModelRequestSchema.parse(params);
validate.zSetSessionModelRequest.parse(params);
const result = await agent.setSessionModel(validatedParams);
return result ?? {};
}
Expand All @@ -110,7 +109,7 @@ export class AgentSideConnection {
): Promise<void> => {
switch (method) {
case schema.AGENT_METHODS.session_cancel: {
const validatedParams = schema.cancelNotificationSchema.parse(params);
const validatedParams = validate.zCancelNotification.parse(params);
return agent.cancel(validatedParams);
}
default:
Expand Down Expand Up @@ -379,7 +378,7 @@ export class TerminalHandle {
*
* Useful for implementing timeouts or cancellation.
*/
async kill(): Promise<schema.KillTerminalResponse> {
async kill(): Promise<schema.KillTerminalCommandResponse> {
return (
(await this.#connection.sendRequest(schema.CLIENT_METHODS.terminal_kill, {
sessionId: this.#sessionId,
Expand Down Expand Up @@ -451,44 +450,40 @@ export class ClientSideConnection implements Agent {
): Promise<unknown> => {
switch (method) {
case schema.CLIENT_METHODS.fs_write_text_file: {
const validatedParams =
schema.writeTextFileRequestSchema.parse(params);
const validatedParams = validate.zWriteTextFileRequest.parse(params);
return client.writeTextFile?.(validatedParams);
}
case schema.CLIENT_METHODS.fs_read_text_file: {
const validatedParams =
schema.readTextFileRequestSchema.parse(params);
const validatedParams = validate.zReadTextFileRequest.parse(params);
return client.readTextFile?.(validatedParams);
}
case schema.CLIENT_METHODS.session_request_permission: {
const validatedParams =
schema.requestPermissionRequestSchema.parse(params);
validate.zRequestPermissionRequest.parse(params);
return client.requestPermission(validatedParams);
}
case schema.CLIENT_METHODS.terminal_create: {
const validatedParams =
schema.createTerminalRequestSchema.parse(params);
const validatedParams = validate.zCreateTerminalRequest.parse(params);
return client.createTerminal?.(validatedParams);
}
case schema.CLIENT_METHODS.terminal_output: {
const validatedParams =
schema.terminalOutputRequestSchema.parse(params);
const validatedParams = validate.zTerminalOutputRequest.parse(params);
return client.terminalOutput?.(validatedParams);
}
case schema.CLIENT_METHODS.terminal_release: {
const validatedParams =
schema.releaseTerminalRequestSchema.parse(params);
validate.zReleaseTerminalRequest.parse(params);
const result = await client.releaseTerminal?.(validatedParams);
return result ?? {};
}
case schema.CLIENT_METHODS.terminal_wait_for_exit: {
const validatedParams =
schema.waitForTerminalExitRequestSchema.parse(params);
validate.zWaitForTerminalExitRequest.parse(params);
return client.waitForTerminalExit?.(validatedParams);
}
case schema.CLIENT_METHODS.terminal_kill: {
const validatedParams =
schema.killTerminalCommandRequestSchema.parse(params);
validate.zKillTerminalCommandRequest.parse(params);
const result = await client.killTerminal?.(validatedParams);
return result ?? {};
}
Expand All @@ -514,8 +509,7 @@ export class ClientSideConnection implements Agent {
): Promise<void> => {
switch (method) {
case schema.CLIENT_METHODS.session_update: {
const validatedParams =
schema.sessionNotificationSchema.parse(params);
const validatedParams = validate.zSessionNotification.parse(params);
return client.sessionUpdate(validatedParams);
}
default:
Expand Down Expand Up @@ -640,6 +634,8 @@ export class ClientSideConnection implements Agent {
* This capability is not part of the spec yet, and may be removed or changed at any point.
*
* Select a model for a given session.
*
* @experimental
*/
async setSessionModel(
params: schema.SetSessionModelRequest,
Expand Down Expand Up @@ -1296,7 +1292,7 @@ export interface Client {
*/
killTerminal?(
params: schema.KillTerminalCommandRequest,
): Promise<schema.KillTerminalResponse | void>;
): Promise<schema.KillTerminalCommandResponse | void>;

/**
* Extension method
Expand Down Expand Up @@ -1400,6 +1396,8 @@ export interface Agent {
* This capability is not part of the spec yet, and may be removed or changed at any point.
*
* Select a model for a given session.
*
* @experimental
*/
setSessionModel?(
params: schema.SetSessionModelRequest,
Expand Down
Loading