@townco/agent 0.1.78 → 0.1.80
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 +104 -19
- package/dist/acp-server/http.js +341 -155
- package/dist/acp-server/session-storage.d.ts +1 -0
- package/dist/acp-server/session-storage.js +1 -0
- package/dist/runner/hooks/constants.d.ts +4 -0
- package/dist/runner/hooks/constants.js +6 -0
- package/dist/runner/hooks/executor.d.ts +11 -1
- package/dist/runner/hooks/executor.js +84 -27
- package/dist/runner/hooks/types.d.ts +3 -0
- package/dist/runner/langchain/index.d.ts +1 -0
- package/dist/runner/langchain/index.js +65 -34
- package/dist/runner/langchain/otel-callbacks.js +9 -1
- package/dist/runner/langchain/tools/todo.d.ts +23 -0
- package/dist/runner/langchain/tools/todo.js +25 -16
- package/dist/telemetry/index.d.ts +18 -0
- package/dist/telemetry/index.js +50 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/context-size-calculator.d.ts +4 -1
- package/dist/utils/context-size-calculator.js +10 -2
- package/package.json +6 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as acp from "@agentclientprotocol/sdk";
|
|
2
2
|
import { context, trace } from "@opentelemetry/api";
|
|
3
3
|
import { createLogger } from "../logger.js";
|
|
4
|
-
import { HookExecutor, loadHookCallback } from "../runner/hooks";
|
|
4
|
+
import { getModelContextWindow, HookExecutor, loadHookCallback, } from "../runner/hooks";
|
|
5
5
|
import { telemetry } from "../telemetry/index.js";
|
|
6
6
|
import { calculateContextSize, } from "../utils/context-size-calculator.js";
|
|
7
7
|
import { countToolResultTokens } from "../utils/token-counter.js";
|
|
@@ -278,16 +278,18 @@ export class AgentAcpAdapter {
|
|
|
278
278
|
return response;
|
|
279
279
|
}
|
|
280
280
|
async newSession(params) {
|
|
281
|
+
// Generate a unique session ID for this session
|
|
281
282
|
const sessionId = Math.random().toString(36).substring(2);
|
|
282
283
|
// Extract configOverrides from _meta if provided (Town Hall comparison feature)
|
|
283
284
|
const configOverrides = params._meta?.configOverrides;
|
|
284
|
-
|
|
285
|
+
const sessionData = {
|
|
285
286
|
pendingPrompt: null,
|
|
286
287
|
messages: [],
|
|
287
288
|
context: [],
|
|
288
289
|
requestParams: params,
|
|
289
290
|
configOverrides,
|
|
290
|
-
}
|
|
291
|
+
};
|
|
292
|
+
this.sessions.set(sessionId, sessionData);
|
|
291
293
|
// Note: Initial message is sent by the HTTP transport when SSE connection is established
|
|
292
294
|
// This ensures the message is delivered after the client is ready to receive it
|
|
293
295
|
return {
|
|
@@ -599,7 +601,9 @@ export class AgentAcpAdapter {
|
|
|
599
601
|
// Calculate context size - no LLM call yet, so only estimated values
|
|
600
602
|
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined, // No LLM-reported tokens yet
|
|
601
603
|
this.currentToolOverheadTokens, // Include tool overhead
|
|
602
|
-
this.currentMcpOverheadTokens
|
|
604
|
+
this.currentMcpOverheadTokens, // Include MCP overhead
|
|
605
|
+
getModelContextWindow(this.agent.definition.model), // Model context window for UI
|
|
606
|
+
true);
|
|
603
607
|
const contextSnapshot = createContextSnapshot(session.messages.length - 1, // Exclude the newly added user message (it will be passed separately via prompt)
|
|
604
608
|
new Date().toISOString(), previousContext, context_size);
|
|
605
609
|
session.context.push(contextSnapshot);
|
|
@@ -908,11 +912,42 @@ export class AgentAcpAdapter {
|
|
|
908
912
|
const hooks = this.agent.definition.hooks ?? [];
|
|
909
913
|
if (hooks.some((h) => h.type === "tool_response")) {
|
|
910
914
|
const latestContext = session.context[session.context.length - 1];
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
915
|
+
// Use max of estimated and LLM-reported tokens (same logic as UI)
|
|
916
|
+
const currentContextTokens = Math.max(latestContext?.context_size.totalEstimated ?? 0, latestContext?.context_size.llmReportedInputTokens ?? 0);
|
|
917
|
+
// Send the context size to the UI so it shows the same value we're using for hook evaluation
|
|
918
|
+
if (latestContext?.context_size) {
|
|
919
|
+
const contextSizeForUI = {
|
|
920
|
+
...latestContext.context_size,
|
|
921
|
+
totalEstimated: currentContextTokens,
|
|
922
|
+
};
|
|
923
|
+
this.connection.sessionUpdate({
|
|
924
|
+
sessionId: params.sessionId,
|
|
925
|
+
update: {
|
|
926
|
+
sessionUpdate: "agent_message_chunk",
|
|
927
|
+
content: {
|
|
928
|
+
type: "text",
|
|
929
|
+
text: "",
|
|
930
|
+
},
|
|
931
|
+
_meta: {
|
|
932
|
+
context_size: contextSizeForUI,
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
});
|
|
936
|
+
}
|
|
914
937
|
const outputTokens = countToolResultTokens(rawOutput);
|
|
915
|
-
|
|
938
|
+
// Create notification callback to stream hook events in real-time
|
|
939
|
+
const sendHookNotification = (notification) => {
|
|
940
|
+
this.connection.sessionUpdate({
|
|
941
|
+
sessionId: params.sessionId,
|
|
942
|
+
update: {
|
|
943
|
+
sessionUpdate: "hook_notification",
|
|
944
|
+
id: `hook_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
945
|
+
notification,
|
|
946
|
+
messageId,
|
|
947
|
+
},
|
|
948
|
+
});
|
|
949
|
+
};
|
|
950
|
+
const hookExecutor = new HookExecutor(hooks, this.agent.definition.model, (callbackRef) => loadHookCallback(callbackRef, this.agentDir), sendHookNotification);
|
|
916
951
|
const hookResult = await hookExecutor.executeToolResponseHooks({
|
|
917
952
|
messages: session.messages,
|
|
918
953
|
context: session.context,
|
|
@@ -924,6 +959,8 @@ export class AgentAcpAdapter {
|
|
|
924
959
|
rawOutput,
|
|
925
960
|
outputTokens,
|
|
926
961
|
});
|
|
962
|
+
// Note: Notifications are now sent in real-time via the callback
|
|
963
|
+
// The hookResult.notifications array is kept for backwards compatibility
|
|
927
964
|
// Apply modifications if hook returned them
|
|
928
965
|
if (hookResult.modifiedOutput) {
|
|
929
966
|
rawOutput = hookResult.modifiedOutput;
|
|
@@ -1024,7 +1061,8 @@ export class AgentAcpAdapter {
|
|
|
1024
1061
|
// Calculate context size - tool result is now in the message, but hasn't been sent to LLM yet
|
|
1025
1062
|
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
|
|
1026
1063
|
this.currentToolOverheadTokens, // Include tool overhead
|
|
1027
|
-
this.currentMcpOverheadTokens
|
|
1064
|
+
this.currentMcpOverheadTokens, // Include MCP overhead
|
|
1065
|
+
getModelContextWindow(this.agent.definition.model));
|
|
1028
1066
|
// Create snapshot with a pointer to the partial message (not a full copy!)
|
|
1029
1067
|
const midTurnSnapshot = {
|
|
1030
1068
|
timestamp: new Date().toISOString(),
|
|
@@ -1177,12 +1215,26 @@ export class AgentAcpAdapter {
|
|
|
1177
1215
|
}
|
|
1178
1216
|
}
|
|
1179
1217
|
// Calculate context size with LLM-reported tokens from this turn
|
|
1218
|
+
// Exclude tool results - they're only sent during the turn they were received,
|
|
1219
|
+
// not in subsequent turns (only messages are sent)
|
|
1180
1220
|
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, turnTokenUsage.inputTokens, // Final LLM-reported tokens from this turn
|
|
1181
1221
|
this.currentToolOverheadTokens, // Include tool overhead
|
|
1182
|
-
this.currentMcpOverheadTokens
|
|
1222
|
+
this.currentMcpOverheadTokens, // Include MCP overhead
|
|
1223
|
+
getModelContextWindow(this.agent.definition.model), // Model context window for UI
|
|
1224
|
+
true);
|
|
1183
1225
|
const contextSnapshot = createContextSnapshot(session.messages.length, new Date().toISOString(), previousContext, context_size);
|
|
1184
1226
|
session.context.push(contextSnapshot);
|
|
1185
1227
|
await this.saveSessionToDisk(params.sessionId, session);
|
|
1228
|
+
// Send final context_size to UI (with tool results excluded)
|
|
1229
|
+
// This ensures the UI shows the correct context size at turn-end
|
|
1230
|
+
this.connection.sessionUpdate({
|
|
1231
|
+
sessionId: params.sessionId,
|
|
1232
|
+
update: {
|
|
1233
|
+
sessionUpdate: "agent_message_chunk",
|
|
1234
|
+
content: { type: "text", text: "" },
|
|
1235
|
+
_meta: { context_size },
|
|
1236
|
+
},
|
|
1237
|
+
});
|
|
1186
1238
|
}
|
|
1187
1239
|
session.pendingPrompt = null;
|
|
1188
1240
|
return {
|
|
@@ -1227,7 +1279,18 @@ export class AgentAcpAdapter {
|
|
|
1227
1279
|
contextEntries: session.context.length,
|
|
1228
1280
|
totalMessages: session.messages.length,
|
|
1229
1281
|
});
|
|
1230
|
-
|
|
1282
|
+
// Create notification callback to stream hook events in real-time
|
|
1283
|
+
const sendHookNotification = (notification) => {
|
|
1284
|
+
this.connection.sessionUpdate({
|
|
1285
|
+
sessionId,
|
|
1286
|
+
update: {
|
|
1287
|
+
sessionUpdate: "hook_notification",
|
|
1288
|
+
id: `hook_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
1289
|
+
notification,
|
|
1290
|
+
},
|
|
1291
|
+
});
|
|
1292
|
+
};
|
|
1293
|
+
const hookExecutor = new HookExecutor(hooks, this.agent.definition.model, (callbackRef) => loadHookCallback(callbackRef, this.agentDir), sendHookNotification);
|
|
1231
1294
|
// Create read-only session view for hooks
|
|
1232
1295
|
const readonlySession = {
|
|
1233
1296
|
messages: session.messages,
|
|
@@ -1238,23 +1301,45 @@ export class AgentAcpAdapter {
|
|
|
1238
1301
|
const latestContext = session.context.length > 0
|
|
1239
1302
|
? session.context[session.context.length - 1]
|
|
1240
1303
|
: undefined;
|
|
1241
|
-
//
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
0;
|
|
1304
|
+
// Use max of estimated and LLM-reported tokens (same logic as UI)
|
|
1305
|
+
// This is conservative - we want to trigger compaction earlier rather than later
|
|
1306
|
+
const actualInputTokens = Math.max(latestContext?.context_size.totalEstimated ?? 0, latestContext?.context_size.llmReportedInputTokens ?? 0);
|
|
1245
1307
|
logger.debug("Using tokens for hook execution", {
|
|
1246
1308
|
llmReported: latestContext?.context_size.llmReportedInputTokens,
|
|
1247
1309
|
estimated: latestContext?.context_size.totalEstimated,
|
|
1248
1310
|
used: actualInputTokens,
|
|
1249
1311
|
});
|
|
1250
|
-
|
|
1251
|
-
//
|
|
1252
|
-
|
|
1312
|
+
// Send the context size to the UI so it shows the same value we're using for hook evaluation
|
|
1313
|
+
// This ensures the UI percentage matches what the hook sees
|
|
1314
|
+
if (latestContext?.context_size) {
|
|
1315
|
+
// Create an updated context_size with the actualInputTokens we computed
|
|
1316
|
+
// so UI can calculate the same percentage
|
|
1317
|
+
const contextSizeForUI = {
|
|
1318
|
+
...latestContext.context_size,
|
|
1319
|
+
// Override with the max value we computed (what hooks actually use)
|
|
1320
|
+
totalEstimated: actualInputTokens,
|
|
1321
|
+
};
|
|
1253
1322
|
this.connection.sessionUpdate({
|
|
1254
1323
|
sessionId,
|
|
1255
|
-
update:
|
|
1324
|
+
update: {
|
|
1325
|
+
sessionUpdate: "agent_message_chunk",
|
|
1326
|
+
content: {
|
|
1327
|
+
type: "text",
|
|
1328
|
+
text: "",
|
|
1329
|
+
},
|
|
1330
|
+
_meta: {
|
|
1331
|
+
context_size: contextSizeForUI,
|
|
1332
|
+
},
|
|
1333
|
+
},
|
|
1334
|
+
});
|
|
1335
|
+
logger.debug("Sent context_size update to UI before hook execution", {
|
|
1336
|
+
actualInputTokens,
|
|
1337
|
+
modelContextWindow: contextSizeForUI.modelContextWindow,
|
|
1256
1338
|
});
|
|
1257
1339
|
}
|
|
1340
|
+
const hookResult = await hookExecutor.executeHooks(readonlySession, actualInputTokens);
|
|
1341
|
+
// Note: Notifications are now sent in real-time via the callback
|
|
1342
|
+
// The hookResult.notifications array is kept for backwards compatibility
|
|
1258
1343
|
// Return new context entries (will be appended by caller)
|
|
1259
1344
|
return hookResult.newContextEntries;
|
|
1260
1345
|
}
|