@agentapplicationprotocol/server 0.6.0 → 0.7.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/agent.js CHANGED
@@ -62,7 +62,7 @@ class Agent {
62
62
  name,
63
63
  title: options.title,
64
64
  description: options.description ?? "",
65
- inputSchema: zod_1.default.toJSONSchema(options.inputSchema),
65
+ parameters: zod_1.default.toJSONSchema(options.inputSchema),
66
66
  });
67
67
  this.tools.set(name, async (input) => {
68
68
  const output = await exec(options.inputSchema.parse(JSON.parse(input)));
@@ -64,9 +64,9 @@ const handler = {
64
64
  apiKey: req.agent.options?.apiKey || undefined,
65
65
  });
66
66
  const model = new model_js_1.AiModelProvider(openai.chat(req.agent.options?.model ?? "gpt-4o"));
67
- const session = new session_js_1.Session(sessionId, agent, model, req.agent, req.tools);
67
+ const session = new session_js_1.Session(sessionId, agent, model, req.agent, req.tools ?? [], req.messages ?? []);
68
68
  sessions.set(sessionId, session);
69
- return session.runNewSession(req);
69
+ return Promise.resolve({ sessionId });
70
70
  },
71
71
  sendTurn(sessionId, req) {
72
72
  const session = sessions.get(sessionId);
@@ -62,9 +62,9 @@ const handler = {
62
62
  apiKey: req.agent.options?.apiKey || undefined,
63
63
  });
64
64
  const model = new model_js_1.AiModelProvider(openai.chat(req.agent.options?.model ?? "gpt-4o"));
65
- const session = new session_js_1.TruncatedHistorySession(sessionId, agent, model, req.agent, req.tools);
65
+ const session = new session_js_1.TruncatedHistorySession(sessionId, agent, model, req.agent, req.tools ?? [], req.messages ?? []);
66
66
  session_js_1.sessions.set(sessionId, session);
67
- return session.runNewSession(req);
67
+ return Promise.resolve({ sessionId });
68
68
  },
69
69
  sendTurn(sessionId, req) {
70
70
  const session = session_js_1.sessions.get(sessionId);
package/dist/src/model.js CHANGED
@@ -168,7 +168,7 @@ function toToolSet(tools) {
168
168
  for (const t of tools) {
169
169
  res[t.name] = (0, ai_1.tool)({
170
170
  description: t.description,
171
- inputSchema: (0, ai_1.jsonSchema)(t.inputSchema),
171
+ inputSchema: (0, ai_1.jsonSchema)(t.parameters),
172
172
  });
173
173
  }
174
174
  return res;
@@ -7,8 +7,7 @@ export interface Handler {
7
7
  }): Promise<SessionListResponse>;
8
8
  getSession(sessionId: string): Promise<SessionResponse>;
9
9
  getSessionHistory(sessionId: string, type: "compacted" | "full"): Promise<HistoryMessage[]>;
10
- /** The last message in `req.messages` is guaranteed to be a user message. */
11
- createSession(req: CreateSessionRequest): Promise<CreateSessionResponse> | AsyncIterable<SSEEvent>;
10
+ createSession(req: CreateSessionRequest): Promise<CreateSessionResponse>;
12
11
  sendTurn(sessionId: string, req: SessionTurnRequest): Promise<AgentResponse> | AsyncIterable<SSEEvent>;
13
12
  deleteSession(sessionId: string): Promise<void>;
14
13
  }
