@townco/agent 0.1.34 → 0.1.36
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/acp-server/adapter.js +143 -21
- package/dist/acp-server/session-storage.d.ts +21 -1
- package/dist/acp-server/session-storage.js +32 -1
- package/dist/runner/agent-runner.d.ts +2 -1
- package/dist/runner/langchain/index.js +5 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as acp from "@agentclientprotocol/sdk";
|
|
2
|
-
import { SessionStorage } from "./session-storage.js";
|
|
2
|
+
import { SessionStorage, } from "./session-storage.js";
|
|
3
3
|
/**
|
|
4
4
|
* ACP extension key for subagent mode indicator
|
|
5
5
|
* Following ACP extensibility pattern with namespaced key
|
|
@@ -62,20 +62,76 @@ export class AgentAcpAdapter {
|
|
|
62
62
|
messages: storedSession.messages,
|
|
63
63
|
requestParams: { cwd: process.cwd(), mcpServers: [] },
|
|
64
64
|
});
|
|
65
|
-
// Replay conversation history to client
|
|
65
|
+
// Replay conversation history to client with ordered content blocks
|
|
66
66
|
console.log(`[adapter] Replaying ${storedSession.messages.length} messages for session ${params.sessionId}`);
|
|
67
67
|
for (const msg of storedSession.messages) {
|
|
68
|
-
console.log(`[adapter] Replaying message: ${msg.role}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
console.log(`[adapter] Replaying message: ${msg.role} with ${msg.content.length} content blocks`);
|
|
69
|
+
// Iterate through content blocks in order
|
|
70
|
+
for (const block of msg.content) {
|
|
71
|
+
if (block.type === "text") {
|
|
72
|
+
// Replay text block
|
|
73
|
+
this.connection.sessionUpdate({
|
|
74
|
+
sessionId: params.sessionId,
|
|
75
|
+
update: {
|
|
76
|
+
sessionUpdate: msg.role === "user"
|
|
77
|
+
? "user_message_chunk"
|
|
78
|
+
: "agent_message_chunk",
|
|
79
|
+
content: {
|
|
80
|
+
type: "text",
|
|
81
|
+
text: block.text,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else if (block.type === "tool_call") {
|
|
87
|
+
// Replay tool call directly in final state (skip pending → completed transitions)
|
|
88
|
+
this.connection.sessionUpdate({
|
|
89
|
+
sessionId: params.sessionId,
|
|
90
|
+
update: {
|
|
91
|
+
sessionUpdate: "tool_call",
|
|
92
|
+
toolCallId: block.id,
|
|
93
|
+
title: block.title,
|
|
94
|
+
kind: block.kind,
|
|
95
|
+
status: block.status, // Use final status directly
|
|
96
|
+
rawInput: block.rawInput,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
// If there's output, emit tool_output event for the UI to display
|
|
100
|
+
if (block.rawOutput) {
|
|
101
|
+
const outputUpdate = {
|
|
102
|
+
sessionUpdate: "tool_output",
|
|
103
|
+
toolCallId: block.id,
|
|
104
|
+
rawOutput: block.rawOutput,
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: "content",
|
|
108
|
+
content: {
|
|
109
|
+
type: "text",
|
|
110
|
+
text: typeof block.rawOutput.content === "string"
|
|
111
|
+
? block.rawOutput.content
|
|
112
|
+
: JSON.stringify(block.rawOutput),
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
this.connection.sessionUpdate({
|
|
118
|
+
sessionId: params.sessionId,
|
|
119
|
+
update: outputUpdate,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// Also emit tool_call_update with final status for consistency
|
|
123
|
+
this.connection.sessionUpdate({
|
|
124
|
+
sessionId: params.sessionId,
|
|
125
|
+
update: {
|
|
126
|
+
sessionUpdate: "tool_call_update",
|
|
127
|
+
toolCallId: block.id,
|
|
128
|
+
status: block.status,
|
|
129
|
+
rawOutput: block.rawOutput,
|
|
130
|
+
error: block.error,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
79
135
|
}
|
|
80
136
|
return {};
|
|
81
137
|
}
|
|
@@ -104,7 +160,7 @@ export class AgentAcpAdapter {
|
|
|
104
160
|
// Generate a unique messageId for this assistant response
|
|
105
161
|
const messageId = Math.random().toString(36).substring(2);
|
|
106
162
|
// Extract and store the user message
|
|
107
|
-
const
|
|
163
|
+
const userMessageText = params.prompt
|
|
108
164
|
.filter((p) => p.type === "text")
|
|
109
165
|
.map((p) => p.text)
|
|
110
166
|
.join("\n");
|
|
@@ -112,13 +168,21 @@ export class AgentAcpAdapter {
|
|
|
112
168
|
if (!this.noSession) {
|
|
113
169
|
const userMessage = {
|
|
114
170
|
role: "user",
|
|
115
|
-
content:
|
|
171
|
+
content: [{ type: "text", text: userMessageText }],
|
|
116
172
|
timestamp: new Date().toISOString(),
|
|
117
173
|
};
|
|
118
174
|
session.messages.push(userMessage);
|
|
119
175
|
}
|
|
120
|
-
//
|
|
121
|
-
|
|
176
|
+
// Build ordered content blocks for the assistant response
|
|
177
|
+
const contentBlocks = [];
|
|
178
|
+
let pendingText = "";
|
|
179
|
+
// Helper function to flush pending text as a TextBlock
|
|
180
|
+
const flushPendingText = () => {
|
|
181
|
+
if (pendingText.length > 0) {
|
|
182
|
+
contentBlocks.push({ type: "text", text: pendingText });
|
|
183
|
+
pendingText = "";
|
|
184
|
+
}
|
|
185
|
+
};
|
|
122
186
|
try {
|
|
123
187
|
const invokeParams = {
|
|
124
188
|
prompt: params.prompt,
|
|
@@ -132,7 +196,7 @@ export class AgentAcpAdapter {
|
|
|
132
196
|
invokeParams.sessionMeta = session.requestParams._meta;
|
|
133
197
|
}
|
|
134
198
|
for await (const msg of this.agent.invoke(invokeParams)) {
|
|
135
|
-
// Accumulate
|
|
199
|
+
// Accumulate text content from message chunks
|
|
136
200
|
if ("sessionUpdate" in msg &&
|
|
137
201
|
msg.sessionUpdate === "agent_message_chunk") {
|
|
138
202
|
if ("content" in msg &&
|
|
@@ -140,7 +204,7 @@ export class AgentAcpAdapter {
|
|
|
140
204
|
typeof msg.content === "object") {
|
|
141
205
|
const content = msg.content;
|
|
142
206
|
if (content.type === "text" && typeof content.text === "string") {
|
|
143
|
-
|
|
207
|
+
pendingText += content.text;
|
|
144
208
|
}
|
|
145
209
|
}
|
|
146
210
|
// Debug: log if this chunk has tokenUsage in _meta
|
|
@@ -151,6 +215,62 @@ export class AgentAcpAdapter {
|
|
|
151
215
|
console.log("DEBUG adapter: sending agent_message_chunk with tokenUsage in _meta:", JSON.stringify(msg._meta.tokenUsage));
|
|
152
216
|
}
|
|
153
217
|
}
|
|
218
|
+
// Handle tool_call - flush pending text and add ToolCallBlock
|
|
219
|
+
if ("sessionUpdate" in msg && msg.sessionUpdate === "tool_call") {
|
|
220
|
+
flushPendingText();
|
|
221
|
+
const toolCallMsg = msg;
|
|
222
|
+
const toolCall = {
|
|
223
|
+
type: "tool_call",
|
|
224
|
+
id: toolCallMsg.toolCallId || `tool_${Date.now()}`,
|
|
225
|
+
title: toolCallMsg.title || "Tool",
|
|
226
|
+
kind: toolCallMsg.kind || "other",
|
|
227
|
+
status: toolCallMsg.status || "pending",
|
|
228
|
+
startedAt: Date.now(),
|
|
229
|
+
};
|
|
230
|
+
// Only add rawInput if it exists
|
|
231
|
+
if (toolCallMsg.rawInput) {
|
|
232
|
+
toolCall.rawInput = toolCallMsg.rawInput;
|
|
233
|
+
}
|
|
234
|
+
contentBlocks.push(toolCall);
|
|
235
|
+
}
|
|
236
|
+
// Handle tool_call_update - update existing ToolCallBlock
|
|
237
|
+
if ("sessionUpdate" in msg &&
|
|
238
|
+
msg.sessionUpdate === "tool_call_update") {
|
|
239
|
+
const updateMsg = msg;
|
|
240
|
+
const toolCallBlock = contentBlocks.find((block) => block.type === "tool_call" && block.id === updateMsg.toolCallId);
|
|
241
|
+
if (toolCallBlock) {
|
|
242
|
+
if (updateMsg.status) {
|
|
243
|
+
toolCallBlock.status =
|
|
244
|
+
updateMsg.status;
|
|
245
|
+
}
|
|
246
|
+
if (updateMsg.rawOutput) {
|
|
247
|
+
toolCallBlock.rawOutput = updateMsg.rawOutput;
|
|
248
|
+
}
|
|
249
|
+
if (updateMsg.error) {
|
|
250
|
+
toolCallBlock.error = updateMsg.error;
|
|
251
|
+
}
|
|
252
|
+
if (toolCallBlock.status === "completed" ||
|
|
253
|
+
toolCallBlock.status === "failed") {
|
|
254
|
+
toolCallBlock.completedAt = Date.now();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Handle tool_output - update ToolCallBlock with output content
|
|
259
|
+
if ("sessionUpdate" in msg && msg.sessionUpdate === "tool_output") {
|
|
260
|
+
const outputMsg = msg;
|
|
261
|
+
const toolCallBlock = contentBlocks.find((block) => block.type === "tool_call" && block.id === outputMsg.toolCallId);
|
|
262
|
+
if (toolCallBlock) {
|
|
263
|
+
// Store both rawOutput and output from tool_output
|
|
264
|
+
if (outputMsg.rawOutput) {
|
|
265
|
+
toolCallBlock.rawOutput = outputMsg.rawOutput;
|
|
266
|
+
}
|
|
267
|
+
else if (outputMsg.output) {
|
|
268
|
+
toolCallBlock.rawOutput = outputMsg.output;
|
|
269
|
+
}
|
|
270
|
+
// Note: content blocks are handled by the transport for display
|
|
271
|
+
// We store the raw output here for session persistence
|
|
272
|
+
}
|
|
273
|
+
}
|
|
154
274
|
// The agent may emit extended types (like tool_output) that aren't in ACP SDK yet
|
|
155
275
|
// The http transport will handle routing these appropriately
|
|
156
276
|
this.connection.sessionUpdate({
|
|
@@ -158,6 +278,8 @@ export class AgentAcpAdapter {
|
|
|
158
278
|
update: msg,
|
|
159
279
|
});
|
|
160
280
|
}
|
|
281
|
+
// Flush any remaining pending text
|
|
282
|
+
flushPendingText();
|
|
161
283
|
}
|
|
162
284
|
catch (err) {
|
|
163
285
|
if (session.pendingPrompt.signal.aborted) {
|
|
@@ -167,10 +289,10 @@ export class AgentAcpAdapter {
|
|
|
167
289
|
}
|
|
168
290
|
// Store the complete assistant response in session messages
|
|
169
291
|
// Only store if session persistence is enabled
|
|
170
|
-
if (!this.noSession &&
|
|
292
|
+
if (!this.noSession && contentBlocks.length > 0) {
|
|
171
293
|
const assistantMessage = {
|
|
172
294
|
role: "assistant",
|
|
173
|
-
content:
|
|
295
|
+
content: contentBlocks,
|
|
174
296
|
timestamp: new Date().toISOString(),
|
|
175
297
|
};
|
|
176
298
|
session.messages.push(assistantMessage);
|
|
@@ -1,9 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content block types for messages
|
|
3
|
+
*/
|
|
4
|
+
export interface TextBlock {
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ToolCallBlock {
|
|
9
|
+
type: "tool_call";
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
|
|
13
|
+
status: "pending" | "in_progress" | "completed" | "failed";
|
|
14
|
+
rawInput?: Record<string, unknown> | undefined;
|
|
15
|
+
rawOutput?: Record<string, unknown> | undefined;
|
|
16
|
+
error?: string | undefined;
|
|
17
|
+
startedAt?: number | undefined;
|
|
18
|
+
completedAt?: number | undefined;
|
|
19
|
+
}
|
|
20
|
+
export type ContentBlock = TextBlock | ToolCallBlock;
|
|
1
21
|
/**
|
|
2
22
|
* Session message format stored in files
|
|
3
23
|
*/
|
|
4
24
|
export interface SessionMessage {
|
|
5
25
|
role: "user" | "assistant";
|
|
6
|
-
content:
|
|
26
|
+
content: ContentBlock[];
|
|
7
27
|
timestamp: string;
|
|
8
28
|
}
|
|
9
29
|
/**
|
|
@@ -4,9 +4,40 @@ import { z } from "zod";
|
|
|
4
4
|
/**
|
|
5
5
|
* Zod schema for validating session files
|
|
6
6
|
*/
|
|
7
|
+
const textBlockSchema = z.object({
|
|
8
|
+
type: z.literal("text"),
|
|
9
|
+
text: z.string(),
|
|
10
|
+
});
|
|
11
|
+
const toolCallBlockSchema = z.object({
|
|
12
|
+
type: z.literal("tool_call"),
|
|
13
|
+
id: z.string(),
|
|
14
|
+
title: z.string(),
|
|
15
|
+
kind: z.enum([
|
|
16
|
+
"read",
|
|
17
|
+
"edit",
|
|
18
|
+
"delete",
|
|
19
|
+
"move",
|
|
20
|
+
"search",
|
|
21
|
+
"execute",
|
|
22
|
+
"think",
|
|
23
|
+
"fetch",
|
|
24
|
+
"switch_mode",
|
|
25
|
+
"other",
|
|
26
|
+
]),
|
|
27
|
+
status: z.enum(["pending", "in_progress", "completed", "failed"]),
|
|
28
|
+
rawInput: z.record(z.string(), z.unknown()).optional(),
|
|
29
|
+
rawOutput: z.record(z.string(), z.unknown()).optional(),
|
|
30
|
+
error: z.string().optional(),
|
|
31
|
+
startedAt: z.number().optional(),
|
|
32
|
+
completedAt: z.number().optional(),
|
|
33
|
+
});
|
|
34
|
+
const contentBlockSchema = z.discriminatedUnion("type", [
|
|
35
|
+
textBlockSchema,
|
|
36
|
+
toolCallBlockSchema,
|
|
37
|
+
]);
|
|
7
38
|
const sessionMessageSchema = z.object({
|
|
8
39
|
role: z.enum(["user", "assistant"]),
|
|
9
|
-
content: z.
|
|
40
|
+
content: z.array(contentBlockSchema),
|
|
10
41
|
timestamp: z.string(),
|
|
11
42
|
});
|
|
12
43
|
const sessionMetadataSchema = z.object({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PromptRequest, PromptResponse, SessionNotification } from "@agentclientprotocol/sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import type { ContentBlock } from "../acp-server/session-storage.js";
|
|
3
4
|
export declare const zAgentRunnerParams: z.ZodObject<{
|
|
4
5
|
systemPrompt: z.ZodNullable<z.ZodString>;
|
|
5
6
|
model: z.ZodString;
|
|
@@ -31,7 +32,7 @@ export declare const zAgentRunnerParams: z.ZodObject<{
|
|
|
31
32
|
export type CreateAgentRunnerParams = z.infer<typeof zAgentRunnerParams>;
|
|
32
33
|
export interface SessionMessage {
|
|
33
34
|
role: "user" | "assistant";
|
|
34
|
-
content:
|
|
35
|
+
content: ContentBlock[];
|
|
35
36
|
timestamp: string;
|
|
36
37
|
}
|
|
37
38
|
export type InvokeRequest = Omit<PromptRequest, "_meta"> & {
|
|
@@ -145,7 +145,11 @@ export class LangchainAgent {
|
|
|
145
145
|
const historyMessages = req.sessionMessages.slice(0, -1);
|
|
146
146
|
messages = historyMessages.map((msg) => ({
|
|
147
147
|
type: msg.role === "user" ? "human" : "ai",
|
|
148
|
-
content
|
|
148
|
+
// Extract text from content blocks (ignore tool call blocks for LLM context)
|
|
149
|
+
content: msg.content
|
|
150
|
+
.filter((block) => block.type === "text")
|
|
151
|
+
.map((block) => block.text)
|
|
152
|
+
.join(""),
|
|
149
153
|
}));
|
|
150
154
|
// Add the current prompt as the final human message
|
|
151
155
|
const currentPromptText = req.prompt
|
|
@@ -301,7 +305,6 @@ export class LangchainAgent {
|
|
|
301
305
|
text: aiMessage.content,
|
|
302
306
|
},
|
|
303
307
|
};
|
|
304
|
-
console.log("DEBUG agent: yielding message (string content):", JSON.stringify(msgToYield));
|
|
305
308
|
yield msgToYield;
|
|
306
309
|
}
|
|
307
310
|
else if (Array.isArray(aiMessage.content)) {
|
|
@@ -325,7 +328,6 @@ export class LangchainAgent {
|
|
|
325
328
|
text: part.text,
|
|
326
329
|
},
|
|
327
330
|
};
|
|
328
|
-
console.log("DEBUG agent: yielding message (array content):", JSON.stringify(msgToYield));
|
|
329
331
|
yield msgToYield;
|
|
330
332
|
}
|
|
331
333
|
else if (part.type === "tool_use") {
|