@agentapplicationprotocol/client 0.4.2 → 0.6.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.
- package/dist/src/client.d.ts +6 -4
- package/dist/src/client.js +13 -8
- package/dist/src/client.test.d.ts +1 -0
- package/dist/src/client.test.js +193 -0
- package/dist/src/examples/basic/index.d.ts +1 -0
- package/dist/src/examples/basic/index.js +88 -0
- package/dist/src/examples/cli/index.d.ts +16 -0
- package/dist/src/examples/cli/index.js +355 -0
- package/dist/src/session.d.ts +5 -2
- package/dist/src/session.js +10 -5
- package/dist/src/session.test.d.ts +1 -0
- package/dist/src/session.test.js +198 -0
- package/dist/src/utils.test.d.ts +1 -0
- package/dist/src/utils.test.js +54 -0
- package/package.json +2 -2
package/dist/src/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentResponse, CreateSessionRequest, CreateSessionResponse, MetaResponse, SessionListResponse, SessionResponse, SessionTurnRequest, SSEEvent } from "@agentapplicationprotocol/core";
|
|
1
|
+
import { AgentResponse, CreateSessionRequest, CreateSessionResponse, MetaResponse, SessionHistoryResponse, SessionListResponse, SessionResponse, SessionTurnRequest, SSEEvent } from "@agentapplicationprotocol/core";
|
|
2
2
|
export interface ClientOptions {
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
apiKey: string;
|
|
@@ -22,10 +22,12 @@ export declare class Client {
|
|
|
22
22
|
listSessions(params?: {
|
|
23
23
|
after?: string;
|
|
24
24
|
}): Promise<SessionListResponse>;
|
|
25
|
-
/** Fetches all
|
|
26
|
-
listAllSessions(): Promise<
|
|
25
|
+
/** Fetches all sessions across all pages. */
|
|
26
|
+
listAllSessions(): Promise<SessionResponse[]>;
|
|
27
27
|
/** GET /session/:id */
|
|
28
|
-
getSession(sessionId: string
|
|
28
|
+
getSession(sessionId: string): Promise<SessionResponse>;
|
|
29
|
+
/** GET /session/:id/history */
|
|
30
|
+
getSessionHistory(sessionId: string, type: "compacted" | "full"): Promise<SessionHistoryResponse>;
|
|
29
31
|
/** PUT /session — non-streaming */
|
|
30
32
|
createSession(req: CreateSessionRequest & {
|
|
31
33
|
stream?: "none";
|
package/dist/src/client.js
CHANGED
|
@@ -50,8 +50,12 @@ class Client {
|
|
|
50
50
|
return parseSSE(res.body);
|
|
51
51
|
}
|
|
52
52
|
/** GET /meta */
|
|
53
|
-
getMeta() {
|
|
54
|
-
|
|
53
|
+
async getMeta() {
|
|
54
|
+
const meta = await this.request("GET", "/meta");
|
|
55
|
+
if (meta.version !== 2) {
|
|
56
|
+
throw new Error(`Protocol version mismatch: expected 2, got ${meta.version}`);
|
|
57
|
+
}
|
|
58
|
+
return meta;
|
|
55
59
|
}
|
|
56
60
|
/** GET /sessions */
|
|
57
61
|
listSessions(params) {
|
|
@@ -61,7 +65,7 @@ class Client {
|
|
|
61
65
|
}
|
|
62
66
|
return this.request("GET", path);
|
|
63
67
|
}
|
|
64
|
-
/** Fetches all
|
|
68
|
+
/** Fetches all sessions across all pages. */
|
|
65
69
|
async listAllSessions() {
|
|
66
70
|
const sessions = [];
|
|
67
71
|
let after;
|
|
@@ -73,11 +77,12 @@ class Client {
|
|
|
73
77
|
return sessions;
|
|
74
78
|
}
|
|
75
79
|
/** GET /session/:id */
|
|
76
|
-
getSession(sessionId
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
getSession(sessionId) {
|
|
81
|
+
return this.request("GET", `/session/${sessionId}`);
|
|
82
|
+
}
|
|
83
|
+
/** GET /session/:id/history */
|
|
84
|
+
getSessionHistory(sessionId, type) {
|
|
85
|
+
return this.request("GET", `/session/${sessionId}/history?${new URLSearchParams({ type }).toString()}`);
|
|
81
86
|
}
|
|
82
87
|
createSession(req) {
|
|
83
88
|
if (req.messages.at(-1)?.role !== "user")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
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({ version: 2, agents: [] });
|
|
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: 2, 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: 2, 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)("getSessionHistory: appends ?type=compacted", async () => {
|
|
62
|
+
const fetch = mockFetch({ history: { compacted: [] } });
|
|
63
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
64
|
+
await client.getSessionHistory("s1", "compacted");
|
|
65
|
+
(0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/session/s1/history?type=compacted`);
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.it)("getSessionHistory: appends ?type=full", async () => {
|
|
68
|
+
const fetch = mockFetch({ history: { full: [] } });
|
|
69
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
70
|
+
await client.getSessionHistory("s1", "full");
|
|
71
|
+
(0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/session/s1/history?type=full`);
|
|
72
|
+
});
|
|
73
|
+
(0, vitest_1.it)("listSessions: GET /sessions without cursor", async () => {
|
|
74
|
+
const res = { sessions: [{ sessionId: "s1", agent: { name: "a" } }] };
|
|
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 s1 = { sessionId: "s1", agent: { name: "a" } };
|
|
88
|
+
const s2 = { sessionId: "s2", agent: { name: "a" } };
|
|
89
|
+
const fetch = vitest_1.vi
|
|
90
|
+
.fn()
|
|
91
|
+
.mockResolvedValueOnce({
|
|
92
|
+
ok: true,
|
|
93
|
+
status: 200,
|
|
94
|
+
json: () => Promise.resolve({ sessions: [s1], next: "c1" }),
|
|
95
|
+
})
|
|
96
|
+
.mockResolvedValueOnce({
|
|
97
|
+
ok: true,
|
|
98
|
+
status: 200,
|
|
99
|
+
json: () => Promise.resolve({ sessions: [s2] }),
|
|
100
|
+
});
|
|
101
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
102
|
+
(0, vitest_1.expect)(await client.listAllSessions()).toEqual([s1, s2]);
|
|
103
|
+
(0, vitest_1.expect)(fetch).toHaveBeenCalledTimes(2);
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.it)("deleteSession: DELETE /session/:id returns void on 204", async () => {
|
|
106
|
+
vitest_1.vi.stubGlobal("fetch", vitest_1.vi.fn().mockResolvedValue({ ok: true, status: 204 }));
|
|
107
|
+
await (0, vitest_1.expect)(client.deleteSession("s1")).resolves.toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.it)("createSession: throws if last message is not a user message", () => {
|
|
110
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch({}));
|
|
111
|
+
(0, vitest_1.expect)(() => client.createSession({
|
|
112
|
+
agent: { name: "a" },
|
|
113
|
+
messages: [{ role: "assistant", content: "hi" }],
|
|
114
|
+
})).toThrow("Last message must be a user message");
|
|
115
|
+
});
|
|
116
|
+
(0, vitest_1.it)("createSession: non-streaming returns AgentResponse", async () => {
|
|
117
|
+
const res = { sessionId: "s1", stopReason: "end_turn", messages: [] };
|
|
118
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch(res, 201));
|
|
119
|
+
const result = await client.createSession({
|
|
120
|
+
agent: { name: "a" },
|
|
121
|
+
messages: [{ role: "user", content: "hi" }],
|
|
122
|
+
});
|
|
123
|
+
(0, vitest_1.expect)(result).toEqual(res);
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.it)("createSession: streaming returns SSE events", async () => {
|
|
126
|
+
const events = [
|
|
127
|
+
{ event: "session_start", sessionId: "s1" },
|
|
128
|
+
{ event: "turn_start" },
|
|
129
|
+
{ event: "turn_stop", stopReason: "end_turn" },
|
|
130
|
+
];
|
|
131
|
+
vitest_1.vi.stubGlobal("fetch", mockSSEFetch(events));
|
|
132
|
+
const stream = await client.createSession({
|
|
133
|
+
agent: { name: "a" },
|
|
134
|
+
messages: [{ role: "user", content: "hi" }],
|
|
135
|
+
stream: "message",
|
|
136
|
+
});
|
|
137
|
+
const received = [];
|
|
138
|
+
for await (const e of stream)
|
|
139
|
+
received.push(e);
|
|
140
|
+
(0, vitest_1.expect)(received).toEqual(events);
|
|
141
|
+
});
|
|
142
|
+
(0, vitest_1.it)("sendTurn: non-streaming returns AgentResponse", async () => {
|
|
143
|
+
const res = { stopReason: "end_turn", messages: [] };
|
|
144
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch(res));
|
|
145
|
+
const result = await client.sendTurn("s1", { messages: [{ role: "user", content: "hi" }] });
|
|
146
|
+
(0, vitest_1.expect)(result).toEqual(res);
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.it)("sendTurn: streaming returns SSE events", async () => {
|
|
149
|
+
const events = [
|
|
150
|
+
{ event: "turn_start" },
|
|
151
|
+
{ event: "text", text: "hello" },
|
|
152
|
+
{ event: "turn_stop", stopReason: "end_turn" },
|
|
153
|
+
];
|
|
154
|
+
vitest_1.vi.stubGlobal("fetch", mockSSEFetch(events));
|
|
155
|
+
const stream = await client.sendTurn("s1", {
|
|
156
|
+
messages: [{ role: "user", content: "hi" }],
|
|
157
|
+
stream: "message",
|
|
158
|
+
});
|
|
159
|
+
const received = [];
|
|
160
|
+
for await (const e of stream)
|
|
161
|
+
received.push(e);
|
|
162
|
+
(0, vitest_1.expect)(received).toEqual(events);
|
|
163
|
+
});
|
|
164
|
+
(0, vitest_1.it)("streamRequest: throws ClientError on non-ok response", async () => {
|
|
165
|
+
vitest_1.vi.stubGlobal("fetch", vitest_1.vi.fn().mockResolvedValue({
|
|
166
|
+
ok: false,
|
|
167
|
+
status: 403,
|
|
168
|
+
body: null,
|
|
169
|
+
text: () => Promise.resolve("Forbidden"),
|
|
170
|
+
}));
|
|
171
|
+
await (0, vitest_1.expect)(client.createSession({
|
|
172
|
+
agent: { name: "a" },
|
|
173
|
+
messages: [{ role: "user", content: "hi" }],
|
|
174
|
+
stream: "delta",
|
|
175
|
+
})).rejects.toThrow(client_1.ClientError);
|
|
176
|
+
});
|
|
177
|
+
(0, vitest_1.it)("throws ClientError on non-ok response", async () => {
|
|
178
|
+
vitest_1.vi.stubGlobal("fetch", vitest_1.vi
|
|
179
|
+
.fn()
|
|
180
|
+
.mockResolvedValue({ ok: false, status: 401, text: () => Promise.resolve("Unauthorized") }));
|
|
181
|
+
await (0, vitest_1.expect)(client.getMeta()).rejects.toThrow(client_1.ClientError);
|
|
182
|
+
});
|
|
183
|
+
(0, vitest_1.it)("ClientError has correct properties", async () => {
|
|
184
|
+
vitest_1.vi.stubGlobal("fetch", vitest_1.vi
|
|
185
|
+
.fn()
|
|
186
|
+
.mockResolvedValue({ ok: false, status: 404, text: () => Promise.resolve("Not Found") }));
|
|
187
|
+
const err = await client.getSession("x").catch((e) => e);
|
|
188
|
+
(0, vitest_1.expect)(err).toBeInstanceOf(client_1.ClientError);
|
|
189
|
+
(0, vitest_1.expect)(err.status).toBe(404);
|
|
190
|
+
(0, vitest_1.expect)(err.method).toBe("GET");
|
|
191
|
+
(0, vitest_1.expect)(err.path).toBe("/session/x");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -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
|
+
});
|
package/dist/src/session.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentConfig, AgentInfo, CreateSessionRequest, HistoryMessage, SessionTurnRequest, SSEEvent, ToolCallEvent, ToolSpec } from "@agentapplicationprotocol/core";
|
|
1
|
+
import { AgentConfig, AgentInfo, CreateSessionRequest, HistoryMessage, SessionResponse, SessionTurnRequest, SSEEvent, ToolCallEvent, ToolSpec } from "@agentapplicationprotocol/core";
|
|
2
2
|
import { Client } from "./client";
|
|
3
3
|
/** Unresolved tool calls from a turn, split by origin. */
|
|
4
4
|
export interface PendingToolUse {
|
|
@@ -31,9 +31,12 @@ export declare class Session {
|
|
|
31
31
|
}>;
|
|
32
32
|
/**
|
|
33
33
|
* Loads an existing session and resolves any pending tool use.
|
|
34
|
+
* @param res - Session response from `client.getSession()`.
|
|
35
|
+
* @param agentInfo - Agent metadata matching the session's agent.
|
|
36
|
+
* @param history - If provided, fetches history of the given type.
|
|
34
37
|
* @returns The loaded session and any pending tool calls.
|
|
35
38
|
*/
|
|
36
|
-
static load(client: Client,
|
|
39
|
+
static load(client: Client, res: SessionResponse, agentInfo: AgentInfo, history?: "full" | "compacted"): Promise<{
|
|
37
40
|
session: Session;
|
|
38
41
|
pending: PendingToolUse;
|
|
39
42
|
}>;
|
package/dist/src/session.js
CHANGED
|
@@ -45,13 +45,18 @@ class Session {
|
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Loads an existing session and resolves any pending tool use.
|
|
48
|
+
* @param res - Session response from `client.getSession()`.
|
|
49
|
+
* @param agentInfo - Agent metadata matching the session's agent.
|
|
50
|
+
* @param history - If provided, fetches history of the given type.
|
|
48
51
|
* @returns The loaded session and any pending tool calls.
|
|
49
52
|
*/
|
|
50
|
-
static async load(client,
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
static async load(client, res, agentInfo, history) {
|
|
54
|
+
const session = new Session(res.sessionId, client, agentInfo, res.agent, res.tools);
|
|
55
|
+
if (history) {
|
|
56
|
+
const histRes = await client.getSessionHistory(res.sessionId, history);
|
|
57
|
+
const h = history === "compacted" ? histRes.history.compacted : histRes.history.full;
|
|
58
|
+
session.history.push(...(h ?? []));
|
|
59
|
+
}
|
|
55
60
|
return { session, pending: (0, utils_1.resolvePendingToolUse)(session.history, session.tools) };
|
|
56
61
|
}
|
|
57
62
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
const sessionRes = { sessionId: "s1", agent: { name: "test-agent" } };
|
|
36
|
+
(0, vitest_1.it)("returns session with correct sessionId and agentConfig", async () => {
|
|
37
|
+
const { session } = await session_1.Session.load(client, sessionRes, agentInfo);
|
|
38
|
+
(0, vitest_1.expect)(session.sessionId).toBe("s1");
|
|
39
|
+
(0, vitest_1.expect)(session.agentConfig).toEqual({ name: "test-agent" });
|
|
40
|
+
(0, vitest_1.expect)(session.agent).toBe(agentInfo);
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)("populates history from full history", async () => {
|
|
43
|
+
const history = [{ role: "user", content: "hi" }];
|
|
44
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch({ history: { full: history } }));
|
|
45
|
+
const { session } = await session_1.Session.load(client, sessionRes, agentInfo, "full");
|
|
46
|
+
(0, vitest_1.expect)(session.history).toEqual(history);
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.it)("populates history from compacted history", async () => {
|
|
49
|
+
const history = [{ role: "user", content: "summary" }];
|
|
50
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch({ history: { compacted: history } }));
|
|
51
|
+
const { session } = await session_1.Session.load(client, sessionRes, agentInfo, "compacted");
|
|
52
|
+
(0, vitest_1.expect)(session.history).toEqual(history);
|
|
53
|
+
});
|
|
54
|
+
(0, vitest_1.it)("returns empty history when no history in response", async () => {
|
|
55
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch({ history: {} }));
|
|
56
|
+
const { session } = await session_1.Session.load(client, sessionRes, agentInfo, "full");
|
|
57
|
+
(0, vitest_1.expect)(session.history).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.it)("resolves pending client tool use from history", async () => {
|
|
60
|
+
const tools = [
|
|
61
|
+
{
|
|
62
|
+
name: "myTool",
|
|
63
|
+
description: "d",
|
|
64
|
+
inputSchema: { type: "object", properties: {} },
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
const history = [
|
|
68
|
+
{ role: "user", content: "go" },
|
|
69
|
+
{
|
|
70
|
+
role: "assistant",
|
|
71
|
+
content: [{ type: "tool_use", toolCallId: "tc1", name: "myTool", input: {} }],
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch({ history: { full: history } }));
|
|
75
|
+
const res = { sessionId: "s1", agent: { name: "test-agent" }, tools };
|
|
76
|
+
const { pending } = await session_1.Session.load(client, res, agentInfo, "full");
|
|
77
|
+
(0, vitest_1.expect)(pending.client).toEqual([{ toolCallId: "tc1", name: "myTool", input: {} }]);
|
|
78
|
+
(0, vitest_1.expect)(pending.server).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
(0, vitest_1.it)("calls getSessionHistory with correct type param", async () => {
|
|
81
|
+
const fetch = mockFetch({ history: { compacted: [] } });
|
|
82
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
83
|
+
await session_1.Session.load(client, sessionRes, agentInfo, "compacted");
|
|
84
|
+
(0, vitest_1.expect)(fetch.mock.calls[0][0]).toBe(`${BASE_URL}/session/s1/history?type=compacted`);
|
|
85
|
+
});
|
|
86
|
+
(0, vitest_1.it)("does not fetch when no history requested", async () => {
|
|
87
|
+
const fetch = vitest_1.vi.fn();
|
|
88
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
89
|
+
await session_1.Session.load(client, sessionRes, agentInfo);
|
|
90
|
+
(0, vitest_1.expect)(fetch).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.describe)("Session.create", () => {
|
|
94
|
+
(0, vitest_1.it)("non-streaming: builds session and history", async () => {
|
|
95
|
+
const res = {
|
|
96
|
+
sessionId: "s1",
|
|
97
|
+
stopReason: "end_turn",
|
|
98
|
+
messages: [{ role: "assistant", content: "hi" }],
|
|
99
|
+
};
|
|
100
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch(res, 201));
|
|
101
|
+
const { session, pending } = await session_1.Session.create(client, { agent: { name: "test-agent" }, messages: [{ role: "user", content: "hello" }] }, agentInfo);
|
|
102
|
+
(0, vitest_1.expect)(session.sessionId).toBe("s1");
|
|
103
|
+
(0, vitest_1.expect)(session.history).toHaveLength(2);
|
|
104
|
+
(0, vitest_1.expect)(pending).toEqual({ client: [], server: [] });
|
|
105
|
+
});
|
|
106
|
+
(0, vitest_1.it)("streaming: builds session from SSE events and calls cb", async () => {
|
|
107
|
+
const events = [
|
|
108
|
+
{ event: "session_start", sessionId: "s2" },
|
|
109
|
+
{ event: "turn_start" },
|
|
110
|
+
{ event: "text", text: "hey" },
|
|
111
|
+
{ event: "turn_stop", stopReason: "end_turn" },
|
|
112
|
+
];
|
|
113
|
+
vitest_1.vi.stubGlobal("fetch", mockSSEFetch(events));
|
|
114
|
+
const cb = vitest_1.vi.fn();
|
|
115
|
+
const { session } = await session_1.Session.create(client, {
|
|
116
|
+
agent: { name: "test-agent" },
|
|
117
|
+
messages: [{ role: "user", content: "hello" }],
|
|
118
|
+
stream: "message",
|
|
119
|
+
}, agentInfo, cb);
|
|
120
|
+
(0, vitest_1.expect)(session.sessionId).toBe("s2");
|
|
121
|
+
(0, vitest_1.expect)(cb).toHaveBeenCalledTimes(events.length);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
(0, vitest_1.describe)("Session.send", () => {
|
|
125
|
+
async function makeSession() {
|
|
126
|
+
const res = { sessionId: "s1", stopReason: "end_turn", messages: [] };
|
|
127
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch(res, 201));
|
|
128
|
+
const { session } = await session_1.Session.create(client, { agent: { name: "test-agent" }, messages: [{ role: "user", content: "hi" }] }, agentInfo);
|
|
129
|
+
return session;
|
|
130
|
+
}
|
|
131
|
+
(0, vitest_1.it)("non-streaming: appends messages and returns pending", async () => {
|
|
132
|
+
const session = await makeSession();
|
|
133
|
+
const turnRes = {
|
|
134
|
+
stopReason: "end_turn",
|
|
135
|
+
messages: [{ role: "assistant", content: "reply" }],
|
|
136
|
+
};
|
|
137
|
+
vitest_1.vi.stubGlobal("fetch", mockFetch(turnRes));
|
|
138
|
+
const pending = await session.send({ messages: [{ role: "user", content: "next" }] });
|
|
139
|
+
(0, vitest_1.expect)(session.history).toHaveLength(3);
|
|
140
|
+
(0, vitest_1.expect)(pending).toEqual({ client: [], server: [] });
|
|
141
|
+
});
|
|
142
|
+
(0, vitest_1.it)("streaming: appends messages and calls cb", async () => {
|
|
143
|
+
const session = await makeSession();
|
|
144
|
+
const events = [
|
|
145
|
+
{ event: "turn_start" },
|
|
146
|
+
{ event: "text", text: "streamed" },
|
|
147
|
+
{ event: "turn_stop", stopReason: "end_turn" },
|
|
148
|
+
];
|
|
149
|
+
vitest_1.vi.stubGlobal("fetch", mockSSEFetch(events));
|
|
150
|
+
const cb = vitest_1.vi.fn();
|
|
151
|
+
await session.send({ messages: [{ role: "user", content: "next" }], stream: "message" }, cb);
|
|
152
|
+
(0, vitest_1.expect)(cb).toHaveBeenCalledTimes(events.length);
|
|
153
|
+
});
|
|
154
|
+
(0, vitest_1.it)("strips unchanged tools from request", async () => {
|
|
155
|
+
const session = await makeSession();
|
|
156
|
+
const tools = [
|
|
157
|
+
{ name: "t", description: "d", inputSchema: { type: "object", properties: {} } },
|
|
158
|
+
];
|
|
159
|
+
session.tools = tools;
|
|
160
|
+
const fetch = mockFetch({ stopReason: "end_turn", messages: [] });
|
|
161
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
162
|
+
await session.send({ messages: [{ role: "user", content: "hi" }], tools });
|
|
163
|
+
const body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
164
|
+
(0, vitest_1.expect)(body.tools).toBeUndefined();
|
|
165
|
+
});
|
|
166
|
+
(0, vitest_1.it)("sends changed tools and updates session.tools", async () => {
|
|
167
|
+
const session = await makeSession();
|
|
168
|
+
const newTools = [
|
|
169
|
+
{ name: "t2", description: "d", inputSchema: { type: "object", properties: {} } },
|
|
170
|
+
];
|
|
171
|
+
const fetch = mockFetch({ stopReason: "end_turn", messages: [] });
|
|
172
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
173
|
+
await session.send({ messages: [{ role: "user", content: "hi" }], tools: newTools });
|
|
174
|
+
const body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
175
|
+
(0, vitest_1.expect)(body.tools).toEqual(newTools);
|
|
176
|
+
(0, vitest_1.expect)(session.tools).toEqual(newTools);
|
|
177
|
+
});
|
|
178
|
+
(0, vitest_1.it)("sends only changed agent options", async () => {
|
|
179
|
+
const session = await makeSession();
|
|
180
|
+
session.agentConfig = { name: "test-agent", options: { model: "gpt-4" } };
|
|
181
|
+
const fetch = mockFetch({ stopReason: "end_turn", messages: [] });
|
|
182
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
183
|
+
await session.send({
|
|
184
|
+
messages: [{ role: "user", content: "hi" }],
|
|
185
|
+
agent: { options: { model: "gpt-4o", temperature: "0.5" } },
|
|
186
|
+
});
|
|
187
|
+
const body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
188
|
+
(0, vitest_1.expect)(body.agent.options).toEqual({ model: "gpt-4o", temperature: "0.5" });
|
|
189
|
+
});
|
|
190
|
+
(0, vitest_1.it)("omits agent if nothing changed", async () => {
|
|
191
|
+
const session = await makeSession();
|
|
192
|
+
const fetch = mockFetch({ stopReason: "end_turn", messages: [] });
|
|
193
|
+
vitest_1.vi.stubGlobal("fetch", fetch);
|
|
194
|
+
await session.send({ messages: [{ role: "user", content: "hi" }] });
|
|
195
|
+
const body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
196
|
+
(0, vitest_1.expect)(body.agent).toBeUndefined();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -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.
|
|
3
|
+
"version": "0.6.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.
|
|
32
|
+
"@agentapplicationprotocol/core": "0.6.0"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsc",
|