@@ -41,18 +41,13 @@ function redactSecretOptions(session, agents) {
41
41
  */
42
42
  function aap(handler) {
43
43
  const router = new hono_1.Hono();
44
- router.get("/meta", (c) => c.json({ version: 2, ...handler.getMeta() }));
45
- router.put("/session", async (c) => {
44
+ router.get("/meta", (c) => c.json({ version: 3, ...handler.getMeta() }));
45
+ router.post("/sessions", async (c) => {
46
46
  const req = await c.req.json();
47
- if (req.messages.at(-1)?.role !== "user")
48
- return c.json({ error: "Last message must be a user message" }, 400);
49
47
  const result = await handler.createSession(req);
50
- if (req.stream === "delta" || req.stream === "message") {
51
- return (0, streaming_1.streamSSE)(c, (stream) => writeSSEEvents(stream, result));
52
- }
53
48
  return c.json(result, 201);
54
49
  });
55
- router.post("/session/:id", async (c) => {
50
+ router.post("/sessions/:id/turns", async (c) => {
56
51
  const req = await c.req.json();
57
52
  const result = await handler.sendTurn(c.req.param("id"), req);
58
53
  if (req.stream === "delta" || req.stream === "message") {
@@ -60,12 +55,12 @@ function aap(handler) {
60
55
  }
61
56
  return c.json(result);
62
57
  });
63
- router.get("/session/:id", async (c) => {
58
+ router.get("/sessions/:id", async (c) => {
64
59
  const session = await handler.getSession(c.req.param("id"));
65
60
  const { agents } = handler.getMeta();
66
61
  return c.json(redactSecretOptions(session, agents));
67
62
  });
68
- router.get("/session/:id/history", async (c) => {
63
+ router.get("/sessions/:id/history", async (c) => {
69
64
  const typeParam = c.req.query("type");
70
65
  if (typeParam !== "compacted" && typeParam !== "full")
71
66
  return c.json({ error: 'type must be "compacted" or "full"' }, 400);
@@ -74,9 +69,14 @@ function aap(handler) {
74
69
  });
75
70
  router.get("/sessions", async (c) => {
76
71
  const after = c.req.query("after");
77
- return c.json(await handler.listSessions({ after }));
72
+ const result = await handler.listSessions({ after });
73
+ const { agents } = handler.getMeta();
74
+ return c.json({
75
+ ...result,
76
+ sessions: result.sessions.map((s) => redactSecretOptions(s, agents)),
77
+ });
78
78
  });
79
- router.delete("/session/:id", async (c) => {
79
+ router.delete("/sessions/:id", async (c) => {
80
80
  await handler.deleteSession(c.req.param("id"));
81
81
  return new Response(null, { status: 204 });
82
82
  });
@@ -5,10 +5,10 @@ const hono_1 = require("hono");
5
5
  const bearer_auth_1 = require("hono/bearer-auth");
6
6
  const cors_1 = require("hono/cors");
7
7
  const server_1 = require("./server");
8
- const meta = { version: 2, agents: [] };
8
+ const meta = { version: 3, agents: [] };
9
9
  const session = { sessionId: "s1", agent: { name: "a" } };
10
10
  const agentResponse = { stopReason: "end_turn", messages: [] };
11
- const createSessionResponse = { ...agentResponse, sessionId: "s1" };
11
+ const createSessionResponse = { sessionId: "s1" };
12
12
  const sessionList = { sessions: [session] };
13
13
  async function* sseEvents() {
14
14
  yield { event: "turn_start" };
@@ -49,7 +49,7 @@ function req(method, path, body, headers) {
49
49
  });
50
50
  (0, vitest_1.it)("GET /session/:id returns session", async () => {
51
51
  const app = makeApp(makeHandler());
52
- const res = await app.fetch(req("GET", "/session/s1"));
52
+ const res = await app.fetch(req("GET", "/sessions/s1"));
53
53
  (0, vitest_1.expect)(res.status).toBe(200);
54
54
  (0, vitest_1.expect)(await res.json()).toEqual(session);
55
55
  });
@@ -73,7 +73,7 @@ function req(method, path, body, headers) {
73
73
  ],
74
74
  }),
75
75
  }));
76
- const res = await app.fetch(req("GET", "/session/s1"));
76
+ const res = await app.fetch(req("GET", "/sessions/s1"));
77
77
  (0, vitest_1.expect)(res.status).toBe(200);
78
78
  (0, vitest_1.expect)(await res.json()).toEqual({
79
79
  ...secretSession,
@@ -88,45 +88,61 @@ function req(method, path, body, headers) {
88
88
  (0, vitest_1.expect)(await res.json()).toEqual(sessionList);
89
89
  (0, vitest_1.expect)(handler.listSessions).toHaveBeenCalledWith({ after: "cursor1" });
90
90
  });
91
- (0, vitest_1.it)("PUT /session returns 400 if last message is not a user message", async () => {
92
- const app = makeApp(makeHandler());
93
- const res = await app.fetch(req("PUT", "/session", {
94
- agent: { name: "a" },
95
- messages: [{ role: "assistant", content: "hi" }],
91
+ (0, vitest_1.it)("GET /sessions redacts secret options in each session", async () => {
92
+ const secretSession = {
93
+ sessionId: "s1",
94
+ agent: { name: "a", options: { key: "mysecret", model: "gpt-4" } },
95
+ };
96
+ const app = makeApp(makeHandler({
97
+ listSessions: vitest_1.vi.fn().mockResolvedValue({ sessions: [secretSession] }),
98
+ getMeta: vitest_1.vi.fn().mockReturnValue({
99
+ agents: [
100
+ {
101
+ name: "a",
102
+ version: "1.0.0",
103
+ options: [
104
+ { type: "secret", name: "key", default: "" },
105
+ { type: "text", name: "model", default: "" },
106
+ ],
107
+ },
108
+ ],
109
+ }),
96
110
  }));
97
- (0, vitest_1.expect)(res.status).toBe(400);
111
+ const res = await app.fetch(req("GET", "/sessions"));
112
+ (0, vitest_1.expect)(res.status).toBe(200);
113
+ (0, vitest_1.expect)(await res.json()).toEqual({
114
+ sessions: [
115
+ {
116
+ ...secretSession,
117
+ agent: { ...secretSession.agent, options: { key: "***", model: "gpt-4" } },
118
+ },
119
+ ],
120
+ });
98
121
  });
99
- (0, vitest_1.it)("PUT /session returns 201 with AgentResponse", async () => {
122
+ (0, vitest_1.it)("POST /sessions returns 201 with sessionId", async () => {
100
123
  const app = makeApp(makeHandler());
101
- const res = await app.fetch(req("PUT", "/session", { agent: { name: "a" }, messages: [{ role: "user", content: "hi" }] }));
124
+ const res = await app.fetch(req("POST", "/sessions", { agent: { name: "a" } }));
102
125
  (0, vitest_1.expect)(res.status).toBe(201);
103
126
  (0, vitest_1.expect)(await res.json()).toEqual(createSessionResponse);
104
127
  });
105
- (0, vitest_1.it)("PUT /session with stream returns SSE", async () => {
106
- const app = makeApp(makeHandler({ createSession: vitest_1.vi.fn().mockResolvedValue(sseEvents()) }));
107
- const res = await app.fetch(req("PUT", "/session", {
108
- agent: { name: "a" },
109
- messages: [{ role: "user", content: "hi" }],
110
- stream: "message",
111
- }));
112
- (0, vitest_1.expect)(res.status).toBe(200);
113
- (0, vitest_1.expect)(res.headers.get("content-type")).toContain("text/event-stream");
114
- });
115
- (0, vitest_1.it)("POST /session/:id returns AgentResponse", async () => {
128
+ (0, vitest_1.it)("POST /sessions/:id/turns returns AgentResponse", async () => {
116
129
  const app = makeApp(makeHandler());
117
- const res = await app.fetch(req("POST", "/session/s1", { messages: [{ role: "user", content: "hi" }] }));
130
+ const res = await app.fetch(req("POST", "/sessions/s1/turns", { messages: [{ role: "user", content: "hi" }] }));
118
131
  (0, vitest_1.expect)(res.status).toBe(200);
119
132
  (0, vitest_1.expect)(await res.json()).toEqual(agentResponse);
120
133
  });
121
- (0, vitest_1.it)("POST /session/:id with stream returns SSE", async () => {
134
+ (0, vitest_1.it)("POST /sessions/:id/turns with stream returns SSE", async () => {
122
135
  const app = makeApp(makeHandler({ sendTurn: vitest_1.vi.fn().mockResolvedValue(sseEvents()) }));
123
- const res = await app.fetch(req("POST", "/session/s1", { messages: [{ role: "user", content: "hi" }], stream: "delta" }));
136
+ const res = await app.fetch(req("POST", "/sessions/s1/turns", {
137
+ messages: [{ role: "user", content: "hi" }],
138
+ stream: "delta",
139
+ }));
124
140
  (0, vitest_1.expect)(res.status).toBe(200);
125
141
  (0, vitest_1.expect)(res.headers.get("content-type")).toContain("text/event-stream");
126
142
  });
127
143
  (0, vitest_1.it)("DELETE /session/:id returns 204", async () => {
128
144
  const app = makeApp(makeHandler());
129
- const res = await app.fetch(req("DELETE", "/session/s1"));
145
+ const res = await app.fetch(req("DELETE", "/sessions/s1"));
130
146
  (0, vitest_1.expect)(res.status).toBe(204);
131
147
  });
132
148
  (0, vitest_1.it)("auth middleware: returns 401 when rejected", async () => {
@@ -159,7 +175,7 @@ function req(method, path, body, headers) {
159
175
  getSession: vitest_1.vi.fn().mockResolvedValue(sessionWithOptions),
160
176
  getMeta: vitest_1.vi.fn().mockReturnValue({ agents: [] }),
161
177
  }));
162
- const res = await app.fetch(req("GET", "/session/s1"));
178
+ const res = await app.fetch(req("GET", "/sessions/s1"));
163
179
  (0, vitest_1.expect)(await res.json()).toEqual(sessionWithOptions);
164
180
  });
165
181
  (0, vitest_1.it)("GET /session/:id does not redact when agent has no secret options", async () => {
@@ -179,14 +195,14 @@ function req(method, path, body, headers) {
179
195
  ],
180
196
  }),
181
197
  }));
182
- const res = await app.fetch(req("GET", "/session/s1"));
198
+ const res = await app.fetch(req("GET", "/sessions/s1"));
183
199
  (0, vitest_1.expect)(await res.json()).toEqual(sessionWithOptions);
184
200
  });
185
201
  (0, vitest_1.it)("GET /session/:id/history?type=compacted calls getSessionHistory", async () => {
186
202
  const messages = [{ role: "user", content: "hi" }];
187
203
  const handler = makeHandler({ getSessionHistory: vitest_1.vi.fn().mockResolvedValue(messages) });
188
204
  const app = makeApp(handler);
189
- const res = await app.fetch(req("GET", "/session/s1/history?type=compacted"));
205
+ const res = await app.fetch(req("GET", "/sessions/s1/history?type=compacted"));
190
206
  (0, vitest_1.expect)(res.status).toBe(200);
191
207
  (0, vitest_1.expect)(handler.getSessionHistory).toHaveBeenCalledWith("s1", "compacted");
192
208
  (0, vitest_1.expect)(await res.json()).toEqual({ history: { compacted: messages } });
@@ -195,14 +211,14 @@ function req(method, path, body, headers) {
195
211
  const messages = [{ role: "user", content: "hi" }];
196
212
  const handler = makeHandler({ getSessionHistory: vitest_1.vi.fn().mockResolvedValue(messages) });
197
213
  const app = makeApp(handler);
198
- const res = await app.fetch(req("GET", "/session/s1/history?type=full"));
214
+ const res = await app.fetch(req("GET", "/sessions/s1/history?type=full"));
199
215
  (0, vitest_1.expect)(res.status).toBe(200);
200
216
  (0, vitest_1.expect)(handler.getSessionHistory).toHaveBeenCalledWith("s1", "full");
201
217
  (0, vitest_1.expect)(await res.json()).toEqual({ history: { full: messages } });
202
218
  });
203
219
  (0, vitest_1.it)("GET /session/:id/history without valid ?type returns 400", async () => {
204
220
  const app = makeApp(makeHandler());
205
- const res = await app.fetch(req("GET", "/session/s1/history"));
221
+ const res = await app.fetch(req("GET", "/sessions/s1/history"));
206
222
  (0, vitest_1.expect)(res.status).toBe(400);
207
223
  });
208
224
  });
@@ -1,4 +1,4 @@
1
- import { HistoryMessage, ToolPermissionMessage, AgentConfig, ToolSpec, SSEEvent, AgentResponse, CreateSessionRequest, CreateSessionResponse, SessionTurnRequest, SessionResponse, DeltaSSEEvent } from "@agentapplicationprotocol/core";
1
+ import { HistoryMessage, ToolPermissionMessage, AgentConfig, ToolSpec, SSEEvent, AgentResponse, SessionTurnRequest, SessionResponse, DeltaSSEEvent } from "@agentapplicationprotocol/core";
2
2
  import { ModelProvider } from "./model";
3
3
  import { Agent } from "./agent";
4
4
  /** Messages accepted as input to a turn: regular history messages or client-side tool permission responses. */
@@ -8,12 +8,12 @@ export declare class Session {
8
8
  sessionId: string;
9
9
  agent: Agent;
10
10
  agentConfig: AgentConfig;
11
- /** Application-side tools declared for this session. */
11
+ /** Client-side tools declared for this session. */
12
12
  clientTools: ToolSpec[];
13
13
  /** Accumulated conversation history across all turns. */
14
14
  history: HistoryMessage[];
15
15
  model: ModelProvider;
16
- constructor(sessionId: string, agent: Agent, model: ModelProvider, agentConfig: AgentConfig, clientTools?: ToolSpec[]);
16
+ constructor(sessionId: string, agent: Agent, model: ModelProvider, agentConfig: AgentConfig, clientTools: ToolSpec[], history: HistoryMessage[]);
17
17
  /** Resolves tool_permission messages into tool result messages by executing granted tools. */
18
18
  private resolvePermissions;
19
19
  /** Returns the set of trusted server tool names from agentConfig. */
@@ -54,8 +54,6 @@ export declare class Session {
54
54
  * Loops the LLM call as long as all tool calls are trusted and executed inline.
55
55
  */
56
56
  private runTurnNone;
57
- /** Runs the first turn and wraps the result with `sessionId` for a `createSession` response. */
58
- runNewSession(req: CreateSessionRequest): Promise<CreateSessionResponse> | AsyncIterable<SSEEvent>;
59
57
  /** Applies config overrides from the request that persist for the session lifetime: client tools, enabled/trusted agent tools, and agent options. */
60
58
  private applySessionOverrides;
61
59
  /** Routes to the appropriate turn runner based on the requested stream mode. */
@@ -4,13 +4,13 @@ exports.Session = void 0;
4
4
  const core_1 = require("@agentapplicationprotocol/core");
5
5
  /** Manages a stateful conversation session, accumulating history across turns. */
6
6
  class Session {
7
- constructor(sessionId, agent, model, agentConfig, clientTools = []) {
7
+ constructor(sessionId, agent, model, agentConfig, clientTools, history) {
8
8
  this.sessionId = sessionId;
9
9
  this.agent = agent;
10
10
  this.model = model;
11
11
  this.agentConfig = agentConfig;
12
12
  this.clientTools = clientTools;
13
- this.history = [];
13
+ this.history = history;
14
14
  }
15
15
  /** Resolves tool_permission messages into tool result messages by executing granted tools. */
16
16
  async resolvePermissions(messages) {
@@ -210,18 +210,6 @@ class Session {
210
210
  newMessages.push(...next);
211
211
  }
212
212
  }
213
- /** Runs the first turn and wraps the result with `sessionId` for a `createSession` response. */
214
- runNewSession(req) {
215
- const sessionId = this.sessionId;
216
- const result = this.dispatchTurn(req.stream, req.messages);
217
- if (result instanceof Promise) {
218
- return result.then((res) => ({ ...res, sessionId }));
219
- }
220
- return (async function* () {
221
- yield { event: "session_start", sessionId };
222
- yield* result;
223
- })();
224
- }
225
213
  /** Applies config overrides from the request that persist for the session lifetime: client tools, enabled/trusted agent tools, and agent options. */
226
214
  applySessionOverrides(req) {
227
215
  // Override client-provided tools
@@ -26,7 +26,7 @@ function makeAgent() {
26
26
  return agent;
27
27
  }
28
28
  function makeSession(agentConfig = { name: "test-agent" }, model = makeModel(), agent = makeAgent()) {
29
- return new session_1.Session("sess-1", agent, model, agentConfig);
29
+ return new session_1.Session("sess-1", agent, model, agentConfig, [], []);
30
30
  }
31
31
  const userMsg = { role: "user", content: "hello" };
32
32
  (0, vitest_1.describe)("Session", () => {
@@ -45,7 +45,7 @@ const userMsg = { role: "user", content: "hello" };
45
45
  });
46
46
  (0, vitest_1.it)("toSessionResponse includes tools when clientTools is set", () => {
47
47
  const s = makeSession();
48
- s.clientTools = [{ name: "t", description: "", inputSchema: {} }];
48
+ s.clientTools = [{ name: "t", description: "", parameters: {} }];
49
49
  (0, vitest_1.expect)(s.toSessionResponse().tools).toHaveLength(1);
50
50
  });
51
51
  (0, vitest_1.describe)("runTurn (none mode)", () => {
@@ -84,7 +84,7 @@ const userMsg = { role: "user", content: "hello" };
84
84
  messages: [{ role: "assistant", content: "done" }],
85
85
  }),
86
86
  });
87
- const s = new session_1.Session("s", agent, model, agentConfig);
87
+ const s = new session_1.Session("s", agent, model, agentConfig, [], []);
88
88
  const res = await s.runTurn({ messages: [userMsg] });
89
89
  (0, vitest_1.expect)(res.stopReason).toBe("end_turn");
90
90
  (0, vitest_1.expect)(model.call).toHaveBeenCalledTimes(2);
@@ -106,7 +106,7 @@ const userMsg = { role: "user", content: "hello" };
106
106
  ],
107
107
  }),
108
108
  });
109
- const s = new session_1.Session("s", agent, model, agentConfig);
109
+ const s = new session_1.Session("s", agent, model, agentConfig, [], []);
110
110
  const res = (await s.runTurn({ messages: [userMsg] }));
111
111
  (0, vitest_1.expect)(res.stopReason).toBe("tool_use");
112
112
  (0, vitest_1.expect)(model.call).toHaveBeenCalledTimes(1);
@@ -145,7 +145,7 @@ const userMsg = { role: "user", content: "hello" };
145
145
  yield { event: "turn_stop", stopReason: "end_turn" };
146
146
  }),
147
147
  });
148
- const s = new session_1.Session("s", agent, model, agentConfig);
148
+ const s = new session_1.Session("s", agent, model, agentConfig, [], []);
149
149
  const events = [];
150
150
  for await (const e of s.runTurn({
151
151
  messages: [userMsg],
@@ -210,7 +210,7 @@ const userMsg = { role: "user", content: "hello" };
210
210
  messages: [{ role: "assistant", content: "done" }],
211
211
  }),
212
212
  });
213
- const s = new session_1.Session("s", agent, model, agentConfig);
213
+ const s = new session_1.Session("s", agent, model, agentConfig, [], []);
214
214
  const events = [];
215
215
  for await (const e of s.runTurn({
216
216
  messages: [userMsg],
@@ -228,7 +228,7 @@ const userMsg = { role: "user", content: "hello" };
228
228
  const agentConfig = { name: "test-agent" };
229
229
  // Seed history with an assistant message containing a tool_use
230
230
  const model = makeModel();
231
- const s = new session_1.Session("s", agent, model, agentConfig);
231
+ const s = new session_1.Session("s", agent, model, agentConfig, [], []);
232
232
  s.history = [
233
233
  { role: "user", content: "hi" },
234
234
  {
@@ -248,7 +248,7 @@ const userMsg = { role: "user", content: "hello" };
248
248
  (0, vitest_1.it)("resolves denied tool_permission with denial message", async () => {
249
249
  const agent = makeAgent();
250
250
  const model = makeModel();
251
- const s = new session_1.Session("s", agent, model, { name: "test-agent" });
251
+ const s = new session_1.Session("s", agent, model, { name: "test-agent" }, [], []);
252
252
  s.history = [
253
253
  {
254
254
  role: "assistant",
@@ -266,7 +266,7 @@ const userMsg = { role: "user", content: "hello" };
266
266
  (0, vitest_1.describe)("applySessionOverrides via runTurn", () => {
267
267
  (0, vitest_1.it)("overrides clientTools when req.tools is provided", async () => {
268
268
  const s = makeSession();
269
- const newTool = { name: "x", description: "", inputSchema: {} };
269
+ const newTool = { name: "x", description: "", parameters: {} };
270
270
  await s.runTurn({ messages: [userMsg], tools: [newTool] });
271
271
  (0, vitest_1.expect)(s.clientTools).toEqual([newTool]);
272
272
  });
@@ -281,23 +281,90 @@ const userMsg = { role: "user", content: "hello" };
281
281
  (0, vitest_1.expect)(s.agentConfig.options).toEqual({ model: "gpt-5" });
282
282
  });
283
283
  });
284
- (0, vitest_1.describe)("runNewSession", () => {
285
- (0, vitest_1.it)("returns CreateSessionResponse with sessionId (none mode)", async () => {
286
- const s = makeSession();
287
- const res = await s.runNewSession({ agent: { name: "test-agent" }, messages: [userMsg] });
288
- (0, vitest_1.expect)(res.sessionId).toBe("sess-1");
289
- });
290
- (0, vitest_1.it)("yields session_start event first (delta mode)", async () => {
291
- const s = makeSession();
292
- const result = s.runNewSession({
293
- agent: { name: "test-agent" },
284
+ (0, vitest_1.describe)("runTurn (delta mode) — untrusted tool stops loop", () => {
285
+ (0, vitest_1.it)("stops at untrusted tool in delta mode", async () => {
286
+ const agent = makeAgent();
287
+ const agentConfig = { name: "test-agent", tools: [] }; // echo not trusted
288
+ const model = makeModel({
289
+ stream: vitest_1.vi.fn().mockImplementationOnce(async function* () {
290
+ yield {
291
+ event: "tool_call",
292
+ toolCallId: "c1",
293
+ name: "echo",
294
+ input: { msg: "hi" },
295
+ };
296
+ yield { event: "turn_stop", stopReason: "tool_use" };
297
+ }),
298
+ });
299
+ const s = new session_1.Session("s", agent, model, agentConfig, [], []);
300
+ const events = [];
301
+ for await (const e of s.runTurn({
294
302
  messages: [userMsg],
295
303
  stream: "delta",
304
+ }))
305
+ events.push(e);
306
+ (0, vitest_1.expect)(events.at(-1).event).toBe("turn_stop");
307
+ (0, vitest_1.expect)(model.stream).toHaveBeenCalledTimes(1);
308
+ });
309
+ });
310
+ (0, vitest_1.describe)("runTurn (message mode) — tool_use block and untrusted stop", () => {
311
+ (0, vitest_1.it)("yields tool_call event for tool_use blocks in message mode", async () => {
312
+ const model = makeModel({
313
+ call: vitest_1.vi.fn().mockResolvedValue({
314
+ stopReason: "tool_use",
315
+ messages: [
316
+ {
317
+ role: "assistant",
318
+ content: [{ type: "tool_use", toolCallId: "c1", name: "echo", input: { msg: "hi" } }],
319
+ },
320
+ ],
321
+ }),
296
322
  });
323
+ const s = makeSession({ name: "test-agent" }, model);
297
324
  const events = [];
298
- for await (const e of result)
325
+ for await (const e of s.runTurn({
326
+ messages: [userMsg],
327
+ stream: "message",
328
+ }))
329
+ events.push(e);
330
+ (0, vitest_1.expect)(events.some((e) => e.event === "tool_call")).toBe(true);
331
+ });
332
+ (0, vitest_1.it)("stops at untrusted tool in message mode", async () => {
333
+ const agent = makeAgent();
334
+ const agentConfig = { name: "test-agent", tools: [] };
335
+ const model = makeModel({
336
+ call: vitest_1.vi.fn().mockResolvedValue({
337
+ stopReason: "tool_use",
338
+ messages: [
339
+ {
340
+ role: "assistant",
341
+ content: [{ type: "tool_use", toolCallId: "c1", name: "echo", input: { msg: "hi" } }],
342
+ },
343
+ ],
344
+ }),
345
+ });
346
+ const s = new session_1.Session("s", agent, model, agentConfig, [], []);
347
+ const events = [];
348
+ for await (const e of s.runTurn({
349
+ messages: [userMsg],
350
+ stream: "message",
351
+ }))
299
352
  events.push(e);
300
- (0, vitest_1.expect)(events[0]).toEqual({ event: "session_start", sessionId: "sess-1" });
353
+ (0, vitest_1.expect)(events.at(-1).event).toBe("turn_stop");
354
+ (0, vitest_1.expect)(model.call).toHaveBeenCalledTimes(1);
355
+ });
356
+ });
357
+ (0, vitest_1.describe)("lastToolUses", () => {
358
+ (0, vitest_1.it)("returns empty array when last assistant message has string content", async () => {
359
+ const s = makeSession();
360
+ s.history = [{ role: "assistant", content: "hello" }];
361
+ // runTurnNone will call lastToolUses; with string content it should return []
362
+ const model = makeModel({
363
+ call: vitest_1.vi.fn().mockResolvedValue({ stopReason: "end_turn", messages: [] }),
364
+ });
365
+ const s2 = new session_1.Session("s", makeAgent(), model, { name: "test-agent" }, [], [{ role: "assistant", content: "hello" }]);
366
+ const res = (await s2.runTurn({ messages: [userMsg] }));
367
+ (0, vitest_1.expect)(res.stopReason).toBe("end_turn");
301
368
  });
302
369
  });
303
370
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentapplicationprotocol/server",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "AAP server",
5
5
  "keywords": [
6
6
  "aap",
@@ -31,7 +31,7 @@
31
31
  "ai": "^6.0.141",
32
32
  "hono": "^4.12.8",
33
33
  "zod": "^4.3.6",
34
- "@agentapplicationprotocol/core": "0.6.0"
34
+ "@agentapplicationprotocol/core": "0.7.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@ai-sdk/openai": "^3.0.48",