@townco/agent 0.1.44 → 0.1.45
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 +1 -0
- package/dist/acp-server/adapter.js +130 -9
- package/dist/acp-server/cli.d.ts +3 -1
- package/dist/acp-server/session-storage.d.ts +21 -1
- package/dist/acp-server/session-storage.js +19 -1
- package/dist/bin.js +0 -0
- package/dist/definition/index.d.ts +19 -0
- package/dist/definition/index.js +11 -0
- package/dist/definition/mcp.js +0 -1
- package/dist/definition/tools/todo.d.ts +49 -0
- package/dist/definition/tools/todo.js +80 -0
- package/dist/definition/tools/web_search.d.ts +4 -0
- package/dist/definition/tools/web_search.js +26 -0
- package/dist/dev-agent/index.d.ts +2 -0
- package/dist/dev-agent/index.js +18 -0
- package/dist/example.d.ts +2 -0
- package/dist/example.js +19 -0
- package/dist/index.js +13 -2
- package/dist/runner/agent-runner.d.ts +28 -2
- package/dist/runner/agent-runner.js +2 -1
- package/dist/runner/hooks/constants.d.ts +9 -0
- package/dist/runner/hooks/constants.js +19 -0
- package/dist/runner/hooks/executor.d.ts +23 -0
- package/dist/runner/hooks/executor.js +163 -0
- package/dist/runner/hooks/index.d.ts +5 -0
- package/dist/runner/hooks/index.js +5 -0
- package/dist/runner/hooks/loader.d.ts +5 -0
- package/dist/runner/hooks/loader.js +49 -0
- package/dist/runner/hooks/predefined/compaction-tool.d.ts +6 -0
- package/dist/runner/hooks/predefined/compaction-tool.js +42 -0
- package/dist/runner/hooks/registry.d.ts +14 -0
- package/dist/runner/hooks/registry.js +20 -0
- package/dist/runner/hooks/types.d.ts +144 -0
- package/dist/runner/hooks/types.js +33 -0
- package/dist/runner/index.d.ts +3 -1
- package/dist/runner/langchain/index.js +16 -10
- package/dist/runner/langchain/model-factory.js +3 -1
- package/dist/scaffold/claude-scaffold.js +8 -6
- package/dist/storage/index.js +3 -1
- package/dist/templates/index.d.ts +7 -0
- package/dist/templates/index.js +7 -2
- package/dist/test-script.js +3 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/index.ts +14 -2
- package/package.json +5 -5
- package/templates/index.ts +14 -2
- package/dist/acp-server/test-acp-summarize.d.ts +0 -7
- package/dist/acp-server/test-acp-summarize.js +0 -127
- package/dist/acp-server/test-summarizer.d.ts +0 -7
- package/dist/acp-server/test-summarizer.js +0 -170
- package/dist/acp-server/tool-summarizer.d.ts +0 -125
- package/dist/acp-server/tool-summarizer.js +0 -182
|
@@ -30,6 +30,15 @@ export declare const zAgentRunnerParams: z.ZodObject<{
|
|
|
30
30
|
url: z.ZodString;
|
|
31
31
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
32
32
|
}, z.core.$strip>]>>>;
|
|
33
|
+
hooks: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
|
+
type: z.ZodEnum<{
|
|
35
|
+
context_size: "context_size";
|
|
36
|
+
}>;
|
|
37
|
+
setting: z.ZodOptional<z.ZodObject<{
|
|
38
|
+
threshold: z.ZodNumber;
|
|
39
|
+
}, z.core.$strip>>;
|
|
40
|
+
callback: z.ZodString;
|
|
41
|
+
}, z.core.$strip>>>;
|
|
33
42
|
}, z.core.$strip>;
|
|
34
43
|
export type CreateAgentRunnerParams = z.infer<typeof zAgentRunnerParams>;
|
|
35
44
|
export interface SessionMessage {
|
|
@@ -40,7 +49,7 @@ export interface SessionMessage {
|
|
|
40
49
|
export type InvokeRequest = Omit<PromptRequest, "_meta"> & {
|
|
41
50
|
messageId: string;
|
|
42
51
|
sessionMeta?: Record<string, unknown>;
|
|
43
|
-
|
|
52
|
+
contextMessages?: SessionMessage[];
|
|
44
53
|
};
|
|
45
54
|
export interface TokenUsage {
|
|
46
55
|
inputTokens?: number;
|
|
@@ -58,6 +67,23 @@ export interface AgentMessageChunkWithTokens {
|
|
|
58
67
|
[key: string]: unknown;
|
|
59
68
|
};
|
|
60
69
|
}
|
|
70
|
+
export type HookNotificationUpdate = {
|
|
71
|
+
sessionUpdate: "hook_triggered";
|
|
72
|
+
hookType: string;
|
|
73
|
+
threshold: number;
|
|
74
|
+
currentPercentage: number;
|
|
75
|
+
callback: string;
|
|
76
|
+
} | {
|
|
77
|
+
sessionUpdate: "hook_completed";
|
|
78
|
+
hookType: string;
|
|
79
|
+
callback: string;
|
|
80
|
+
metadata?: Record<string, unknown>;
|
|
81
|
+
} | {
|
|
82
|
+
sessionUpdate: "hook_error";
|
|
83
|
+
hookType: string;
|
|
84
|
+
callback: string;
|
|
85
|
+
error: string;
|
|
86
|
+
};
|
|
61
87
|
export type ExtendedSessionUpdate = (SessionNotification["update"] & {
|
|
62
88
|
prettyName?: string;
|
|
63
89
|
icon?: string;
|
|
@@ -72,7 +98,7 @@ export type ExtendedSessionUpdate = (SessionNotification["update"] & {
|
|
|
72
98
|
_meta?: {
|
|
73
99
|
messageId?: string;
|
|
74
100
|
};
|
|
75
|
-
} | AgentMessageChunkWithTokens;
|
|
101
|
+
} | AgentMessageChunkWithTokens | HookNotificationUpdate;
|
|
76
102
|
/** Describes an object that can run an agent definition */
|
|
77
103
|
export interface AgentRunner {
|
|
78
104
|
definition: CreateAgentRunnerParams;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { McpConfigSchema } from "../definition";
|
|
2
|
+
import { HookConfigSchema, McpConfigSchema } from "../definition";
|
|
3
3
|
import { zToolType } from "./tools";
|
|
4
4
|
export const zAgentRunnerParams = z.object({
|
|
5
5
|
systemPrompt: z.string().nullable(),
|
|
6
6
|
model: z.string(),
|
|
7
7
|
tools: z.array(zToolType).optional(),
|
|
8
8
|
mcps: z.array(McpConfigSchema).optional(),
|
|
9
|
+
hooks: z.array(HookConfigSchema).optional(),
|
|
9
10
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model context window sizes in tokens
|
|
3
|
+
* Add more models as needed
|
|
4
|
+
*/
|
|
5
|
+
export const MODEL_CONTEXT_WINDOWS = {
|
|
6
|
+
// Claude 3.5 Sonnet
|
|
7
|
+
"claude-sonnet-4-5-20250929": 200000,
|
|
8
|
+
"claude-sonnet-4-20250514": 200000,
|
|
9
|
+
"claude-3-5-sonnet-20241022": 200000,
|
|
10
|
+
"claude-3-5-sonnet-20240620": 200000,
|
|
11
|
+
// Claude 3 Opus
|
|
12
|
+
"claude-3-opus-20240229": 200000,
|
|
13
|
+
// Claude 3 Haiku
|
|
14
|
+
"claude-3-haiku-20240307": 200000,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Default context size for unknown models
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_CONTEXT_SIZE = 200000;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ContextEntry } from "../../acp-server/session-storage";
|
|
2
|
+
import type { HookCallback, HookConfig, HookNotification, ReadonlySession } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Hook executor manages hook lifecycle
|
|
5
|
+
*/
|
|
6
|
+
export declare class HookExecutor {
|
|
7
|
+
private hooks;
|
|
8
|
+
private model;
|
|
9
|
+
private loadCallback;
|
|
10
|
+
constructor(hooks: HookConfig[], model: string, loadCallback: (callbackRef: string) => Promise<HookCallback>);
|
|
11
|
+
/**
|
|
12
|
+
* Execute hooks before agent invocation
|
|
13
|
+
* Returns new context entries to append and any notifications to send
|
|
14
|
+
*/
|
|
15
|
+
executeHooks(session: ReadonlySession): Promise<{
|
|
16
|
+
newContextEntries: ContextEntry[];
|
|
17
|
+
notifications: HookNotification[];
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Execute a context_size hook
|
|
21
|
+
*/
|
|
22
|
+
private executeContextSizeHook;
|
|
23
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createLogger } from "@townco/core";
|
|
2
|
+
import { DEFAULT_CONTEXT_SIZE, MODEL_CONTEXT_WINDOWS } from "./constants";
|
|
3
|
+
const logger = createLogger("hook-executor");
|
|
4
|
+
/**
|
|
5
|
+
* Get max context size for a model
|
|
6
|
+
*/
|
|
7
|
+
function getModelMaxTokens(model) {
|
|
8
|
+
return MODEL_CONTEXT_WINDOWS[model] ?? DEFAULT_CONTEXT_SIZE;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Estimate token count for session messages
|
|
12
|
+
* This is a rough estimate: ~4 characters per token
|
|
13
|
+
*/
|
|
14
|
+
function estimateTokens(messages) {
|
|
15
|
+
let totalChars = 0;
|
|
16
|
+
for (const message of messages) {
|
|
17
|
+
// Count characters in content blocks
|
|
18
|
+
for (const block of message.content) {
|
|
19
|
+
if (block.type === "text") {
|
|
20
|
+
totalChars += block.text.length;
|
|
21
|
+
}
|
|
22
|
+
else if (block.type === "tool_call") {
|
|
23
|
+
// Estimate tool call size (title + inputs/outputs)
|
|
24
|
+
totalChars += block.title.length;
|
|
25
|
+
if (block.rawInput) {
|
|
26
|
+
totalChars += JSON.stringify(block.rawInput).length;
|
|
27
|
+
}
|
|
28
|
+
if (block.rawOutput) {
|
|
29
|
+
totalChars += JSON.stringify(block.rawOutput).length;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Rough estimate: 4 characters per token
|
|
35
|
+
return Math.ceil(totalChars / 4);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve context entries to session messages
|
|
39
|
+
*/
|
|
40
|
+
function resolveContextToMessages(context, allMessages) {
|
|
41
|
+
if (context.length === 0) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const latestContext = context[context.length - 1];
|
|
45
|
+
if (!latestContext) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const resolved = [];
|
|
49
|
+
for (const entry of latestContext.messages) {
|
|
50
|
+
if (entry.type === "pointer") {
|
|
51
|
+
const message = allMessages[entry.index];
|
|
52
|
+
if (message) {
|
|
53
|
+
resolved.push(message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (entry.type === "full") {
|
|
57
|
+
resolved.push(entry.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Hook executor manages hook lifecycle
|
|
64
|
+
*/
|
|
65
|
+
export class HookExecutor {
|
|
66
|
+
hooks;
|
|
67
|
+
model;
|
|
68
|
+
loadCallback;
|
|
69
|
+
constructor(hooks, model, loadCallback) {
|
|
70
|
+
this.hooks = hooks;
|
|
71
|
+
this.model = model;
|
|
72
|
+
this.loadCallback = loadCallback;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Execute hooks before agent invocation
|
|
76
|
+
* Returns new context entries to append and any notifications to send
|
|
77
|
+
*/
|
|
78
|
+
async executeHooks(session) {
|
|
79
|
+
logger.info(`Executing hooks - found ${this.hooks.length} hook(s)`, {
|
|
80
|
+
hooks: this.hooks.map((h) => h.type),
|
|
81
|
+
contextEntries: session.context.length,
|
|
82
|
+
totalMessages: session.messages.length,
|
|
83
|
+
});
|
|
84
|
+
const newContextEntries = [];
|
|
85
|
+
const notifications = [];
|
|
86
|
+
for (const hook of this.hooks) {
|
|
87
|
+
if (hook.type === "context_size") {
|
|
88
|
+
const result = await this.executeContextSizeHook(hook, session);
|
|
89
|
+
if (result) {
|
|
90
|
+
notifications.push(...result.notifications);
|
|
91
|
+
if (result.newContextEntry) {
|
|
92
|
+
newContextEntries.push(result.newContextEntry);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { newContextEntries, notifications };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Execute a context_size hook
|
|
101
|
+
*/
|
|
102
|
+
async executeContextSizeHook(hook, session) {
|
|
103
|
+
// Resolve context to messages for token estimation
|
|
104
|
+
const resolvedMessages = resolveContextToMessages(session.context, session.messages);
|
|
105
|
+
const maxTokens = getModelMaxTokens(this.model);
|
|
106
|
+
const currentTokens = estimateTokens(resolvedMessages);
|
|
107
|
+
const percentage = (currentTokens / maxTokens) * 100;
|
|
108
|
+
// Default threshold is 95%
|
|
109
|
+
const threshold = hook.setting?.threshold ?? 95;
|
|
110
|
+
// Check if threshold exceeded
|
|
111
|
+
if (percentage < threshold) {
|
|
112
|
+
logger.info(`Context hook not triggered: ${currentTokens} tokens (${percentage.toFixed(1)}%) < threshold ${threshold}%`);
|
|
113
|
+
return null; // No action needed
|
|
114
|
+
}
|
|
115
|
+
logger.info(`Context hook triggered: ${currentTokens} tokens (${percentage.toFixed(1)}%) exceeds threshold ${threshold}%`);
|
|
116
|
+
const notifications = [];
|
|
117
|
+
// Notify that hook is triggered
|
|
118
|
+
notifications.push({
|
|
119
|
+
type: "hook_triggered",
|
|
120
|
+
hookType: "context_size",
|
|
121
|
+
threshold,
|
|
122
|
+
currentPercentage: percentage,
|
|
123
|
+
callback: hook.callback,
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
// Load and execute callback
|
|
127
|
+
const callback = await this.loadCallback(hook.callback);
|
|
128
|
+
const hookContext = {
|
|
129
|
+
session,
|
|
130
|
+
currentTokens,
|
|
131
|
+
maxTokens,
|
|
132
|
+
percentage,
|
|
133
|
+
model: this.model,
|
|
134
|
+
};
|
|
135
|
+
const result = await callback(hookContext);
|
|
136
|
+
// Notify completion
|
|
137
|
+
notifications.push({
|
|
138
|
+
type: "hook_completed",
|
|
139
|
+
hookType: "context_size",
|
|
140
|
+
callback: hook.callback,
|
|
141
|
+
metadata: result.metadata,
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
newContextEntry: result.newContextEntry,
|
|
145
|
+
notifications,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// Notify error
|
|
150
|
+
notifications.push({
|
|
151
|
+
type: "hook_error",
|
|
152
|
+
hookType: "context_size",
|
|
153
|
+
callback: hook.callback,
|
|
154
|
+
error: error instanceof Error ? error.message : String(error),
|
|
155
|
+
});
|
|
156
|
+
// Return no context entry on error
|
|
157
|
+
return {
|
|
158
|
+
newContextEntry: null,
|
|
159
|
+
notifications,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { createLogger } from "@townco/core";
|
|
3
|
+
import { getPredefinedHook, isPredefinedHook } from "./registry";
|
|
4
|
+
const logger = createLogger("hook-loader");
|
|
5
|
+
/**
|
|
6
|
+
* Load a hook callback from either registry or custom file
|
|
7
|
+
*/
|
|
8
|
+
export async function loadHookCallback(callbackRef, agentDir) {
|
|
9
|
+
// Check if it's a predefined hook
|
|
10
|
+
if (isPredefinedHook(callbackRef)) {
|
|
11
|
+
const hook = getPredefinedHook(callbackRef);
|
|
12
|
+
if (!hook) {
|
|
13
|
+
throw new Error(`Predefined hook "${callbackRef}" not found`);
|
|
14
|
+
}
|
|
15
|
+
return hook;
|
|
16
|
+
}
|
|
17
|
+
// It's a file path - load custom hook
|
|
18
|
+
if (!agentDir) {
|
|
19
|
+
throw new Error(`Cannot load custom hook "${callbackRef}" without agent directory`);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
// Resolve path relative to agent directory
|
|
23
|
+
const hookPath = resolve(agentDir, callbackRef);
|
|
24
|
+
logger.debug(`Loading custom hook from ${hookPath}`);
|
|
25
|
+
// Dynamic import the module
|
|
26
|
+
const module = await import(hookPath);
|
|
27
|
+
// Type guard to check if module is an object
|
|
28
|
+
if (typeof module !== "object" || module === null) {
|
|
29
|
+
throw new Error(`Hook module at "${hookPath}" must export an object`);
|
|
30
|
+
}
|
|
31
|
+
// Look for default export or named export
|
|
32
|
+
const moduleObj = module;
|
|
33
|
+
const callback = moduleObj.default || moduleObj.hookCallback || moduleObj.callback;
|
|
34
|
+
if (!callback) {
|
|
35
|
+
throw new Error(`Hook module at "${hookPath}" must export a default function or "hookCallback"`);
|
|
36
|
+
}
|
|
37
|
+
if (typeof callback !== "function") {
|
|
38
|
+
throw new Error(`Hook at "${hookPath}" must export a function, got ${typeof callback}`);
|
|
39
|
+
}
|
|
40
|
+
return callback;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const errorData = typeof error === "object" && error !== null
|
|
44
|
+
? error
|
|
45
|
+
: undefined;
|
|
46
|
+
logger.error(`Failed to load custom hook "${callbackRef}"`, errorData);
|
|
47
|
+
throw new Error(`Failed to load custom hook "${callbackRef}": ${error instanceof Error ? error.message : String(error)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createLogger } from "@townco/core";
|
|
2
|
+
import { createContextEntry, createFullMessageEntry, } from "../types";
|
|
3
|
+
const logger = createLogger("compaction-tool");
|
|
4
|
+
/**
|
|
5
|
+
* Placeholder compaction tool for testing hooks
|
|
6
|
+
* Logs the context and returns null (no new context entry)
|
|
7
|
+
*/
|
|
8
|
+
export const compactionTool = async (ctx) => {
|
|
9
|
+
logger.info("Compaction tool triggered", {
|
|
10
|
+
currentTokens: ctx.currentTokens,
|
|
11
|
+
maxTokens: ctx.maxTokens,
|
|
12
|
+
percentage: ctx.percentage.toFixed(2) + "%",
|
|
13
|
+
contextEntries: ctx.session.context.length,
|
|
14
|
+
totalMessages: ctx.session.messages.length,
|
|
15
|
+
model: ctx.model,
|
|
16
|
+
});
|
|
17
|
+
// TODO: Implement actual compaction logic
|
|
18
|
+
// Example: Create a new context entry with a summary message
|
|
19
|
+
// const summaryEntry = createFullMessageEntry(
|
|
20
|
+
// "assistant",
|
|
21
|
+
// "Summary of previous conversation: ..."
|
|
22
|
+
// );
|
|
23
|
+
//
|
|
24
|
+
// const newContextEntry = createContextEntry([summaryEntry]);
|
|
25
|
+
//
|
|
26
|
+
// return {
|
|
27
|
+
// newContextEntry,
|
|
28
|
+
// metadata: {
|
|
29
|
+
// action: "compacted",
|
|
30
|
+
// messagesRemoved: ctx.session.messages.length - 1,
|
|
31
|
+
// tokensSaved: 1000,
|
|
32
|
+
// },
|
|
33
|
+
// };
|
|
34
|
+
// For now, just return null (no new context entry)
|
|
35
|
+
return {
|
|
36
|
+
newContextEntry: null,
|
|
37
|
+
metadata: {
|
|
38
|
+
action: "placeholder",
|
|
39
|
+
message: "Compaction tool called but no action taken (placeholder)",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HookCallback } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Registry of predefined hook callbacks
|
|
4
|
+
* Maps callback names to their implementations
|
|
5
|
+
*/
|
|
6
|
+
export declare const HOOK_REGISTRY: Record<string, HookCallback>;
|
|
7
|
+
/**
|
|
8
|
+
* Check if a callback name is a predefined hook
|
|
9
|
+
*/
|
|
10
|
+
export declare function isPredefinedHook(callback: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Get a predefined hook callback by name
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPredefinedHook(callback: string): HookCallback | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { compactionTool } from "./predefined/compaction-tool";
|
|
2
|
+
/**
|
|
3
|
+
* Registry of predefined hook callbacks
|
|
4
|
+
* Maps callback names to their implementations
|
|
5
|
+
*/
|
|
6
|
+
export const HOOK_REGISTRY = {
|
|
7
|
+
compaction_tool: compactionTool,
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Check if a callback name is a predefined hook
|
|
11
|
+
*/
|
|
12
|
+
export function isPredefinedHook(callback) {
|
|
13
|
+
return callback in HOOK_REGISTRY;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get a predefined hook callback by name
|
|
17
|
+
*/
|
|
18
|
+
export function getPredefinedHook(callback) {
|
|
19
|
+
return HOOK_REGISTRY[callback];
|
|
20
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { ContextEntry } from "../../acp-server/session-storage";
|
|
2
|
+
import type { SessionMessage } from "../agent-runner";
|
|
3
|
+
/**
|
|
4
|
+
* Hook types supported by the agent system
|
|
5
|
+
*/
|
|
6
|
+
export type HookType = "context_size";
|
|
7
|
+
/**
|
|
8
|
+
* Settings for context_size hook
|
|
9
|
+
*/
|
|
10
|
+
export interface ContextSizeSettings {
|
|
11
|
+
/**
|
|
12
|
+
* Threshold as percentage (0-100) of model's max context window
|
|
13
|
+
*/
|
|
14
|
+
threshold: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Hook configuration in agent definition
|
|
18
|
+
*/
|
|
19
|
+
export interface HookConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Type of hook to trigger
|
|
22
|
+
*/
|
|
23
|
+
type: HookType;
|
|
24
|
+
/**
|
|
25
|
+
* Optional hook-specific settings
|
|
26
|
+
*/
|
|
27
|
+
setting?: ContextSizeSettings | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Callback reference - either a predefined hook name or a file path
|
|
30
|
+
* Examples: "compaction_tool" or "./hooks/my_compaction_tool.ts"
|
|
31
|
+
*/
|
|
32
|
+
callback: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read-only view of an agent session for hooks
|
|
36
|
+
*/
|
|
37
|
+
export interface ReadonlySession {
|
|
38
|
+
/**
|
|
39
|
+
* All session messages
|
|
40
|
+
*/
|
|
41
|
+
readonly messages: ReadonlyArray<Readonly<SessionMessage>>;
|
|
42
|
+
/**
|
|
43
|
+
* Current context entries (time-based snapshots)
|
|
44
|
+
*/
|
|
45
|
+
readonly context: ReadonlyArray<Readonly<ContextEntry>>;
|
|
46
|
+
/**
|
|
47
|
+
* Original request parameters
|
|
48
|
+
*/
|
|
49
|
+
readonly requestParams: Readonly<Record<string, unknown>>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Context passed to hook callbacks
|
|
53
|
+
*/
|
|
54
|
+
export interface HookContext {
|
|
55
|
+
/**
|
|
56
|
+
* Read-only access to the full session
|
|
57
|
+
*/
|
|
58
|
+
session: ReadonlySession;
|
|
59
|
+
/**
|
|
60
|
+
* Current context size in tokens
|
|
61
|
+
*/
|
|
62
|
+
currentTokens: number;
|
|
63
|
+
/**
|
|
64
|
+
* Maximum context size for the model in tokens
|
|
65
|
+
*/
|
|
66
|
+
maxTokens: number;
|
|
67
|
+
/**
|
|
68
|
+
* Percentage of context used (0-100)
|
|
69
|
+
*/
|
|
70
|
+
percentage: number;
|
|
71
|
+
/**
|
|
72
|
+
* The model being used
|
|
73
|
+
*/
|
|
74
|
+
model: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Result returned by hook callbacks
|
|
78
|
+
*/
|
|
79
|
+
export interface HookResult {
|
|
80
|
+
/**
|
|
81
|
+
* New context entry to append to session.context
|
|
82
|
+
* If null, no context change is made
|
|
83
|
+
*/
|
|
84
|
+
newContextEntry: ContextEntry | null;
|
|
85
|
+
/**
|
|
86
|
+
* Optional metadata about what the hook did
|
|
87
|
+
*/
|
|
88
|
+
metadata?: {
|
|
89
|
+
action?: string;
|
|
90
|
+
messagesRemoved?: number;
|
|
91
|
+
tokensSaved?: number;
|
|
92
|
+
[key: string]: unknown;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Hook callback function signature
|
|
97
|
+
*/
|
|
98
|
+
export type HookCallback = (context: HookContext) => Promise<HookResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Helper function to create a new context entry
|
|
101
|
+
* Use this when hooks want to create a new context snapshot
|
|
102
|
+
*/
|
|
103
|
+
export declare function createContextEntry(messages: Array<{
|
|
104
|
+
type: "pointer";
|
|
105
|
+
index: number;
|
|
106
|
+
} | {
|
|
107
|
+
type: "full";
|
|
108
|
+
message: SessionMessage;
|
|
109
|
+
}>, timestamp?: string): ContextEntry;
|
|
110
|
+
/**
|
|
111
|
+
* Helper function to create a full message entry for context
|
|
112
|
+
* Use this when hooks need to inject new messages into context
|
|
113
|
+
*/
|
|
114
|
+
export declare function createFullMessageEntry(role: "user" | "assistant", text: string): {
|
|
115
|
+
type: "full";
|
|
116
|
+
message: SessionMessage;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Helper function to create a pointer entry for context
|
|
120
|
+
*/
|
|
121
|
+
export declare function createPointerEntry(index: number): {
|
|
122
|
+
type: "pointer";
|
|
123
|
+
index: number;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Hook notification events for GUI/TUI
|
|
127
|
+
*/
|
|
128
|
+
export type HookNotification = {
|
|
129
|
+
type: "hook_triggered";
|
|
130
|
+
hookType: HookType;
|
|
131
|
+
threshold: number;
|
|
132
|
+
currentPercentage: number;
|
|
133
|
+
callback: string;
|
|
134
|
+
} | {
|
|
135
|
+
type: "hook_completed";
|
|
136
|
+
hookType: HookType;
|
|
137
|
+
callback: string;
|
|
138
|
+
metadata?: HookResult["metadata"];
|
|
139
|
+
} | {
|
|
140
|
+
type: "hook_error";
|
|
141
|
+
hookType: HookType;
|
|
142
|
+
callback: string;
|
|
143
|
+
error: string;
|
|
144
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to create a new context entry
|
|
3
|
+
* Use this when hooks want to create a new context snapshot
|
|
4
|
+
*/
|
|
5
|
+
export function createContextEntry(messages, timestamp) {
|
|
6
|
+
return {
|
|
7
|
+
timestamp: timestamp || new Date().toISOString(),
|
|
8
|
+
messages,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Helper function to create a full message entry for context
|
|
13
|
+
* Use this when hooks need to inject new messages into context
|
|
14
|
+
*/
|
|
15
|
+
export function createFullMessageEntry(role, text) {
|
|
16
|
+
return {
|
|
17
|
+
type: "full",
|
|
18
|
+
message: {
|
|
19
|
+
role,
|
|
20
|
+
content: [{ type: "text", text }],
|
|
21
|
+
timestamp: new Date().toISOString(),
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Helper function to create a pointer entry for context
|
|
27
|
+
*/
|
|
28
|
+
export function createPointerEntry(index) {
|
|
29
|
+
return {
|
|
30
|
+
type: "pointer",
|
|
31
|
+
index,
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/runner/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AgentDefinition } from "../definition";
|
|
2
2
|
import { type AgentRunner } from "./agent-runner";
|
|
3
3
|
export type { AgentRunner };
|
|
4
|
-
export declare const makeRunnerFromDefinition: (
|
|
4
|
+
export declare const makeRunnerFromDefinition: (
|
|
5
|
+
definition: AgentDefinition,
|
|
6
|
+
) => AgentRunner;
|