Skip to content

Commit 468054e

Browse files
authored
chore: Update to v0.8.0 of the JSON Schema (#19)
* chore: Update to v0.8.0 of the JSON Schema * Switch to @hey-api/openapi-ts for schema generation The old package was dropping stuff like discriminators, which made it unusable. For this, we pretend to be an OpenAPI spec and it works beautifully. * Add a --skip-download flag for local testing * Format non-generated output
1 parent fd28121 commit 468054e

File tree

11 files changed

+5632
-5676
lines changed

11 files changed

+5632
-5676
lines changed

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default [
1414
"*.min.js",
1515
"*.config.js",
1616
".github/",
17+
"src/schema.ts",
1718
],
1819
},
1920
js.configs.recommended,

package-lock.json

Lines changed: 817 additions & 1595 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"scripts": {
3131
"clean": "rm -rf dist tsconfig.tsbuildinfo",
3232
"test": "vitest run",
33-
"generate": "node scripts/generate.js && npm run format",
33+
"generate": "node scripts/generate.js",
3434
"build": "npm run generate && tsc",
3535
"format": "prettier --write .",
3636
"format:check": "prettier --check .",
@@ -43,24 +43,24 @@
4343
"docs:ts:dev": "concurrently \"cd src && typedoc --watch --preserveWatchOutput\" \"npx http-server src/docs -p 8081\"",
4444
"docs:ts:verify": "cd src && typedoc --emit none && echo 'TypeDoc verification passed'"
4545
},
46-
"dependencies": {
47-
"zod": "^3.0.0"
46+
"peerDependencies": {
47+
"zod": "^3.25.0 || ^4.0.0"
4848
},
4949
"devDependencies": {
50-
"@types/node": "^24.9.1",
51-
"@typescript-eslint/eslint-plugin": "^8.46.2",
52-
"@typescript-eslint/parser": "^8.46.2",
50+
"@hey-api/openapi-ts": "^0.88.0",
51+
"@types/node": "^24.10.1",
52+
"@typescript-eslint/eslint-plugin": "^8.48.0",
53+
"@typescript-eslint/parser": "^8.48.0",
5354
"concurrently": "^9.2.1",
54-
"eslint": "^9.38.0",
55+
"eslint": "^9.39.1",
5556
"eslint-config-prettier": "^10.1.8",
5657
"http-server": "^14.1.1",
57-
"json-schema-to-typescript": "^15.0.4",
58-
"prettier": "^3.6.2",
59-
"ts-to-zod": "^3.15.0",
58+
"prettier": "^3.7.1",
6059
"tsx": "^4.20.6",
6160
"typedoc": "^0.28.14",
6261
"typedoc-github-theme": "^0.3.1",
6362
"typescript": "^5.9.3",
64-
"vitest": "^4.0.2"
63+
"vitest": "^4.0.14",
64+
"zod": "^3.25.0 || ^4.0.0"
6565
}
6666
}

schema/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"authenticate": "authenticate",
44
"initialize": "initialize",
55
"session_cancel": "session/cancel",
6+
"session_list": "session/list",
67
"session_load": "session/load",
78
"session_new": "session/new",
89
"session_prompt": "session/prompt",

schema/schema.json

Lines changed: 1295 additions & 828 deletions
Large diffs are not rendered by default.

scripts/generate.js

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,75 @@
11
#!/usr/bin/env node
22

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

8-
const CURRENT_SCHEMA_RELEASE = "v0.6.3";
8+
const CURRENT_SCHEMA_RELEASE = "v0.8.0";
99

