@agentapplicationprotocol/client 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const client_1 = require("./client");
5
+ const BASE_URL = "https://example.com";
6
+ const API_KEY = "test-key";
7
+ function mockFetch(body, status = 200) {
8
+ return vitest_1.vi.fn().mockResolvedValue({
9
+ ok: status >= 200 && status < 300,
10
+ status,
11
+ statusText: "OK",
12
+ json: () => Promise.resolve(body),
13
+ text: () => Promise.resolve(String(body)),
14
+ body: null,
15
+ });
16
+ }
17
+ function mockSSEFetch(events, status = 200) {
18
+ const chunks = events.map(({ event, ...data }) => new TextEncoder().encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
19
+ let i = 0;
20
+ const body = {
21
+ getReader: () => ({
22
+ read: async () => i < chunks.length ? { done: false, value: chunks[i++] } : { done: true, value: undefined },
23
+ releaseLock: vitest_1.vi.fn(),
24
+ }),
25
+ };
26
+ return vitest_1.vi.fn().mockResolvedValue({
27
+ ok: status >= 200 && status < 300,
28
+ status,
29
+ body,
30
+ text: () => Promise.resolve(""),
31
+ });
32
+ }
33
+ let client;
34
+ (0, vitest_1.beforeEach)(() => {
35
+ client = new client_1.Client({ baseUrl: BASE_URL, apiKey: API_KEY });
36
+ });
37
+ (0, vitest_1.describe)("Client", () => {
38
+ (0, vitest_1.it)("strips trailing slash from baseUrl", () => {
39
+ const c = new client_1.Client({ baseUrl: BASE_URL + "/", apiKey: API_KEY });
40
+ const fetch = mockFetch({});
41
+ vitest_1.vi.stubGlobal("fetch", fetch);
42
+ c.getMeta();
43
+ (0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/meta`);
44
+ });
45
+ (0, vitest_1.it)("sends Authorization header", async () => {
46
+ const fetch = mockFetch({ version: 1, agents: [] });
47
+ vitest_1.vi.stubGlobal("fetch", fetch);
48
+ await client.getMeta();
49
+ (0, vitest_1.expect)(fetch.mock.calls[0][1].headers["Authorization"]).toBe(`Bearer ${API_KEY}`);
50
+ });
51
+ (0, vitest_1.it)("getMeta: GET /meta", async () => {
52
+ const meta = { version: 1, agents: [] };
53
+ vitest_1.vi.stubGlobal("fetch", mockFetch(meta));
54
+ (0, vitest_1.expect)(await client.getMeta()).toEqual(meta);
55
+ });
56
+ (0, vitest_1.it)("getSession: GET /session/:id", async () => {
57
+ const session = { sessionId: "s1", agent: { name: "a" } };
58
+ vitest_1.vi.stubGlobal("fetch", mockFetch(session));
59
+ (0, vitest_1.expect)(await client.getSession("s1")).toEqual(session);
60
+ });
61
+ (0, vitest_1.it)("getSession: appends ?history=compacted", async () => {
62
+ const fetch = mockFetch({ sessionId: "s1", agent: { name: "a" } });
63
+ vitest_1.vi.stubGlobal("fetch", fetch);
64
+ await client.getSession("s1", "compacted");
65
+ (0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/session/s1?history=compacted`);
66
+ });
67
+ (0, vitest_1.it)("getSession: appends ?history=full", async () => {
68
+ const fetch = mockFetch({ sessionId: "s1", agent: { name: "a" } });
69
+ vitest_1.vi.stubGlobal("fetch", fetch);
70
+ await client.getSession("s1", "full");
71
+ (0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/session/s1?history=full`);
72
+ });
73
+ (0, vitest_1.it)("listSessions: GET /sessions without cursor", async () => {
74
+ const res = { sessions: ["s1"] };
75
+ const fetch = mockFetch(res);
76
+ vitest_1.vi.stubGlobal("fetch", fetch);
77
+ await client.listSessions();
78
+ (0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/sessions`);
79
+ });
80
+ (0, vitest_1.it)("listSessions: appends after param", async () => {
81
+ const fetch = mockFetch({ sessions: [] });
82
+ vitest_1.vi.stubGlobal("fetch", fetch);
83
+ await client.listSessions({ after: "cursor1" });
84
+ (0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/sessions?after=cursor1`);
85
+ });
86
+ (0, vitest_1.it)("listAllSessions: paginates until no next", async () => {
87
+ const fetch = vitest_1.vi
88
+ .fn()
89
+ .mockResolvedValueOnce({
90
+ ok: true,
91
+ status: 200,
92
+ json: () => Promise.resolve({ sessions: ["s1"], next: "c1" }),
93
+ })
94
+ .mockResolvedValueOnce({
95
+ ok: true,
96
+ status: 200,
97
+ json: () => Promise.resolve({ sessions: ["s2"] }),
98
+ });
99
+ vitest_1.vi.stubGlobal("fetch", fetch);
100
+ (0, vitest_1.expect)(await client.listAllSessions()).toEqual(["s1", "s2"]);
101
+ (0, vitest_1.expect)(fetch).toHaveBeenCalledTimes(2);
102
+ });
103
+ (0, vitest_1.it)("deleteSession: DELETE /session/:id returns void on 204", async () => {
104
+ vitest_1.vi.stubGlobal("fetch", vitest_1.vi.fn().mockResolvedValue({ ok: true, status: 204 }));
105
+ await (0, vitest_1.expect)(client.deleteSession("s1")).resolves.toBeUndefined();
106
+ });
107
+ (0, vitest_1.it)("createSession: throws if last message is not a user message", () => {
108
+ vitest_1.vi.stubGlobal("fetch", mockFetch({}));
109
+ (0, vitest_1.expect)(() => client.createSession({
110
+ agent: { name: "a" },
111
+ messages: [{ role: "assistant", content: "hi" }],
112
+ })).toThrow("Last message must be a user message");
113
+ });
114
+ (0, vitest_1.it)("createSession: non-streaming returns AgentResponse", async () => {
115
+ const res = { sessionId: "s1", stopReason: "end_turn", messages: [] };
116
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res, 201));
117
+ const result = await client.createSession({
118
+ agent: { name: "a" },
119
+ messages: [{ role: "user", content: "hi" }],
120
+ });
121
+ (0, vitest_1.expect)(result).toEqual(res);
122
+ });
123
+ (0, vitest_1.it)("createSession: streaming returns SSE events", async () => {
124
+ const events = [
125
+ { event: "session_start", sessionId: "s1" },
126
+ { event: "turn_start" },
127
+ { event: "turn_stop", stopReason: "end_turn" },
128
+ ];
129
+ vitest_1.vi.stubGlobal("fetch", mockSSEFetch(events));
130
+ const stream = await client.createSession({
131
+ agent: { name: "a" },
132
+ messages: [{ role: "user", content: "hi" }],
133
+ stream: "message",
134
+ });
135
+ const received = [];
136
+ for await (const e of stream)
137
+ received.push(e);
138
+ (0, vitest_1.expect)(received).toEqual(events);
139
+ });
140
+ (0, vitest_1.it)("sendTurn: non-streaming returns AgentResponse", async () => {
141
+ const res = { stopReason: "end_turn", messages: [] };
142
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res));
143
+ const result = await client.sendTurn("s1", { messages: [{ role: "user", content: "hi" }] });
144
+ (0, vitest_1.expect)(result).toEqual(res);
145
+ });
146
+ (0, vitest_1.it)("sendTurn: streaming returns SSE events", async () => {
147
+ const events = [
148
+ { event: "turn_start" },
149
+ { event: "text", text: "hello" },
150
+ { event: "turn_stop", stopReason: "end_turn" },
151
+ ];
152
+ vitest_1.vi.stubGlobal("fetch", mockSSEFetch(events));
153
+ const stream = await client.sendTurn("s1", {
154
+ messages: [{ role: "user", content: "hi" }],
155
+ stream: "message",
156
+ });
157
+ const received = [];
158
+ for await (const e of stream)
159
+ received.push(e);
160
+ (0, vitest_1.expect)(received).toEqual(events);
161
+ });
162
+ (0, vitest_1.it)("streamRequest: throws ClientError on non-ok response", async () => {
163
+ vitest_1.vi.stubGlobal("fetch", vitest_1.vi.fn().mockResolvedValue({
164
+ ok: false,
165
+ status: 403,
166
+ body: null,
167
+ text: () => Promise.resolve("Forbidden"),
168
+ }));
169
+ await (0, vitest_1.expect)(client.createSession({
170
+ agent: { name: "a" },
171
+ messages: [{ role: "user", content: "hi" }],
172
+ stream: "delta",
173
+ })).rejects.toThrow(client_1.ClientError);
174
+ });
175
+ (0, vitest_1.it)("throws ClientError on non-ok response", async () => {
176
+ vitest_1.vi.stubGlobal("fetch", vitest_1.vi
177
+ .fn()
178
+ .mockResolvedValue({ ok: false, status: 401, text: () => Promise.resolve("Unauthorized") }));
179
+ await (0, vitest_1.expect)(client.getMeta()).rejects.toThrow(client_1.ClientError);
180
+ });
181
+ (0, vitest_1.it)("ClientError has correct properties", async () => {
182
+ vitest_1.vi.stubGlobal("fetch", vitest_1.vi
183
+ .fn()
184
+ .mockResolvedValue({ ok: false, status: 404, text: () => Promise.resolve("Not Found") }));
185
+ const err = await client.getSession("x").catch((e) => e);
186
+ (0, vitest_1.expect)(err).toBeInstanceOf(client_1.ClientError);
187
+ (0, vitest_1.expect)(err.status).toBe(404);
188
+ (0, vitest_1.expect)(err.method).toBe("GET");
189
+ (0, vitest_1.expect)(err.path).toBe("/session/x");
190
+ });
191
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const readline = __importStar(require("node:readline/promises"));
37
+ const node_process_1 = require("node:process");
38
+ const index_js_1 = require("../../index.js");
39
+ const BASE_URL = process.env.BASE_URL ?? "http://localhost:3010";
40
+ const API_KEY = process.env.API_KEY ?? "";
41
+ function extractText(content) {
42
+ if (!content)
43
+ return "";
44
+ if (typeof content === "string")
45
+ return content;
46
+ if (Array.isArray(content))
47
+ return content
48
+ .filter((b) => b.type === "text")
49
+ .map((b) => b.text)
50
+ .join("");
51
+ return String(content);
52
+ }
53
+ const prompts = [
54
+ "What is the capital of France?",
55
+ "What is 12 * 8?",
56
+ "Summarize what we discussed.",
57
+ ];
58
+ async function main() {
59
+ const client = new index_js_1.Client({ baseUrl: BASE_URL, apiKey: API_KEY });
60
+ const meta = await client.getMeta();
61
+ const agentInfo = meta.agents[0];
62
+ if (!agentInfo)
63
+ throw new Error("No agents available");
64
+ // Prompt for agent options
65
+ const options = {};
66
+ if (agentInfo.options?.length) {
67
+ const rl = readline.createInterface({ input: node_process_1.stdin, output: node_process_1.stdout });
68
+ for (const opt of agentInfo.options) {
69
+ const hint = opt.type === "secret" ? "(secret)" : `default: ${opt.default}`;
70
+ const answer = await rl.question(`${opt.title ?? opt.name} [${hint}]: `);
71
+ if (answer)
72
+ options[opt.name] = answer;
73
+ }
74
+ rl.close();
75
+ }
76
+ const { session } = await index_js_1.Session.create(client, {
77
+ agent: { name: agentInfo.name, options: Object.keys(options).length ? options : undefined },
78
+ messages: [{ role: "user", content: prompts[0] }],
79
+ }, agentInfo);
80
+ console.log(`\n[user] ${prompts[0]}`);
81
+ console.log(`[assistant] ${extractText(session.history.at(-1)?.content)}\n`);
82
+ for (const prompt of prompts.slice(1)) {
83
+ await session.send({ messages: [{ role: "user", content: prompt }] });
84
+ console.log(`[user] ${prompt}`);
85
+ console.log(`[assistant] ${extractText(session.history.at(-1)?.content)}\n`);
86
+ }
87
+ }
88
+ main().catch(console.error);
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AAP CLI — interactive chat client
4
+ *
5
+ * Usage: BASE_URL=http://localhost:3010 API_KEY=secret tsx src/examples/cli/index.ts [agent-name]
6
+ *
7
+ * Slash commands:
8
+ * /stream delta|message|none — set streaming mode
9
+ * /enable <tool> — enable a server or client tool
10
+ * /disable <tool> — disable a server or client tool
11
+ * /trust <tool> — trust a tool (server: run inline; client: auto-execute without confirmation)
12
+ * /set <option>=<value> — set an agent option
13
+ * /help — show this help
14
+ * /quit — exit
15
+ */
16
+ export {};
@@ -0,0 +1,355 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * AAP CLI — interactive chat client
5
+ *
6
+ * Usage: BASE_URL=http://localhost:3010 API_KEY=secret tsx src/examples/cli/index.ts [agent-name]
7
+ *
8
+ * Slash commands:
9
+ * /stream delta|message|none — set streaming mode
10
+ * /enable <tool> — enable a server or client tool
11
+ * /disable <tool> — disable a server or client tool
12
+ * /trust <tool> — trust a tool (server: run inline; client: auto-execute without confirmation)
13
+ * /set <option>=<value> — set an agent option
14
+ * /help — show this help
15
+ * /quit — exit
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ const readline = __importStar(require("node:readline/promises"));
52
+ const node_process_1 = require("node:process");
53
+ const client_js_1 = require("../../client.js");
54
+ const session_js_1 = require("../../session.js");
55
+ const BASE_URL = process.env.BASE_URL ?? "http://localhost:3010";
56
+ const API_KEY = process.env.API_KEY ?? "";
57
+ const client = new client_js_1.Client({ baseUrl: BASE_URL, apiKey: API_KEY });
58
+ // --- built-in client tools ---
59
+ const CLIENT_TOOLS = {
60
+ calculate: {
61
+ spec: {
62
+ name: "calculate",
63
+ description: "Evaluate a mathematical expression",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ expression: {
68
+ type: "string",
69
+ description: "Math expression to evaluate",
70
+ },
71
+ },
72
+ required: ["expression"],
73
+ },
74
+ },
75
+ exec: (input) => {
76
+ const { expression } = input;
77
+ try {
78
+ if (!/^[\d\s+\-*/().%^]+$/.test(expression))
79
+ return "Error: invalid expression";
80
+ return Function(`"use strict"; return (${expression})`)();
81
+ }
82
+ catch {
83
+ return "Error: could not evaluate expression";
84
+ }
85
+ },
86
+ },
87
+ };
88
+ // --- state ---
89
+ let streamMode = "delta";
90
+ const enabledTools = new Set(); // enabled server and client tools
91
+ const trustedTools = new Set(); // trusted: server tools run inline; client tools auto-execute
92
+ const agentOptions = {};
93
+ let rl;
94
+ function isClientTool(name) {
95
+ return name in CLIENT_TOOLS;
96
+ }
97
+ function printHelp(agent) {
98
+ console.log("Commands:");
99
+ console.log(" /stream delta|message|none");
100
+ console.log(" /enable <tool> — enable a server or client tool");
101
+ console.log(" /disable <tool> — disable a server or client tool");
102
+ console.log(" /trust <tool> — trust a tool (server: inline; client: auto-execute)");
103
+ console.log(" /set <option>=<value>");
104
+ console.log(" /help /quit");
105
+ if (agent.tools?.length)
106
+ console.log("Server tools:", agent.tools.map((t) => t.name).join(", "));
107
+ if (agent.options?.length)
108
+ console.log("Options:", agent.options.map((o) => `${o.name} (default: ${o.default})`).join(", "));
109
+ console.log("Client tools:", Object.keys(CLIENT_TOOLS).join(", "));
110
+ }
111
+ function handleCommand(line, agent) {
112
+ const [cmd, ...args] = line.slice(1).trim().split(/\s+/);
113
+ switch (cmd) {
114
+ case "stream": {
115
+ const mode = args[0];
116
+ if (!["delta", "message", "none"].includes(mode)) {
117
+ console.log("Usage: /stream delta|message|none");
118
+ }
119
+ else {
120
+ streamMode = mode;
121
+ console.log(`Stream mode: ${streamMode}`);
122
+ }
123
+ return true;
124
+ }
125
+ case "enable": {
126
+ const tool = args[0];
127
+ if (!tool) {
128
+ console.log("Usage: /enable <tool>");
129
+ return true;
130
+ }
131
+ enabledTools.add(tool);
132
+ console.log(`Enabled: ${tool}`);
133
+ return true;
134
+ }
135
+ case "disable": {
136
+ const tool = args[0];
137
+ if (!tool) {
138
+ console.log("Usage: /disable <tool>");
139
+ return true;
140
+ }
141
+ enabledTools.delete(tool);
142
+ console.log(`Disabled: ${tool}`);
143
+ return true;
144
+ }
145
+ case "trust": {
146
+ const tool = args[0];
147
+ if (!tool) {
148
+ console.log("Usage: /trust <tool>");
149
+ return true;
150
+ }
151
+ if (trustedTools.has(tool)) {
152
+ trustedTools.delete(tool);
153
+ console.log(`Untrusted: ${tool}`);
154
+ }
155
+ else {
156
+ trustedTools.add(tool);
157
+ console.log(`Trusted: ${tool}`);
158
+ }
159
+ return true;
160
+ }
161
+ case "set": {
162
+ const [key, ...rest] = args.join(" ").split("=");
163
+ const value = rest.join("=");
164
+ if (!key || value === undefined) {
165
+ console.log("Usage: /set <option>=<value>");
166
+ return true;
167
+ }
168
+ const name = key.trim();
169
+ if (!agent.options?.some((o) => o.name === name)) {
170
+ console.log(`Unknown option: ${name}`);
171
+ return true;
172
+ }
173
+ agentOptions[name] = value;
174
+ console.log(`Set ${name} = ${value}`);
175
+ return true;
176
+ }
177
+ case "help":
178
+ printHelp(agent);
179
+ return true;
180
+ case "quit":
181
+ process.exit(0);
182
+ }
183
+ return false;
184
+ }
185
+ let inDelta = false;
186
+ function sseCallback(e) {
187
+ if (e.event === "text_delta") {
188
+ process.stdout.write(e.delta);
189
+ inDelta = true;
190
+ }
191
+ else if (e.event === "thinking_delta") {
192
+ process.stdout.write(`\x1b[2m${e.delta}\x1b[0m`);
193
+ inDelta = true;
194
+ }
195
+ else {
196
+ if (inDelta) {
197
+ process.stdout.write("\n");
198
+ inDelta = false;
199
+ }
200
+ if (e.event === "text")
201
+ console.log(e.text);
202
+ else if (e.event === "thinking")
203
+ console.log(`\x1b[2m${e.thinking}\x1b[0m`);
204
+ else if (e.event === "tool_call")
205
+ console.log(`[tool call: ${e.name}(${JSON.stringify(e.input)})]`);
206
+ }
207
+ }
208
+ function printLastAssistant(session) {
209
+ if (streamMode !== "none")
210
+ return;
211
+ const last = session.history.at(-1);
212
+ if (last?.role !== "assistant")
213
+ return;
214
+ const text = typeof last.content === "string"
215
+ ? last.content
216
+ : last.content
217
+ .filter((b) => b.type === "text")
218
+ .map((b) => b.text)
219
+ .join("");
220
+ if (text)
221
+ console.log(`Assistant: ${text}`);
222
+ }
223
+ async function confirm(prompt) {
224
+ const answer = await rl.question(prompt);
225
+ return answer.toLowerCase().startsWith("y");
226
+ }
227
+ /** Resolve pending tool calls, prompting for untrusted ones, looping until none remain. */
228
+ async function resolvePending(session, pending) {
229
+ while (pending.client.length > 0 || pending.server.length > 0) {
230
+ const messages = [];
231
+ // client tools
232
+ for (const t of pending.client) {
233
+ const tool = CLIENT_TOOLS[t.name];
234
+ if (!tool) {
235
+ messages.push({
236
+ role: "tool",
237
+ toolCallId: t.toolCallId,
238
+ content: `Unknown tool: ${t.name}`,
239
+ });
240
+ continue;
241
+ }
242
+ if (!trustedTools.has(t.name) &&
243
+ !(await confirm(`Allow ${t.name}(${JSON.stringify(t.input)})? [y/N] `))) {
244
+ messages.push({
245
+ role: "tool",
246
+ toolCallId: t.toolCallId,
247
+ content: "Tool use denied by user",
248
+ });
249
+ continue;
250
+ }
251
+ const result = String(tool.exec(t.input));
252
+ console.log(`[${t.name} → ${result}]`);
253
+ messages.push({
254
+ role: "tool",
255
+ toolCallId: t.toolCallId,
256
+ content: result,
257
+ });
258
+ }
259
+ // untrusted server tools — prompt for permission
260
+ const permissions = [];
261
+ for (const t of pending.server) {
262
+ const granted = await confirm(`Allow server tool ${t.name}(${JSON.stringify(t.input)})? [y/N] `);
263
+ permissions.push({
264
+ role: "tool_permission",
265
+ toolCallId: t.toolCallId,
266
+ granted,
267
+ reason: granted ? undefined : "denied by user",
268
+ });
269
+ }
270
+ pending = await session.send({
271
+ messages: [...messages, ...permissions],
272
+ stream: streamMode === "none" ? undefined : streamMode,
273
+ }, sseCallback);
274
+ printLastAssistant(session);
275
+ }
276
+ }
277
+ function getServerTools(agentInfo) {
278
+ return agentInfo.tools
279
+ ?.filter((t) => enabledTools.has(t.name))
280
+ .map((t) => ({ name: t.name, trust: trustedTools.has(t.name) }));
281
+ }
282
+ function getClientTools() {
283
+ return Object.values(CLIENT_TOOLS)
284
+ .filter((t) => enabledTools.has(t.spec.name))
285
+ .map((t) => t.spec);
286
+ }
287
+ async function main() {
288
+ const meta = await client.getMeta();
289
+ const agentName = process.argv[2] ?? meta.agents[0]?.name;
290
+ const agentInfo = meta.agents.find((a) => a.name === agentName);
291
+ if (!agentInfo) {
292
+ console.error(`Agent "${agentName}" not found. Available: ${meta.agents.map((a) => a.name).join(", ")}`);
293
+ process.exit(1);
294
+ }
295
+ console.log(`Connected to ${agentInfo.title ?? agentInfo.name} (${agentInfo.version})`);
296
+ if (agentInfo.description)
297
+ console.log(agentInfo.description);
298
+ // enable all server and client tools by default
299
+ agentInfo.tools?.forEach((t) => enabledTools.add(t.name));
300
+ Object.keys(CLIENT_TOOLS).forEach((name) => enabledTools.add(name));
301
+ if (agentInfo.tools?.length)
302
+ console.log(`Server tools: ${agentInfo.tools.map((t) => t.name).join(", ")}`);
303
+ console.log(`Client tools: ${Object.keys(CLIENT_TOOLS).join(", ")}`);
304
+ if (agentInfo.options?.length)
305
+ console.log(`Options: ${agentInfo.options.map((o) => `${o.name}=${o.default}`).join(", ")}`);
306
+ console.log("Type /help for commands.\n");
307
+ rl = readline.createInterface({ input: node_process_1.stdin, output: node_process_1.stdout });
308
+ let firstInput;
309
+ while (true) {
310
+ firstInput = (await rl.question("You: ")).trim();
311
+ if (!firstInput)
312
+ continue;
313
+ if (firstInput.startsWith("/")) {
314
+ handleCommand(firstInput, agentInfo);
315
+ continue;
316
+ }
317
+ break;
318
+ }
319
+ const { session, pending } = await session_js_1.Session.create(client, {
320
+ agent: {
321
+ name: agentName,
322
+ tools: getServerTools(agentInfo),
323
+ options: Object.keys(agentOptions).length ? agentOptions : undefined,
324
+ },
325
+ tools: getClientTools(),
326
+ stream: streamMode === "none" ? undefined : streamMode,
327
+ messages: [{ role: "user", content: firstInput }],
328
+ }, agentInfo, sseCallback);
329
+ printLastAssistant(session);
330
+ await resolvePending(session, pending);
331
+ while (true) {
332
+ const input = (await rl.question("You: ")).trim();
333
+ if (!input)
334
+ continue;
335
+ if (input.startsWith("/")) {
336
+ handleCommand(input, agentInfo);
337
+ continue;
338
+ }
339
+ const turnPending = await session.send({
340
+ agent: {
341
+ tools: getServerTools(agentInfo),
342
+ options: Object.keys(agentOptions).length ? { ...agentOptions } : undefined,
343
+ },
344
+ tools: getClientTools(),
345
+ stream: streamMode === "none" ? undefined : streamMode,
346
+ messages: [{ role: "user", content: input }],
347
+ }, sseCallback);
348
+ printLastAssistant(session);
349
+ await resolvePending(session, turnPending);
350
+ }
351
+ }
352
+ main().catch((e) => {
353
+ console.error(e);
354
+ process.exit(1);
355
+ });
@@ -31,9 +31,10 @@ export declare class Session {
31
31
  }>;
32
32
  /**
33
33
  * Loads an existing session and resolves any pending tool use.
34
+ * @param agents - List of known agents to match against the session's agent name.
34
35
  * @returns The loaded session and any pending tool calls.
35
36
  */
36
- static load(client: Client, sessionId: string, agentInfo: AgentInfo, history?: "full" | "compacted"): Promise<{
37
+ static load(client: Client, sessionId: string, agents: AgentInfo[], history?: "full" | "compacted"): Promise<{
37
38
  session: Session;
38
39
  pending: PendingToolUse;
39
40
  }>;
@@ -45,10 +45,14 @@ class Session {
45
45
  }
46
46
  /**
47
47
  * Loads an existing session and resolves any pending tool use.
48
+ * @param agents - List of known agents to match against the session's agent name.
48
49
  * @returns The loaded session and any pending tool calls.
49
50
  */
50
- static async load(client, sessionId, agentInfo, history) {
51
+ static async load(client, sessionId, agents, history) {
51
52
  const res = await client.getSession(sessionId, history);
53
+ const agentInfo = agents.find((a) => a.name === res.agent.name);
54
+ if (!agentInfo)
55
+ throw new Error(`Unknown agent: ${res.agent.name}`);
52
56
  const session = new Session(sessionId, client, agentInfo, res.agent, res.tools);
53
57
  const h = history === "compacted" ? res.history?.compacted : res.history?.full;
54
58
  session.history.push(...(h ?? []));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const client_1 = require("./client");
5
+ const session_1 = require("./session");
6
+ const BASE_URL = "https://example.com";
7
+ const agentInfo = { name: "test-agent", version: "1.0.0" };
8
+ function mockFetch(body, status = 200) {
9
+ return vitest_1.vi.fn().mockResolvedValue({
10
+ ok: status >= 200 && status < 300,
11
+ status,
12
+ json: () => Promise.resolve(body),
13
+ text: () => Promise.resolve(String(body)),
14
+ body: null,
15
+ });
16
+ }
17
+ function mockSSEFetch(events) {
18
+ const chunks = events.map(({ event, ...data }) => new TextEncoder().encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
19
+ let i = 0;
20
+ const body = {
21
+ getReader: () => ({
22
+ read: async () => i < chunks.length ? { done: false, value: chunks[i++] } : { done: true, value: undefined },
23
+ releaseLock: vitest_1.vi.fn(),
24
+ }),
25
+ };
26
+ return vitest_1.vi
27
+ .fn()
28
+ .mockResolvedValue({ ok: true, status: 200, body, text: () => Promise.resolve("") });
29
+ }
30
+ let client;
31
+ (0, vitest_1.beforeEach)(() => {
32
+ client = new client_1.Client({ baseUrl: BASE_URL, apiKey: "key" });
33
+ });
34
+ (0, vitest_1.describe)("Session.load", () => {
35
+ (0, vitest_1.it)("returns session with correct sessionId and agentConfig", async () => {
36
+ const res = { sessionId: "s1", agent: { name: "test-agent" } };
37
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res));
38
+ const { session } = await session_1.Session.load(client, "s1", [agentInfo]);
39
+ (0, vitest_1.expect)(session.sessionId).toBe("s1");
40
+ (0, vitest_1.expect)(session.agentConfig).toEqual({ name: "test-agent" });
41
+ (0, vitest_1.expect)(session.agent).toBe(agentInfo);
42
+ });
43
+ (0, vitest_1.it)("populates history from full history", async () => {
44
+ const history = [{ role: "user", content: "hi" }];
45
+ const res = {
46
+ sessionId: "s1",
47
+ agent: { name: "test-agent" },
48
+ history: { full: history },
49
+ };
50
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res));
51
+ const { session } = await session_1.Session.load(client, "s1", [agentInfo], "full");
52
+ (0, vitest_1.expect)(session.history).toEqual(history);
53
+ });
54
+ (0, vitest_1.it)("populates history from compacted history", async () => {
55
+ const history = [{ role: "user", content: "summary" }];
56
+ const res = {
57
+ sessionId: "s1",
58
+ agent: { name: "test-agent" },
59
+ history: { compacted: history },
60
+ };
61
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res));
62
+ const { session } = await session_1.Session.load(client, "s1", [agentInfo], "compacted");
63
+ (0, vitest_1.expect)(session.history).toEqual(history);
64
+ });
65
+ (0, vitest_1.it)("returns empty history when no history in response", async () => {
66
+ const res = { sessionId: "s1", agent: { name: "test-agent" } };
67
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res));
68
+ const { session } = await session_1.Session.load(client, "s1", [agentInfo], "full");
69
+ (0, vitest_1.expect)(session.history).toEqual([]);
70
+ });
71
+ (0, vitest_1.it)("resolves pending client tool use from history", async () => {
72
+ const tools = [
73
+ {
74
+ name: "myTool",
75
+ description: "d",
76
+ inputSchema: { type: "object", properties: {} },
77
+ },
78
+ ];
79
+ const history = [
80
+ { role: "user", content: "go" },
81
+ {
82
+ role: "assistant",
83
+ content: [{ type: "tool_use", toolCallId: "tc1", name: "myTool", input: {} }],
84
+ },
85
+ ];
86
+ const res = {
87
+ sessionId: "s1",
88
+ agent: { name: "test-agent" },
89
+ tools,
90
+ history: { full: history },
91
+ };
92
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res));
93
+ const { pending } = await session_1.Session.load(client, "s1", [agentInfo], "full");
94
+ (0, vitest_1.expect)(pending.client).toEqual([{ toolCallId: "tc1", name: "myTool", input: {} }]);
95
+ (0, vitest_1.expect)(pending.server).toEqual([]);
96
+ });
97
+ (0, vitest_1.it)("calls getSession with correct history param", async () => {
98
+ const fetch = mockFetch({
99
+ sessionId: "s1",
100
+ agent: { name: "test-agent" },
101
+ });
102
+ vitest_1.vi.stubGlobal("fetch", fetch);
103
+ await session_1.Session.load(client, "s1", [agentInfo], "compacted");
104
+ (0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/session/s1?history=compacted`);
105
+ });
106
+ (0, vitest_1.it)("throws if agent name not found in list", async () => {
107
+ const res = { sessionId: "s1", agent: { name: "unknown-agent" } };
108
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res));
109
+ await (0, vitest_1.expect)(session_1.Session.load(client, "s1", [agentInfo])).rejects.toThrow("Unknown agent: unknown-agent");
110
+ });
111
+ });
112
+ (0, vitest_1.describe)("Session.create", () => {
113
+ (0, vitest_1.it)("non-streaming: builds session and history", async () => {
114
+ const res = {
115
+ sessionId: "s1",
116
+ stopReason: "end_turn",
117
+ messages: [{ role: "assistant", content: "hi" }],
118
+ };
119
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res, 201));
120
+ const { session, pending } = await session_1.Session.create(client, { agent: { name: "test-agent" }, messages: [{ role: "user", content: "hello" }] }, agentInfo);
121
+ (0, vitest_1.expect)(session.sessionId).toBe("s1");
122
+ (0, vitest_1.expect)(session.history).toHaveLength(2);
123
+ (0, vitest_1.expect)(pending).toEqual({ client: [], server: [] });
124
+ });
125
+ (0, vitest_1.it)("streaming: builds session from SSE events and calls cb", async () => {
126
+ const events = [
127
+ { event: "session_start", sessionId: "s2" },
128
+ { event: "turn_start" },
129
+ { event: "text", text: "hey" },
130
+ { event: "turn_stop", stopReason: "end_turn" },
131
+ ];
132
+ vitest_1.vi.stubGlobal("fetch", mockSSEFetch(events));
133
+ const cb = vitest_1.vi.fn();
134
+ const { session } = await session_1.Session.create(client, {
135
+ agent: { name: "test-agent" },
136
+ messages: [{ role: "user", content: "hello" }],
137
+ stream: "message",
138
+ }, agentInfo, cb);
139
+ (0, vitest_1.expect)(session.sessionId).toBe("s2");
140
+ (0, vitest_1.expect)(cb).toHaveBeenCalledTimes(events.length);
141
+ });
142
+ });
143
+ (0, vitest_1.describe)("Session.send", () => {
144
+ async function makeSession() {
145
+ const res = { sessionId: "s1", stopReason: "end_turn", messages: [] };
146
+ vitest_1.vi.stubGlobal("fetch", mockFetch(res, 201));
147
+ const { session } = await session_1.Session.create(client, { agent: { name: "test-agent" }, messages: [{ role: "user", content: "hi" }] }, agentInfo);
148
+ return session;
149
+ }
150
+ (0, vitest_1.it)("non-streaming: appends messages and returns pending", async () => {
151
+ const session = await makeSession();
152
+ const turnRes = {
153
+ stopReason: "end_turn",
154
+ messages: [{ role: "assistant", content: "reply" }],
155
+ };
156
+ vitest_1.vi.stubGlobal("fetch", mockFetch(turnRes));
157
+ const pending = await session.send({ messages: [{ role: "user", content: "next" }] });
158
+ (0, vitest_1.expect)(session.history).toHaveLength(3);
159
+ (0, vitest_1.expect)(pending).toEqual({ client: [], server: [] });
160
+ });
161
+ (0, vitest_1.it)("streaming: appends messages and calls cb", async () => {
162
+ const session = await makeSession();
163
+ const events = [
164
+ { event: "turn_start" },
165
+ { event: "text", text: "streamed" },
166
+ { event: "turn_stop", stopReason: "end_turn" },
167
+ ];
168
+ vitest_1.vi.stubGlobal("fetch", mockSSEFetch(events));
169
+ const cb = vitest_1.vi.fn();
170
+ await session.send({ messages: [{ role: "user", content: "next" }], stream: "message" }, cb);
171
+ (0, vitest_1.expect)(cb).toHaveBeenCalledTimes(events.length);
172
+ });
173
+ (0, vitest_1.it)("strips unchanged tools from request", async () => {
174
+ const session = await makeSession();
175
+ const tools = [
176
+ { name: "t", description: "d", inputSchema: { type: "object", properties: {} } },
177
+ ];
178
+ session.tools = tools;
179
+ const fetch = mockFetch({ stopReason: "end_turn", messages: [] });
180
+ vitest_1.vi.stubGlobal("fetch", fetch);
181
+ await session.send({ messages: [{ role: "user", content: "hi" }], tools });
182
+ const body = JSON.parse(fetch.mock.calls[0][1].body);
183
+ (0, vitest_1.expect)(body.tools).toBeUndefined();
184
+ });
185
+ (0, vitest_1.it)("sends changed tools and updates session.tools", async () => {
186
+ const session = await makeSession();
187
+ const newTools = [
188
+ { name: "t2", description: "d", inputSchema: { type: "object", properties: {} } },
189
+ ];
190
+ const fetch = mockFetch({ stopReason: "end_turn", messages: [] });
191
+ vitest_1.vi.stubGlobal("fetch", fetch);
192
+ await session.send({ messages: [{ role: "user", content: "hi" }], tools: newTools });
193
+ const body = JSON.parse(fetch.mock.calls[0][1].body);
194
+ (0, vitest_1.expect)(body.tools).toEqual(newTools);
195
+ (0, vitest_1.expect)(session.tools).toEqual(newTools);
196
+ });
197
+ (0, vitest_1.it)("sends only changed agent options", async () => {
198
+ const session = await makeSession();
199
+ session.agentConfig = { name: "test-agent", options: { model: "gpt-4" } };
200
+ const fetch = mockFetch({ stopReason: "end_turn", messages: [] });
201
+ vitest_1.vi.stubGlobal("fetch", fetch);
202
+ await session.send({
203
+ messages: [{ role: "user", content: "hi" }],
204
+ agent: { options: { model: "gpt-4o", temperature: "0.5" } },
205
+ });
206
+ const body = JSON.parse(fetch.mock.calls[0][1].body);
207
+ (0, vitest_1.expect)(body.agent.options).toEqual({ model: "gpt-4o", temperature: "0.5" });
208
+ });
209
+ (0, vitest_1.it)("omits agent if nothing changed", async () => {
210
+ const session = await makeSession();
211
+ const fetch = mockFetch({ stopReason: "end_turn", messages: [] });
212
+ vitest_1.vi.stubGlobal("fetch", fetch);
213
+ await session.send({ messages: [{ role: "user", content: "hi" }] });
214
+ const body = JSON.parse(fetch.mock.calls[0][1].body);
215
+ (0, vitest_1.expect)(body.agent).toBeUndefined();
216
+ });
217
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const utils_1 = require("./utils");
5
+ (0, vitest_1.describe)("resolvePendingToolUse", () => {
6
+ const clientTool = {
7
+ name: "client_tool",
8
+ description: "d",
9
+ inputSchema: { type: "object" },
10
+ };
11
+ (0, vitest_1.it)("classifies client and server tools", () => {
12
+ const messages = [
13
+ {
14
+ role: "assistant",
15
+ content: [
16
+ { type: "tool_use", toolCallId: "c1", name: "client_tool", input: {} },
17
+ { type: "tool_use", toolCallId: "s1", name: "server_tool", input: {} },
18
+ ],
19
+ },
20
+ ];
21
+ const { client, server } = (0, utils_1.resolvePendingToolUse)(messages, [clientTool]);
22
+ (0, vitest_1.expect)(client).toEqual([{ toolCallId: "c1", name: "client_tool", input: {} }]);
23
+ (0, vitest_1.expect)(server).toEqual([{ toolCallId: "s1", name: "server_tool", input: {} }]);
24
+ });
25
+ (0, vitest_1.it)("skips tool_use blocks already resolved by tool_result", () => {
26
+ const messages = [
27
+ {
28
+ role: "assistant",
29
+ content: [{ type: "tool_use", toolCallId: "c1", name: "client_tool", input: {} }],
30
+ },
31
+ { role: "tool", toolCallId: "c1", content: "done" },
32
+ ];
33
+ (0, vitest_1.expect)((0, utils_1.resolvePendingToolUse)(messages, [clientTool])).toEqual({ client: [], server: [] });
34
+ });
35
+ (0, vitest_1.it)("returns empty if last assistant message has string content", () => {
36
+ const messages = [{ role: "assistant", content: "plain" }];
37
+ (0, vitest_1.expect)((0, utils_1.resolvePendingToolUse)(messages)).toEqual({ client: [], server: [] });
38
+ });
39
+ (0, vitest_1.it)("returns empty if no assistant message in history", () => {
40
+ const messages = [{ role: "user", content: "hi" }];
41
+ (0, vitest_1.expect)((0, utils_1.resolvePendingToolUse)(messages)).toEqual({ client: [], server: [] });
42
+ });
43
+ (0, vitest_1.it)("finds last assistant message even if not the last message", () => {
44
+ const messages = [
45
+ {
46
+ role: "assistant",
47
+ content: [{ type: "tool_use", toolCallId: "c1", name: "client_tool", input: {} }],
48
+ },
49
+ { role: "tool", toolCallId: "c1", content: "done" },
50
+ { role: "user", content: "thanks" },
51
+ ];
52
+ (0, vitest_1.expect)((0, utils_1.resolvePendingToolUse)(messages, [clientTool])).toEqual({ client: [], server: [] });
53
+ });
54
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentapplicationprotocol/client",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "AAP client",
5
5
  "keywords": [
6
6
  "aap",
@@ -29,7 +29,7 @@
29
29
  "types": "dist/src/index.d.ts",
30
30
  "dependencies": {
31
31
  "eventsource-parser": "^3.0.6",
32
- "@agentapplicationprotocol/core": "0.4.2"
32
+ "@agentapplicationprotocol/core": "0.5.0"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsc",