@townco/agent 0.1.52 → 0.1.54
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.d.ts +18 -0
- package/dist/acp-server/adapter.js +258 -19
- package/dist/acp-server/http.js +39 -1
- package/dist/acp-server/session-storage.d.ts +18 -1
- package/dist/acp-server/session-storage.js +25 -0
- package/dist/definition/index.d.ts +2 -2
- package/dist/definition/index.js +1 -0
- package/dist/runner/agent-runner.d.ts +11 -2
- package/dist/runner/langchain/index.d.ts +0 -1
- package/dist/runner/langchain/index.js +265 -64
- package/dist/runner/langchain/tools/generate_image.d.ts +28 -0
- package/dist/runner/langchain/tools/generate_image.js +135 -0
- package/dist/runner/langchain/tools/subagent.d.ts +6 -1
- package/dist/runner/langchain/tools/subagent.js +12 -2
- package/dist/runner/tools.d.ts +19 -2
- package/dist/runner/tools.js +9 -0
- package/dist/telemetry/index.js +7 -1
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.js +26 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/context-size-calculator.d.ts +9 -4
- package/dist/utils/context-size-calculator.js +23 -6
- package/dist/utils/tool-overhead-calculator.d.ts +30 -0
- package/dist/utils/tool-overhead-calculator.js +54 -0
- package/package.json +7 -6
- package/templates/index.ts +36 -5
- package/dist/check-jaeger.d.ts +0 -5
- package/dist/check-jaeger.js +0 -82
- package/dist/run-subagents.d.ts +0 -9
- package/dist/run-subagents.js +0 -110
- package/dist/runner/langchain/custom-stream-types.d.ts +0 -36
- package/dist/runner/langchain/custom-stream-types.js +0 -23
- package/dist/runner/langchain/tools/bash.d.ts +0 -14
- package/dist/runner/langchain/tools/bash.js +0 -135
- package/dist/scaffold/link-local.d.ts +0 -1
- package/dist/scaffold/link-local.js +0 -54
- package/dist/test-telemetry.d.ts +0 -5
- package/dist/test-telemetry.js +0 -88
- package/dist/utils/logger.d.ts +0 -39
- package/dist/utils/logger.js +0 -175
|
@@ -24,7 +24,23 @@ export declare class AgentAcpAdapter implements acp.Agent {
|
|
|
24
24
|
private agentVersion;
|
|
25
25
|
private agentDescription;
|
|
26
26
|
private agentSuggestedPrompts;
|
|
27
|
+
private currentToolOverheadTokens;
|
|
28
|
+
private currentMcpOverheadTokens;
|
|
27
29
|
constructor(agent: AgentRunner, connection: acp.AgentSideConnection, agentDir?: string, agentName?: string);
|
|
30
|
+
/**
|
|
31
|
+
* Extract tool metadata from the agent definition for exposing to clients.
|
|
32
|
+
* This provides basic info about available tools without loading them fully.
|
|
33
|
+
*/
|
|
34
|
+
private getToolsMetadata;
|
|
35
|
+
/**
|
|
36
|
+
* Extract MCP metadata from the agent definition for exposing to clients.
|
|
37
|
+
*/
|
|
38
|
+
private getMcpsMetadata;
|
|
39
|
+
/**
|
|
40
|
+
* Extract subagent metadata from the Task tool if present.
|
|
41
|
+
* This provides info about subagents configured via makeSubagentsTool.
|
|
42
|
+
*/
|
|
43
|
+
private getSubagentsMetadata;
|
|
28
44
|
/**
|
|
29
45
|
* Helper to save session to disk
|
|
30
46
|
* Call this after any modification to session.messages or session.context
|
|
@@ -36,10 +52,12 @@ export declare class AgentAcpAdapter implements acp.Agent {
|
|
|
36
52
|
authenticate(_params: acp.AuthenticateRequest): Promise<acp.AuthenticateResponse | undefined>;
|
|
37
53
|
setSessionMode(_params: acp.SetSessionModeRequest): Promise<acp.SetSessionModeResponse>;
|
|
38
54
|
prompt(params: acp.PromptRequest): Promise<acp.PromptResponse>;
|
|
55
|
+
private _promptImpl;
|
|
39
56
|
/**
|
|
40
57
|
* Execute hooks if configured for this agent
|
|
41
58
|
* Returns new context entries that should be appended to session.context
|
|
42
59
|
*/
|
|
43
60
|
private executeHooksIfConfigured;
|
|
61
|
+
private _executeHooksImpl;
|
|
44
62
|
cancel(params: acp.CancelNotification): Promise<void>;
|
|
45
63
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as acp from "@agentclientprotocol/sdk";
|
|
2
|
+
import { context, trace } from "@opentelemetry/api";
|
|
2
3
|
import { createLogger } from "@townco/core";
|
|
3
4
|
import { HookExecutor, loadHookCallback } from "../runner/hooks";
|
|
5
|
+
import { telemetry } from "../telemetry/index.js";
|
|
4
6
|
import { calculateContextSize, } from "../utils/context-size-calculator.js";
|
|
5
7
|
import { countToolResultTokens } from "../utils/token-counter.js";
|
|
6
8
|
import { SessionStorage, } from "./session-storage.js";
|
|
@@ -103,6 +105,8 @@ export class AgentAcpAdapter {
|
|
|
103
105
|
agentVersion;
|
|
104
106
|
agentDescription;
|
|
105
107
|
agentSuggestedPrompts;
|
|
108
|
+
currentToolOverheadTokens = 0; // Track tool overhead for current turn
|
|
109
|
+
currentMcpOverheadTokens = 0; // Track MCP overhead for current turn
|
|
106
110
|
constructor(agent, connection, agentDir, agentName) {
|
|
107
111
|
this.connection = connection;
|
|
108
112
|
this.sessions = new Map();
|
|
@@ -130,6 +134,82 @@ export class AgentAcpAdapter {
|
|
|
130
134
|
sessionStoragePath: this.storage ? `${agentDir}/.sessions` : null,
|
|
131
135
|
});
|
|
132
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Extract tool metadata from the agent definition for exposing to clients.
|
|
139
|
+
* This provides basic info about available tools without loading them fully.
|
|
140
|
+
*/
|
|
141
|
+
getToolsMetadata() {
|
|
142
|
+
const tools = this.agent.definition.tools ?? [];
|
|
143
|
+
return tools.map((tool) => {
|
|
144
|
+
if (typeof tool === "string") {
|
|
145
|
+
// Built-in tool - return basic info
|
|
146
|
+
return { name: tool, description: `Built-in tool: ${tool}` };
|
|
147
|
+
}
|
|
148
|
+
else if (tool.type === "direct") {
|
|
149
|
+
return {
|
|
150
|
+
name: tool.name,
|
|
151
|
+
description: tool.description,
|
|
152
|
+
...(tool.prettyName ? { prettyName: tool.prettyName } : {}),
|
|
153
|
+
...(tool.icon ? { icon: tool.icon } : {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
else if (tool.type === "filesystem") {
|
|
157
|
+
return {
|
|
158
|
+
name: "filesystem",
|
|
159
|
+
description: "File system access tools",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
else if (tool.type === "custom") {
|
|
163
|
+
// Custom tools from module paths - extract name from path
|
|
164
|
+
const pathParts = tool.modulePath.split("/");
|
|
165
|
+
const fileName = pathParts[pathParts.length - 1] ?? "custom";
|
|
166
|
+
const name = fileName.replace(/\.(ts|js)$/, "");
|
|
167
|
+
return {
|
|
168
|
+
name,
|
|
169
|
+
description: `Custom tool from ${tool.modulePath}`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return { name: "unknown", description: "Unknown tool type" };
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Extract MCP metadata from the agent definition for exposing to clients.
|
|
177
|
+
*/
|
|
178
|
+
getMcpsMetadata() {
|
|
179
|
+
const mcps = this.agent.definition.mcps ?? [];
|
|
180
|
+
return mcps.map((mcp) => {
|
|
181
|
+
if (typeof mcp === "string") {
|
|
182
|
+
return {
|
|
183
|
+
name: mcp,
|
|
184
|
+
transport: "http", // String configs are resolved to HTTP proxy
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
name: mcp.name,
|
|
189
|
+
transport: mcp.transport,
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Extract subagent metadata from the Task tool if present.
|
|
195
|
+
* This provides info about subagents configured via makeSubagentsTool.
|
|
196
|
+
*/
|
|
197
|
+
getSubagentsMetadata() {
|
|
198
|
+
const tools = this.agent.definition.tools ?? [];
|
|
199
|
+
for (const tool of tools) {
|
|
200
|
+
// Look for direct tools with subagentConfigs (Task tools created by makeSubagentsTool)
|
|
201
|
+
if (typeof tool === "object" &&
|
|
202
|
+
tool.type === "direct" &&
|
|
203
|
+
"subagentConfigs" in tool &&
|
|
204
|
+
Array.isArray(tool.subagentConfigs)) {
|
|
205
|
+
return tool.subagentConfigs.map((config) => ({
|
|
206
|
+
name: config.agentName,
|
|
207
|
+
description: config.description,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
133
213
|
/**
|
|
134
214
|
* Helper to save session to disk
|
|
135
215
|
* Call this after any modification to session.messages or session.context
|
|
@@ -166,19 +246,23 @@ export class AgentAcpAdapter {
|
|
|
166
246
|
...(this.agentDisplayName ? { title: this.agentDisplayName } : {}),
|
|
167
247
|
};
|
|
168
248
|
}
|
|
169
|
-
// Pass
|
|
170
|
-
// since Implementation doesn't support these fields
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
249
|
+
// Pass extended agent info via _meta extension point
|
|
250
|
+
// since ACP Implementation type doesn't support these fields directly
|
|
251
|
+
const toolsMetadata = this.getToolsMetadata();
|
|
252
|
+
const mcpsMetadata = this.getMcpsMetadata();
|
|
253
|
+
const subagentsMetadata = this.getSubagentsMetadata();
|
|
254
|
+
response._meta = {
|
|
255
|
+
...response._meta,
|
|
256
|
+
...(this.agentDescription
|
|
257
|
+
? { agentDescription: this.agentDescription }
|
|
258
|
+
: {}),
|
|
259
|
+
...(this.agentSuggestedPrompts
|
|
260
|
+
? { suggestedPrompts: this.agentSuggestedPrompts }
|
|
261
|
+
: {}),
|
|
262
|
+
...(toolsMetadata.length > 0 ? { tools: toolsMetadata } : {}),
|
|
263
|
+
...(mcpsMetadata.length > 0 ? { mcps: mcpsMetadata } : {}),
|
|
264
|
+
...(subagentsMetadata.length > 0 ? { subagents: subagentsMetadata } : {}),
|
|
265
|
+
};
|
|
182
266
|
return response;
|
|
183
267
|
}
|
|
184
268
|
async newSession(params) {
|
|
@@ -234,6 +318,14 @@ export class AgentAcpAdapter {
|
|
|
234
318
|
}
|
|
235
319
|
else if (block.type === "tool_call") {
|
|
236
320
|
// Replay tool call directly in final state (skip pending → completed transitions)
|
|
321
|
+
const replayMeta = {
|
|
322
|
+
isReplay: true,
|
|
323
|
+
...(block.prettyName ? { prettyName: block.prettyName } : {}),
|
|
324
|
+
...(block.icon ? { icon: block.icon } : {}),
|
|
325
|
+
...(block.subline ? { subline: block.subline } : {}),
|
|
326
|
+
...(block.batchId ? { batchId: block.batchId } : {}),
|
|
327
|
+
...block._meta,
|
|
328
|
+
};
|
|
237
329
|
this.connection.sessionUpdate({
|
|
238
330
|
sessionId: params.sessionId,
|
|
239
331
|
update: {
|
|
@@ -243,6 +335,7 @@ export class AgentAcpAdapter {
|
|
|
243
335
|
kind: block.kind,
|
|
244
336
|
status: block.status, // Use final status directly
|
|
245
337
|
rawInput: block.rawInput,
|
|
338
|
+
_meta: replayMeta,
|
|
246
339
|
},
|
|
247
340
|
});
|
|
248
341
|
// If there's output, emit tool_output event for the UI to display
|
|
@@ -324,6 +417,22 @@ export class AgentAcpAdapter {
|
|
|
324
417
|
return {};
|
|
325
418
|
}
|
|
326
419
|
async prompt(params) {
|
|
420
|
+
const promptSpan = telemetry.startSpan("adapter.prompt", {
|
|
421
|
+
"session.id": params.sessionId,
|
|
422
|
+
});
|
|
423
|
+
const spanContext = promptSpan
|
|
424
|
+
? trace.setSpan(context.active(), promptSpan)
|
|
425
|
+
: context.active();
|
|
426
|
+
return context.with(spanContext, async () => {
|
|
427
|
+
try {
|
|
428
|
+
return await this._promptImpl(params);
|
|
429
|
+
}
|
|
430
|
+
finally {
|
|
431
|
+
telemetry.endSpan(promptSpan);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
async _promptImpl(params) {
|
|
327
436
|
let session = this.sessions.get(params.sessionId);
|
|
328
437
|
// If session not found (e.g., after server restart), create a new one
|
|
329
438
|
if (!session) {
|
|
@@ -338,9 +447,34 @@ export class AgentAcpAdapter {
|
|
|
338
447
|
}
|
|
339
448
|
session.pendingPrompt?.abort();
|
|
340
449
|
session.pendingPrompt = new AbortController();
|
|
450
|
+
// Reset tool overhead for new turn (will be set by harness)
|
|
451
|
+
this.currentToolOverheadTokens = 0;
|
|
452
|
+
this.currentMcpOverheadTokens = 0;
|
|
341
453
|
// Generate a unique messageId for this assistant response
|
|
342
454
|
const messageId = Math.random().toString(36).substring(2);
|
|
343
|
-
//
|
|
455
|
+
// Convert prompt content blocks to session storage format
|
|
456
|
+
const userContentBlocks = params.prompt.map((block) => {
|
|
457
|
+
if (block.type === "text") {
|
|
458
|
+
return { type: "text", text: block.text };
|
|
459
|
+
}
|
|
460
|
+
else if (block.type === "image") {
|
|
461
|
+
// Preserve image block with all its data
|
|
462
|
+
const imgBlock = block;
|
|
463
|
+
const imageBlock = { type: "image" };
|
|
464
|
+
if (imgBlock.source)
|
|
465
|
+
imageBlock.source = imgBlock.source;
|
|
466
|
+
if (imgBlock.url)
|
|
467
|
+
imageBlock.url = imgBlock.url;
|
|
468
|
+
if (imgBlock.data)
|
|
469
|
+
imageBlock.data = imgBlock.data;
|
|
470
|
+
if (imgBlock.mimeType)
|
|
471
|
+
imageBlock.mimeType = imgBlock.mimeType;
|
|
472
|
+
return imageBlock;
|
|
473
|
+
}
|
|
474
|
+
// Fallback for unknown types - convert to text
|
|
475
|
+
return { type: "text", text: JSON.stringify(block) };
|
|
476
|
+
});
|
|
477
|
+
// Extract text for logging
|
|
344
478
|
const userMessageText = params.prompt
|
|
345
479
|
.filter((p) => p.type === "text")
|
|
346
480
|
.map((p) => p.text)
|
|
@@ -354,7 +488,7 @@ export class AgentAcpAdapter {
|
|
|
354
488
|
if (!this.noSession) {
|
|
355
489
|
const userMessage = {
|
|
356
490
|
role: "user",
|
|
357
|
-
content:
|
|
491
|
+
content: userContentBlocks,
|
|
358
492
|
timestamp: new Date().toISOString(),
|
|
359
493
|
};
|
|
360
494
|
session.messages.push(userMessage);
|
|
@@ -397,7 +531,9 @@ export class AgentAcpAdapter {
|
|
|
397
531
|
}
|
|
398
532
|
}
|
|
399
533
|
// Calculate context size - no LLM call yet, so only estimated values
|
|
400
|
-
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined
|
|
534
|
+
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined, // No LLM-reported tokens yet
|
|
535
|
+
this.currentToolOverheadTokens, // Include tool overhead
|
|
536
|
+
this.currentMcpOverheadTokens);
|
|
401
537
|
const contextSnapshot = createContextSnapshot(session.messages.length, new Date().toISOString(), previousContext, context_size);
|
|
402
538
|
session.context.push(contextSnapshot);
|
|
403
539
|
await this.saveSessionToDisk(params.sessionId, session);
|
|
@@ -461,6 +597,20 @@ export class AgentAcpAdapter {
|
|
|
461
597
|
let iterResult = await generator.next();
|
|
462
598
|
while (!iterResult.done) {
|
|
463
599
|
const msg = iterResult.value;
|
|
600
|
+
// Capture tool overhead info if provided by harness
|
|
601
|
+
if ("sessionUpdate" in msg &&
|
|
602
|
+
msg.sessionUpdate === "tool_overhead_info") {
|
|
603
|
+
const overheadInfo = msg;
|
|
604
|
+
this.currentToolOverheadTokens = overheadInfo.toolOverheadTokens;
|
|
605
|
+
this.currentMcpOverheadTokens = overheadInfo.mcpOverheadTokens;
|
|
606
|
+
logger.debug("Received tool overhead info from harness", {
|
|
607
|
+
toolOverheadTokens: this.currentToolOverheadTokens,
|
|
608
|
+
mcpOverheadTokens: this.currentMcpOverheadTokens,
|
|
609
|
+
});
|
|
610
|
+
// Don't send this update to client, it's internal metadata
|
|
611
|
+
iterResult = await generator.next();
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
464
614
|
// Extract and accumulate token usage from message chunks
|
|
465
615
|
if ("sessionUpdate" in msg &&
|
|
466
616
|
msg.sessionUpdate === "agent_message_chunk" &&
|
|
@@ -516,10 +666,50 @@ export class AgentAcpAdapter {
|
|
|
516
666
|
if ("sessionUpdate" in msg && msg.sessionUpdate === "tool_call") {
|
|
517
667
|
flushPendingText();
|
|
518
668
|
const toolCallMsg = msg;
|
|
669
|
+
// Extract prettyName, icon, and batchId from _meta
|
|
670
|
+
const prettyName = toolCallMsg._meta &&
|
|
671
|
+
typeof toolCallMsg._meta === "object" &&
|
|
672
|
+
"prettyName" in toolCallMsg._meta
|
|
673
|
+
? String(toolCallMsg._meta.prettyName)
|
|
674
|
+
: undefined;
|
|
675
|
+
const icon = toolCallMsg._meta &&
|
|
676
|
+
typeof toolCallMsg._meta === "object" &&
|
|
677
|
+
"icon" in toolCallMsg._meta
|
|
678
|
+
? String(toolCallMsg._meta.icon)
|
|
679
|
+
: undefined;
|
|
680
|
+
const batchId = toolCallMsg._meta &&
|
|
681
|
+
typeof toolCallMsg._meta === "object" &&
|
|
682
|
+
"batchId" in toolCallMsg._meta
|
|
683
|
+
? String(toolCallMsg._meta.batchId)
|
|
684
|
+
: undefined;
|
|
685
|
+
// Extract subline from _meta, or compute it for todo_write from rawInput
|
|
686
|
+
let subline = toolCallMsg._meta &&
|
|
687
|
+
typeof toolCallMsg._meta === "object" &&
|
|
688
|
+
"subline" in toolCallMsg._meta
|
|
689
|
+
? String(toolCallMsg._meta.subline)
|
|
690
|
+
: undefined;
|
|
691
|
+
// For todo_write, compute subline from in_progress item's activeForm
|
|
692
|
+
if (!subline &&
|
|
693
|
+
toolCallMsg.title === "todo_write" &&
|
|
694
|
+
toolCallMsg.rawInput &&
|
|
695
|
+
"todos" in toolCallMsg.rawInput &&
|
|
696
|
+
Array.isArray(toolCallMsg.rawInput.todos)) {
|
|
697
|
+
const inProgressItem = toolCallMsg.rawInput.todos.find((todo) => todo.status === "in_progress");
|
|
698
|
+
if (inProgressItem &&
|
|
699
|
+
typeof inProgressItem === "object" &&
|
|
700
|
+
"activeForm" in inProgressItem &&
|
|
701
|
+
typeof inProgressItem.activeForm === "string") {
|
|
702
|
+
subline = inProgressItem.activeForm;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
519
705
|
const toolCall = {
|
|
520
706
|
type: "tool_call",
|
|
521
707
|
id: toolCallMsg.toolCallId || `tool_${Date.now()}`,
|
|
708
|
+
...(batchId ? { batchId } : {}),
|
|
522
709
|
title: toolCallMsg.title || "Tool",
|
|
710
|
+
...(prettyName ? { prettyName } : {}),
|
|
711
|
+
...(icon ? { icon } : {}),
|
|
712
|
+
...(subline ? { subline } : {}),
|
|
523
713
|
kind: toolCallMsg.kind || "other",
|
|
524
714
|
status: toolCallMsg.status || "pending",
|
|
525
715
|
startedAt: Date.now(),
|
|
@@ -659,7 +849,9 @@ export class AgentAcpAdapter {
|
|
|
659
849
|
}
|
|
660
850
|
}
|
|
661
851
|
// Calculate context size - tool result is now in the message, but hasn't been sent to LLM yet
|
|
662
|
-
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined
|
|
852
|
+
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined, // Tool result hasn't been sent to LLM yet, so no new LLM-reported tokens
|
|
853
|
+
this.currentToolOverheadTokens, // Include tool overhead
|
|
854
|
+
this.currentMcpOverheadTokens);
|
|
663
855
|
// Create snapshot with a pointer to the partial message (not a full copy!)
|
|
664
856
|
const midTurnSnapshot = {
|
|
665
857
|
timestamp: new Date().toISOString(),
|
|
@@ -691,8 +883,35 @@ export class AgentAcpAdapter {
|
|
|
691
883
|
}
|
|
692
884
|
// The agent may emit extended types (like tool_output) that aren't in ACP SDK yet
|
|
693
885
|
// The http transport will handle routing these appropriately
|
|
694
|
-
//
|
|
886
|
+
// Enhance todo_write tool_call with subline showing in-progress item
|
|
695
887
|
let enhancedMsg = msg;
|
|
888
|
+
if ("sessionUpdate" in msg &&
|
|
889
|
+
msg.sessionUpdate === "tool_call" &&
|
|
890
|
+
"title" in msg &&
|
|
891
|
+
msg.title === "todo_write" &&
|
|
892
|
+
"rawInput" in msg &&
|
|
893
|
+
msg.rawInput &&
|
|
894
|
+
typeof msg.rawInput === "object" &&
|
|
895
|
+
"todos" in msg.rawInput &&
|
|
896
|
+
Array.isArray(msg.rawInput.todos)) {
|
|
897
|
+
// Find the in_progress item and extract its activeForm
|
|
898
|
+
const inProgressItem = msg.rawInput.todos.find((todo) => todo.status === "in_progress");
|
|
899
|
+
if (inProgressItem &&
|
|
900
|
+
typeof inProgressItem === "object" &&
|
|
901
|
+
"activeForm" in inProgressItem &&
|
|
902
|
+
typeof inProgressItem.activeForm === "string") {
|
|
903
|
+
enhancedMsg = {
|
|
904
|
+
...msg,
|
|
905
|
+
_meta: {
|
|
906
|
+
...(typeof msg._meta === "object" && msg._meta !== null
|
|
907
|
+
? msg._meta
|
|
908
|
+
: {}),
|
|
909
|
+
subline: inProgressItem.activeForm,
|
|
910
|
+
},
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
// Add context input tokens to messages with token usage metadata
|
|
696
915
|
if (!this.noSession &&
|
|
697
916
|
"_meta" in msg &&
|
|
698
917
|
msg._meta &&
|
|
@@ -779,7 +998,9 @@ export class AgentAcpAdapter {
|
|
|
779
998
|
}
|
|
780
999
|
}
|
|
781
1000
|
// Calculate context size with LLM-reported tokens from this turn
|
|
782
|
-
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, turnTokenUsage.inputTokens
|
|
1001
|
+
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, turnTokenUsage.inputTokens, // Final LLM-reported tokens from this turn
|
|
1002
|
+
this.currentToolOverheadTokens, // Include tool overhead
|
|
1003
|
+
this.currentMcpOverheadTokens);
|
|
783
1004
|
const contextSnapshot = createContextSnapshot(session.messages.length, new Date().toISOString(), previousContext, context_size);
|
|
784
1005
|
session.context.push(contextSnapshot);
|
|
785
1006
|
await this.saveSessionToDisk(params.sessionId, session);
|
|
@@ -799,6 +1020,24 @@ export class AgentAcpAdapter {
|
|
|
799
1020
|
if (this.noSession || !hooks || hooks.length === 0) {
|
|
800
1021
|
return [];
|
|
801
1022
|
}
|
|
1023
|
+
const hookSpan = telemetry.startSpan("adapter.executeHooks", {
|
|
1024
|
+
"hooks.executionPoint": executionPoint,
|
|
1025
|
+
"hooks.count": hooks.length,
|
|
1026
|
+
"session.id": sessionId,
|
|
1027
|
+
});
|
|
1028
|
+
const spanContext = hookSpan
|
|
1029
|
+
? trace.setSpan(context.active(), hookSpan)
|
|
1030
|
+
: context.active();
|
|
1031
|
+
return context.with(spanContext, async () => {
|
|
1032
|
+
try {
|
|
1033
|
+
return await this._executeHooksImpl(session, sessionId, executionPoint, hooks);
|
|
1034
|
+
}
|
|
1035
|
+
finally {
|
|
1036
|
+
telemetry.endSpan(hookSpan);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
async _executeHooksImpl(session, sessionId, executionPoint, hooks) {
|
|
802
1041
|
logger.info(`Executing hooks at ${executionPoint}`, {
|
|
803
1042
|
hooksLength: hooks.length,
|
|
804
1043
|
contextEntries: session.context.length,
|
package/dist/acp-server/http.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
3
|
import { gzipSync } from "node:zlib";
|
|
4
4
|
import * as acp from "@agentclientprotocol/sdk";
|
|
5
5
|
import { PGlite } from "@electric-sql/pglite";
|
|
@@ -264,6 +264,44 @@ export function makeHttpTransport(agent, agentDir, agentName) {
|
|
|
264
264
|
allowMethods: ["GET", "POST", "OPTIONS"],
|
|
265
265
|
}));
|
|
266
266
|
app.get("/health", (c) => c.json({ ok: true }));
|
|
267
|
+
// Serve static files from agent directory (for generated images, etc.)
|
|
268
|
+
if (agentDir) {
|
|
269
|
+
app.get("/static/*", async (c) => {
|
|
270
|
+
const path = c.req.path.replace(/^\/static\//, "");
|
|
271
|
+
const filePath = join(agentDir, path);
|
|
272
|
+
// Security check: ensure the file is within the agent directory
|
|
273
|
+
const normalizedPath = resolve(filePath);
|
|
274
|
+
const normalizedAgentDir = resolve(agentDir);
|
|
275
|
+
if (!normalizedPath.startsWith(normalizedAgentDir)) {
|
|
276
|
+
logger.warn("Attempted to access file outside agent directory", {
|
|
277
|
+
path,
|
|
278
|
+
filePath,
|
|
279
|
+
agentDir,
|
|
280
|
+
});
|
|
281
|
+
return c.json({ error: "Invalid path" }, 403);
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
const file = Bun.file(filePath);
|
|
285
|
+
const exists = await file.exists();
|
|
286
|
+
if (!exists) {
|
|
287
|
+
logger.warn("Static file not found", { path, filePath });
|
|
288
|
+
return c.json({ error: "File not found" }, 404);
|
|
289
|
+
}
|
|
290
|
+
const buffer = await file.arrayBuffer();
|
|
291
|
+
const contentType = file.type || "application/octet-stream";
|
|
292
|
+
return new Response(buffer, {
|
|
293
|
+
headers: {
|
|
294
|
+
"Content-Type": contentType,
|
|
295
|
+
"Cache-Control": "public, max-age=31536000",
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
logger.error("Error serving static file", { error, path, filePath });
|
|
301
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
267
305
|
app.get("/events", (c) => {
|
|
268
306
|
const sessionId = c.req.header("X-Session-ID");
|
|
269
307
|
if (!sessionId) {
|
|
@@ -5,10 +5,25 @@ export interface TextBlock {
|
|
|
5
5
|
type: "text";
|
|
6
6
|
text: string;
|
|
7
7
|
}
|
|
8
|
+
export interface ImageBlock {
|
|
9
|
+
type: "image";
|
|
10
|
+
source?: {
|
|
11
|
+
type: "base64";
|
|
12
|
+
media_type: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
|
|
13
|
+
data: string;
|
|
14
|
+
} | undefined;
|
|
15
|
+
url?: string | undefined;
|
|
16
|
+
data?: string | undefined;
|
|
17
|
+
mimeType?: string | undefined;
|
|
18
|
+
}
|
|
8
19
|
export interface ToolCallBlock {
|
|
9
20
|
type: "tool_call";
|
|
10
21
|
id: string;
|
|
22
|
+
batchId?: string | undefined;
|
|
11
23
|
title: string;
|
|
24
|
+
prettyName?: string | undefined;
|
|
25
|
+
icon?: string | undefined;
|
|
26
|
+
subline?: string | undefined;
|
|
12
27
|
kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
|
|
13
28
|
status: "pending" | "in_progress" | "completed" | "failed";
|
|
14
29
|
rawInput?: Record<string, unknown> | undefined;
|
|
@@ -23,7 +38,7 @@ export interface ToolCallBlock {
|
|
|
23
38
|
finalTokens?: number;
|
|
24
39
|
};
|
|
25
40
|
}
|
|
26
|
-
export type ContentBlock = TextBlock | ToolCallBlock;
|
|
41
|
+
export type ContentBlock = TextBlock | ImageBlock | ToolCallBlock;
|
|
27
42
|
/**
|
|
28
43
|
* Session message format stored in files
|
|
29
44
|
*/
|
|
@@ -62,6 +77,8 @@ export interface ContextEntry {
|
|
|
62
77
|
*/
|
|
63
78
|
context_size: {
|
|
64
79
|
systemPromptTokens: number;
|
|
80
|
+
toolOverheadTokens?: number | undefined;
|
|
81
|
+
mcpOverheadTokens?: number | undefined;
|
|
65
82
|
userMessagesTokens: number;
|
|
66
83
|
assistantMessagesTokens: number;
|
|
67
84
|
toolInputTokens: number;
|
|
@@ -8,10 +8,32 @@ const textBlockSchema = z.object({
|
|
|
8
8
|
type: z.literal("text"),
|
|
9
9
|
text: z.string(),
|
|
10
10
|
});
|
|
11
|
+
const imageBlockSchema = z.object({
|
|
12
|
+
type: z.literal("image"),
|
|
13
|
+
source: z
|
|
14
|
+
.object({
|
|
15
|
+
type: z.literal("base64"),
|
|
16
|
+
media_type: z.enum([
|
|
17
|
+
"image/jpeg",
|
|
18
|
+
"image/png",
|
|
19
|
+
"image/gif",
|
|
20
|
+
"image/webp",
|
|
21
|
+
]),
|
|
22
|
+
data: z.string(),
|
|
23
|
+
})
|
|
24
|
+
.optional(),
|
|
25
|
+
url: z.string().optional(),
|
|
26
|
+
data: z.string().optional(),
|
|
27
|
+
mimeType: z.string().optional(),
|
|
28
|
+
});
|
|
11
29
|
const toolCallBlockSchema = z.object({
|
|
12
30
|
type: z.literal("tool_call"),
|
|
13
31
|
id: z.string(),
|
|
32
|
+
batchId: z.string().optional(),
|
|
14
33
|
title: z.string(),
|
|
34
|
+
prettyName: z.string().optional(),
|
|
35
|
+
icon: z.string().optional(),
|
|
36
|
+
subline: z.string().optional(),
|
|
15
37
|
kind: z.enum([
|
|
16
38
|
"read",
|
|
17
39
|
"edit",
|
|
@@ -33,6 +55,7 @@ const toolCallBlockSchema = z.object({
|
|
|
33
55
|
});
|
|
34
56
|
const contentBlockSchema = z.discriminatedUnion("type", [
|
|
35
57
|
textBlockSchema,
|
|
58
|
+
imageBlockSchema,
|
|
36
59
|
toolCallBlockSchema,
|
|
37
60
|
]);
|
|
38
61
|
const sessionMessageSchema = z.object({
|
|
@@ -58,6 +81,8 @@ const contextEntrySchema = z.object({
|
|
|
58
81
|
compactedUpTo: z.number().optional(),
|
|
59
82
|
context_size: z.object({
|
|
60
83
|
systemPromptTokens: z.number(),
|
|
84
|
+
toolOverheadTokens: z.number().optional(),
|
|
85
|
+
mcpOverheadTokens: z.number().optional(),
|
|
61
86
|
userMessagesTokens: z.number(),
|
|
62
87
|
assistantMessagesTokens: z.number(),
|
|
63
88
|
toolInputTokens: z.number(),
|
|
@@ -11,7 +11,7 @@ export declare const McpConfigSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
11
11
|
transport: z.ZodLiteral<"http">;
|
|
12
12
|
url: z.ZodString;
|
|
13
13
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
14
|
-
}, z.core.$strip
|
|
14
|
+
}, z.core.$strip>, z.ZodString]>;
|
|
15
15
|
/** Hook configuration schema. */
|
|
16
16
|
export declare const HookConfigSchema: z.ZodObject<{
|
|
17
17
|
type: z.ZodEnum<{
|
|
@@ -59,7 +59,7 @@ export declare const AgentDefinitionSchema: z.ZodObject<{
|
|
|
59
59
|
transport: z.ZodLiteral<"http">;
|
|
60
60
|
url: z.ZodString;
|
|
61
61
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
62
|
-
}, z.core.$strip
|
|
62
|
+
}, z.core.$strip>, z.ZodString]>>>;
|
|
63
63
|
harnessImplementation: z.ZodOptional<z.ZodLiteral<"langchain">>;
|
|
64
64
|
hooks: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
65
65
|
type: z.ZodEnum<{
|
package/dist/definition/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const McpStreamableHttpConfigSchema = McpBaseConfigSchema.extend({
|
|
|
23
23
|
export const McpConfigSchema = z.union([
|
|
24
24
|
McpStdioConfigSchema,
|
|
25
25
|
McpStreamableHttpConfigSchema,
|
|
26
|
+
z.string(),
|
|
26
27
|
]);
|
|
27
28
|
/** Custom tool configuration schema. */
|
|
28
29
|
const CustomToolSchema = z.object({
|
|
@@ -8,7 +8,7 @@ export declare const zAgentRunnerParams: z.ZodObject<{
|
|
|
8
8
|
suggestedPrompts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
9
9
|
systemPrompt: z.ZodNullable<z.ZodString>;
|
|
10
10
|
model: z.ZodString;
|
|
11
|
-
tools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">]>, z.ZodObject<{
|
|
11
|
+
tools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">]>, z.ZodObject<{
|
|
12
12
|
type: z.ZodLiteral<"custom">;
|
|
13
13
|
modulePath: z.ZodString;
|
|
14
14
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -22,6 +22,11 @@ export declare const zAgentRunnerParams: z.ZodObject<{
|
|
|
22
22
|
schema: z.ZodAny;
|
|
23
23
|
prettyName: z.ZodOptional<z.ZodString>;
|
|
24
24
|
icon: z.ZodOptional<z.ZodString>;
|
|
25
|
+
subagentConfigs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
26
|
+
agentName: z.ZodString;
|
|
27
|
+
description: z.ZodString;
|
|
28
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, z.core.$strip>>>;
|
|
25
30
|
}, z.core.$strip>]>>>;
|
|
26
31
|
mcps: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
27
32
|
name: z.ZodString;
|
|
@@ -33,7 +38,7 @@ export declare const zAgentRunnerParams: z.ZodObject<{
|
|
|
33
38
|
transport: z.ZodLiteral<"http">;
|
|
34
39
|
url: z.ZodString;
|
|
35
40
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
36
|
-
}, z.core.$strip
|
|
41
|
+
}, z.core.$strip>, z.ZodString]>>>;
|
|
37
42
|
hooks: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
38
43
|
type: z.ZodEnum<{
|
|
39
44
|
context_size: "context_size";
|
|
@@ -109,6 +114,10 @@ export type ExtendedSessionUpdate = (SessionNotification["update"] & {
|
|
|
109
114
|
contextInputTokens?: number;
|
|
110
115
|
[key: string]: unknown;
|
|
111
116
|
};
|
|
117
|
+
} | {
|
|
118
|
+
sessionUpdate: "tool_overhead_info";
|
|
119
|
+
toolOverheadTokens: number;
|
|
120
|
+
mcpOverheadTokens: number;
|
|
112
121
|
} | AgentMessageChunkWithTokens | HookNotificationUpdate;
|
|
113
122
|
/** Describes an object that can run an agent definition */
|
|
114
123
|
export interface AgentRunner {
|
|
@@ -10,7 +10,6 @@ type MakeLazy<T> = T extends LangchainTool ? () => T : never;
|
|
|
10
10
|
export declare const TOOL_REGISTRY: Record<BuiltInToolType, LangchainTool | LazyLangchainTool | LazyLangchainTools>;
|
|
11
11
|
export declare class LangchainAgent implements AgentRunner {
|
|
12
12
|
definition: CreateAgentRunnerParams;
|
|
13
|
-
private toolSpans;
|
|
14
13
|
constructor(params: CreateAgentRunnerParams);
|
|
15
14
|
invoke(req: InvokeRequest): AsyncGenerator<ExtendedSessionUpdate, PromptResponse, undefined>;
|
|
16
15
|
}
|