10-
await downloadSchemas(CURRENT_SCHEMA_RELEASE);
10+
await main();
11+
12+
async function main() {
13+
if (!process.argv.includes("--skip-download")) {
14+
await downloadSchemas(CURRENT_SCHEMA_RELEASE);
15+
}
16+
17+
const metadata = JSON.parse(await fs.readFile("./schema/meta.json", "utf8"));
18+
19+
const schemaSrc = await fs.readFile("./schema/schema.json", "utf8");
20+
const jsonSchema = JSON.parse(
21+
schemaSrc.replaceAll("#/$defs/", "#/components/schemas/"),
22+
);
23+
await createClient({
24+
input: {
25+
openapi: "3.1.0",
26+
info: {
27+
title: "Agent Client Protocol",
28+
version: "1.0.0",
29+
},
30+
components: {
31+
schemas: jsonSchema.$defs,
32+
},
33+
},
34+
output: {
35+
path: "./src/schema",
36+
format: "prettier",
37+
},
38+
plugins: ["@hey-api/transformers", "@hey-api/typescript", "zod"],
39+
});
40+
41+
const zodPath = "./src/schema/zod.gen.ts";
42+
const zodSrc = await fs.readFile(zodPath, "utf8");
43+
await fs.writeFile(
44+
zodPath,
45+
updateDocs(zodSrc.replace(`from "zod"`, `from "zod/v4"`)),
46+
);
47+
48+
const tsPath = "./src/schema/types.gen.ts";
49+
const tsSrc = await fs.readFile(tsPath, "utf8");
50+
await fs.writeFile(
51+
tsPath,
52+
updateDocs(
53+
tsSrc.replace(
54+
`export type ClientOptions`,
55+
`// eslint-disable-next-line @typescript-eslint/no-unused-vars\ntype ClientOptions`,
56+
),
57+
),
58+
);
59+
60+
const meta = await prettier.format(
61+
`export const AGENT_METHODS = ${JSON.stringify(metadata.agentMethods, null, 2)} as const;
62+
63+
export const CLIENT_METHODS = ${JSON.stringify(metadata.clientMethods, null, 2)} as const;
64+
65+
export const PROTOCOL_VERSION = ${metadata.version};
66+
`,
67+
{ parser: "typescript" },
68+
);
69+
const indexPath = "./src/schema/index.ts";
70+
const indexSrc = await fs.readFile(indexPath, "utf8");
71+
await fs.writeFile(indexPath, `${indexSrc}\n${meta}`);
72+
}
1173

1274
/**
1375
* Downloads a file from a URL to a local path
@@ -39,8 +101,8 @@ async function downloadFile(url, outputPath) {
39101
async function downloadSchemas(tag) {
40102
const baseUrl = `https://github.com/agentclientprotocol/agent-client-protocol/releases/download/${tag}`;
41103
const files = [
42-
{ url: `${baseUrl}/schema.json`, path: "./schema/schema.json" },
43-
{ url: `${baseUrl}/meta.json`, path: "./schema/meta.json" },
104+
{ url: `${baseUrl}/schema.unstable.json`, path: "./schema/schema.json" },
105+
{ url: `${baseUrl}/meta.unstable.json`, path: "./schema/meta.json" },
44106
];
45107

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

55-
const jsonSchema = JSON.parse(
56-
await fs.readFile("./schema/schema.json", "utf8"),
57-
);
58-
const metadata = JSON.parse(await fs.readFile("./schema/meta.json", "utf8"));
59-
60-
const tsSrc = await compile(jsonSchema, "Agent Client Protocol", {
61-
additionalProperties: false,
62-
bannerComment: false,
63-
});
64-
65-
const zodGenerator = generate({
66-
sourceText: tsSrc,
67-
bannerComment: false,
68-
keepComments: false,
69-
});
70-
const zodSchemas = zodGenerator.getZodSchemasFile();
71-
72-
const schemaTs = `
73-
export const AGENT_METHODS = ${JSON.stringify(metadata.agentMethods, null, 2)} as const;
74-
75-
export const CLIENT_METHODS = ${JSON.stringify(metadata.clientMethods, null, 2)} as const;
76-
77-
export const PROTOCOL_VERSION = ${metadata.version};
78-
79-
import { z } from "zod";
80-
81-
${markSpecificTypesAsInternal(tsSrc)}
82-
83-
${markZodSchemasAsInternal(fixGeneratedZod(zodSchemas))}
84-
`;
85-
86-
function fixGeneratedZod(src) {
87-
return src
88-
.replace(`// Generated by ts-to-zod\nimport { z } from "zod";\n`, "")
89-
.replace(`import * as generated from "./zod";\n`, "")
90-
.replace(/typeof generated./g, "typeof ");
91-
}
92-
93-
function markSpecificTypesAsInternal(src) {
94-
const typesToExclude = [
95-
"AgentRequest",
96-
"AgentResponse",
97-
"AgentNotification",
98-
"ClientRequest",
99-
"ClientResponse",
100-
"ClientNotification",
101-
];
102-
117+
function updateDocs(src) {
103118
let result = src;
104119

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

110126
return result;
111127
}
112-
113-
function markZodSchemasAsInternal(src) {
114-
// Mark all zod schemas as internal - they're implementation details
115-
return src.replace(/(export const \w+Schema = )/g, "/** @internal */\n$1");
116-
}
117-
118-
await fs.writeFile("src/schema.ts", schemaTs, "utf8");

