@townco/agent 0.1.77 → 0.1.79
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 +109 -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);
|
|
@@ -826,6 +830,11 @@ export class AgentAcpAdapter {
|
|
|
826
830
|
toolCallBlock.status =
|
|
827
831
|
updateMsg.status;
|
|
828
832
|
}
|
|
833
|
+
// Update rawInput if provided (for preliminary -> full tool call flow)
|
|
834
|
+
if (updateMsg.rawInput &&
|
|
835
|
+
Object.keys(updateMsg.rawInput).length > 0) {
|
|
836
|
+
toolCallBlock.rawInput = updateMsg.rawInput;
|
|
837
|
+
}
|
|
829
838
|
if (updateMsg.rawOutput) {
|
|
830
839
|
toolCallBlock.rawOutput = updateMsg.rawOutput;
|
|
831
840
|
}
|
|
@@ -903,11 +912,42 @@ export class AgentAcpAdapter {
|
|
|
903
912
|
const hooks = this.agent.definition.hooks ?? [];
|
|
904
913
|
if (hooks.some((h) => h.type === "tool_response")) {
|
|
905
914
|
const latestContext = session.context[session.context.length - 1];
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
+
}
|
|
909
937
|
const outputTokens = countToolResultTokens(rawOutput);
|
|
910
|
-
|
|
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);
|
|
911
951
|
const hookResult = await hookExecutor.executeToolResponseHooks({
|
|
912
952
|
messages: session.messages,
|
|
913
953
|
context: session.context,
|
|
@@ -919,6 +959,8 @@ export class AgentAcpAdapter {
|
|
|
919
959
|
rawOutput,
|
|
920
960
|
outputTokens,
|
|
921
961
|
});
|
|
962
|
+
// Note: Notifications are now sent in real-time via the callback
|
|
963
|
+
// The hookResult.notifications array is kept for backwards compatibility
|
|
922
964
|
// Apply modifications if hook returned them
|
|
923
965
|
if (hookResult.modifiedOutput) {
|
|
924
966
|
rawOutput = hookResult.modifiedOutput;
|
|
@@ -1019,7 +1061,8 @@ export class AgentAcpAdapter {
|
|
|
1019
1061
|
// Calculate context size - tool result is now in the message, but hasn't been sent to LLM yet
|
|
1020
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
|
|
1021
1063
|
this.currentToolOverheadTokens, // Include tool overhead
|
|
1022
|
-
this.currentMcpOverheadTokens
|
|
1064
|
+
this.currentMcpOverheadTokens, // Include MCP overhead
|
|
1065
|
+
getModelContextWindow(this.agent.definition.model));
|
|
1023
1066
|
// Create snapshot with a pointer to the partial message (not a full copy!)
|
|
1024
1067
|
const midTurnSnapshot = {
|
|
1025
1068
|
timestamp: new Date().toISOString(),
|
|
@@ -1172,12 +1215,26 @@ export class AgentAcpAdapter {
|
|
|
1172
1215
|
}
|
|
1173
1216
|
}
|
|
1174
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)
|
|
1175
1220
|
const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, turnTokenUsage.inputTokens, // Final LLM-reported tokens from this turn
|
|
1176
1221
|
this.currentToolOverheadTokens, // Include tool overhead
|
|
1177
|
-
this.currentMcpOverheadTokens
|
|
1222
|
+
this.currentMcpOverheadTokens, // Include MCP overhead
|
|
1223
|
+
getModelContextWindow(this.agent.definition.model), // Model context window for UI
|
|
1224
|
+
true);
|
|
1178
1225
|
const contextSnapshot = createContextSnapshot(session.messages.length, new Date().toISOString(), previousContext, context_size);
|
|
1179
1226
|
session.context.push(contextSnapshot);
|
|
1180
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
|
+
});
|
|
1181
1238
|
}
|
|
1182
1239
|
session.pendingPrompt = null;
|
|
1183
1240
|
return {
|
|
@@ -1222,7 +1279,18 @@ export class AgentAcpAdapter {
|
|
|
1222
1279
|
contextEntries: session.context.length,
|
|
1223
1280
|
totalMessages: session.messages.length,
|
|
1224
1281
|
});
|
|
1225
|
-
|
|
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);
|
|
1226
1294
|
// Create read-only session view for hooks
|
|
1227
1295
|
const readonlySession = {
|
|
1228
1296
|
messages: session.messages,
|
|
@@ -1233,23 +1301,45 @@ export class AgentAcpAdapter {
|
|
|
1233
1301
|
const latestContext = session.context.length > 0
|
|
1234
1302
|
? session.context[session.context.length - 1]
|
|
1235
1303
|
: undefined;
|
|
1236
|
-
//
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
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);
|
|
1240
1307
|
logger.debug("Using tokens for hook execution", {
|
|
1241
1308
|
llmReported: latestContext?.context_size.llmReportedInputTokens,
|
|
1242
1309
|
estimated: latestContext?.context_size.totalEstimated,
|
|
1243
1310
|
used: actualInputTokens,
|
|
1244
1311
|
});
|
|
1245
|
-
|
|
1246
|
-
//
|
|
1247
|
-
|
|
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
|
+
};
|
|
1248
1322
|
this.connection.sessionUpdate({
|
|
1249
1323
|
sessionId,
|
|
1250
|
-
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,
|
|
1251
1338
|
});
|
|
1252
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
|
|
1253
1343
|
// Return new context entries (will be appended by caller)
|
|
1254
1344
|
return hookResult.newContextEntries;
|
|
1255
1345
|
}
|