src/acp.ts

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { z } from "zod";
2-
import * as schema from "./schema.js";
3-
export * from "./schema.js";
2+
import * as schema from "./schema";
3+
import * as validate from "./schema/zod.gen.js";
4+
export * from "./schema";
45
export * from "./stream.js";
56

67
import type { Stream } from "./stream.js";
@@ -48,45 +49,43 @@ export class AgentSideConnection {
4849
): Promise<unknown> => {
4950
switch (method) {
5051
case schema.AGENT_METHODS.initialize: {
51-
const validatedParams = schema.initializeRequestSchema.parse(params);
52+
const validatedParams = validate.zInitializeRequest.parse(params);
5253
return agent.initialize(validatedParams);
5354
}
5455
case schema.AGENT_METHODS.session_new: {
55-
const validatedParams = schema.newSessionRequestSchema.parse(params);
56+
const validatedParams = validate.zNewSessionRequest.parse(params);
5657
return agent.newSession(validatedParams);
5758
}
5859
case schema.AGENT_METHODS.session_load: {
5960
if (!agent.loadSession) {
6061
throw RequestError.methodNotFound(method);
6162
}
62-
const validatedParams = schema.loadSessionRequestSchema.parse(params);
63+
const validatedParams = validate.zLoadSessionRequest.parse(params);
6364
return agent.loadSession(validatedParams);
6465
}
6566
case schema.AGENT_METHODS.session_set_mode: {
6667
if (!agent.setSessionMode) {
6768
throw RequestError.methodNotFound(method);
6869
}
69-
const validatedParams =
70-
schema.setSessionModeRequestSchema.parse(params);
70+
const validatedParams = validate.zSetSessionModeRequest.parse(params);
7171
const result = await agent.setSessionMode(validatedParams);
7272
return result ?? {};
7373
}
7474
case schema.AGENT_METHODS.authenticate: {
75-
const validatedParams =
76-
schema.authenticateRequestSchema.parse(params);
75+
const validatedParams = validate.zAuthenticateRequest.parse(params);
7776
const result = await agent.authenticate(validatedParams);
7877
return result ?? {};
7978
}
8079
case schema.AGENT_METHODS.session_prompt: {
81-
const validatedParams = schema.promptRequestSchema.parse(params);
80+
const validatedParams = validate.zPromptRequest.parse(params);
8281
return agent.prompt(validatedParams);
8382
}
8483
case schema.AGENT_METHODS.session_set_model: {
8584
if (!agent.setSessionModel) {
8685
throw RequestError.methodNotFound(method);
8786
}
8887
const validatedParams =
89-
schema.setSessionModelRequestSchema.parse(params);
88+
validate.zSetSessionModelRequest.parse(params);
9089
const result = await agent.setSessionModel(validatedParams);
9190
return result ?? {};
9291
}
@@ -110,7 +109,7 @@ export class AgentSideConnection {
110109
): Promise<void> => {
111110
switch (method) {
112111
case schema.AGENT_METHODS.session_cancel: {
113-
const validatedParams = schema.cancelNotificationSchema.parse(params);
112+
const validatedParams = validate.zCancelNotification.parse(params);
114113
return agent.cancel(validatedParams);
115114
}
116115
default:
@@ -379,7 +378,7 @@ export class TerminalHandle {
379378
*
380379
* Useful for implementing timeouts or cancellation.
381380
*/
382-
async kill(): Promise<schema.KillTerminalResponse> {
381+
async kill(): Promise<schema.KillTerminalCommandResponse> {
383382
return (
384383
(await this.#connection.sendRequest(schema.CLIENT_METHODS.terminal_kill, {
385384
sessionId: this.#sessionId,
@@ -451,44 +450,40 @@ export class ClientSideConnection implements Agent {
451450
): Promise<unknown> => {
452451
switch (method) {
453452
case schema.CLIENT_METHODS.fs_write_text_file: {
454-
const validatedParams =
455-
schema.writeTextFileRequestSchema.parse(params);
453+
const validatedParams = validate.zWriteTextFileRequest.parse(params);
456454
return client.writeTextFile?.(validatedParams);
457455
}
458456
case schema.CLIENT_METHODS.fs_read_text_file: {
459-
const validatedParams =
460-
schema.readTextFileRequestSchema.parse(params);
457+
const validatedParams = validate.zReadTextFileRequest.parse(params);
461458
return client.readTextFile?.(validatedParams);
462459
}
463460
case schema.CLIENT_METHODS.session_request_permission: {
464461
const validatedParams =
465-
schema.requestPermissionRequestSchema.parse(params);
462+
validate.zRequestPermissionRequest.parse(params);
466463
return client.requestPermission(validatedParams);
467464
}
468465
case schema.CLIENT_METHODS.terminal_create: {
469-
const validatedParams =
470-
schema.createTerminalRequestSchema.parse(params);
466+
const validatedParams = validate.zCreateTerminalRequest.parse(params);
471467
return client.createTerminal?.(validatedParams);
472468
}
473469
case schema.CLIENT_METHODS.terminal_output: {
474-
const validatedParams =
475-
schema.terminalOutputRequestSchema.parse(params);
470+
const validatedParams = validate.zTerminalOutputRequest.parse(params);
476471
return client.terminalOutput?.(validatedParams);
477472
}
478473
case schema.CLIENT_METHODS.terminal_release: {
479474
const validatedParams =
480-
schema.releaseTerminalRequestSchema.parse(params);
475+
validate.zReleaseTerminalRequest.parse(params);
481476
const result = await client.releaseTerminal?.(validatedParams);
482477
return result ?? {};
483478
}
484479
case schema.CLIENT_METHODS.terminal_wait_for_exit: {
485480
const validatedParams =
486-
schema.waitForTerminalExitRequestSchema.parse(params);
481+
validate.zWaitForTerminalExitRequest.parse(params);
487482
return client.waitForTerminalExit?.(validatedParams);
488483
}
489484
case schema.CLIENT_METHODS.terminal_kill: {
490485
const validatedParams =
491-
schema.killTerminalCommandRequestSchema.parse(params);
486+
validate.zKillTerminalCommandRequest.parse(params);
492487
const result = await client.killTerminal?.(validatedParams);
493488
return result ?? {};
494489
}
@@ -514,8 +509,7 @@ export class ClientSideConnection implements Agent {
514509
): Promise<void> => {
515510
switch (method) {
516511
case schema.CLIENT_METHODS.session_update: {
517-
const validatedParams =
518-
schema.sessionNotificationSchema.parse(params);
512+
const validatedParams = validate.zSessionNotification.parse(params);
519513
return client.sessionUpdate(validatedParams);
520514
}
521515
default:
@@ -640,6 +634,8 @@ export class ClientSideConnection implements Agent {
640634
* This capability is not part of the spec yet, and may be removed or changed at any point.
641635
*
642636
* Select a model for a given session.
637+
*
638+
* @experimental
643639
*/
644640
async setSessionModel(
645641
params: schema.SetSessionModelRequest,
@@ -1296,7 +1292,7 @@ export interface Client {
12961292
*/
12971293
killTerminal?(
12981294
params: schema.KillTerminalCommandRequest,
1299-
): Promise<schema.KillTerminalResponse | void>;
1295+
): Promise<schema.KillTerminalCommandResponse | void>;
13001296

13011297
/**
13021298
* Extension method
@@ -1400,6 +1396,8 @@ export interface Agent {
14001396
* This capability is not part of the spec yet, and may be removed or changed at any point.
14011397
*
14021398
* Select a model for a given session.
1399+
*
1400+
* @experimental
14031401
*/
14041402
setSessionModel?(
14051403
params: schema.SetSessionModelRequest,

0 commit comments

Comments
 (0)