agent-sh 0.8.0 → 0.10.0
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/README.md +27 -43
- package/dist/agent/agent-loop.d.ts +69 -6
- package/dist/agent/agent-loop.js +954 -153
- package/dist/agent/conversation-state.d.ts +74 -21
- package/dist/agent/conversation-state.js +361 -150
- package/dist/agent/history-file.d.ts +13 -4
- package/dist/agent/history-file.js +110 -36
- package/dist/agent/nuclear-form.d.ts +28 -3
- package/dist/agent/nuclear-form.js +88 -6
- package/dist/agent/skills.d.ts +2 -4
- package/dist/agent/skills.js +10 -4
- package/dist/agent/subagent.d.ts +23 -0
- package/dist/agent/subagent.js +53 -11
- package/dist/agent/system-prompt.d.ts +37 -5
- package/dist/agent/system-prompt.js +100 -67
- package/dist/{token-budget.d.ts → agent/token-budget.d.ts} +5 -4
- package/dist/{token-budget.js → agent/token-budget.js} +15 -20
- package/dist/agent/tool-protocol.d.ts +105 -0
- package/dist/agent/tool-protocol.js +551 -0
- package/dist/agent/tools/bash.js +3 -3
- package/dist/agent/tools/edit-file.js +9 -6
- package/dist/agent/tools/glob.js +4 -2
- package/dist/agent/tools/grep.js +27 -3
- package/dist/agent/tools/ls.js +5 -6
- package/dist/agent/types.d.ts +22 -2
- package/dist/context-manager.d.ts +17 -0
- package/dist/context-manager.js +37 -4
- package/dist/core.d.ts +7 -7
- package/dist/core.js +99 -196
- package/dist/event-bus.d.ts +85 -2
- package/dist/event-bus.js +20 -1
- package/dist/executor.d.ts +4 -3
- package/dist/executor.js +18 -15
- package/dist/extension-loader.d.ts +5 -0
- package/dist/extension-loader.js +143 -19
- package/dist/extensions/agent-backend.d.ts +14 -0
- package/dist/extensions/agent-backend.js +188 -0
- package/dist/extensions/command-suggest.d.ts +3 -3
- package/dist/extensions/command-suggest.js +4 -3
- package/dist/extensions/index.d.ts +19 -0
- package/dist/extensions/index.js +24 -0
- package/dist/extensions/slash-commands.d.ts +1 -1
- package/dist/extensions/slash-commands.js +30 -10
- package/dist/extensions/tui-renderer.js +117 -113
- package/dist/index.js +39 -26
- package/dist/settings.d.ts +40 -3
- package/dist/settings.js +57 -10
- package/dist/{input-handler.d.ts → shell/input-handler.d.ts} +3 -2
- package/dist/{input-handler.js → shell/input-handler.js} +111 -85
- package/dist/{output-parser.d.ts → shell/output-parser.d.ts} +1 -1
- package/dist/{output-parser.js → shell/output-parser.js} +1 -1
- package/dist/{shell.d.ts → shell/shell.d.ts} +8 -2
- package/dist/{shell.js → shell/shell.js} +39 -8
- package/dist/types.d.ts +61 -10
- package/dist/utils/ansi.d.ts +5 -0
- package/dist/utils/ansi.js +1 -1
- package/dist/utils/compositor.d.ts +67 -0
- package/dist/utils/compositor.js +116 -0
- package/dist/utils/diff-renderer.d.ts +9 -0
- package/dist/utils/diff-renderer.js +312 -146
- package/dist/utils/diff.d.ts +21 -2
- package/dist/utils/diff.js +165 -89
- package/dist/utils/floating-panel.d.ts +2 -0
- package/dist/utils/floating-panel.js +30 -14
- package/dist/utils/handler-registry.d.ts +31 -10
- package/dist/utils/handler-registry.js +58 -16
- package/dist/utils/line-editor.d.ts +33 -3
- package/dist/utils/line-editor.js +221 -44
- package/dist/utils/markdown.d.ts +1 -0
- package/dist/utils/markdown.js +1 -1
- package/dist/utils/message-utils.d.ts +35 -0
- package/dist/utils/message-utils.js +75 -0
- package/dist/utils/terminal-buffer.d.ts +5 -1
- package/dist/utils/terminal-buffer.js +18 -2
- package/dist/utils/tool-display.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -4
- package/dist/utils/tool-interactive.d.ts +12 -0
- package/dist/utils/tool-interactive.js +53 -0
- package/examples/extensions/ash-acp-bridge/README.md +39 -0
- package/examples/extensions/ash-acp-bridge/package.json +23 -0
- package/examples/extensions/ash-acp-bridge/src/index.ts +574 -0
- package/examples/extensions/ash-acp-bridge/tsconfig.json +14 -0
- package/examples/extensions/ash-mcp-bridge/README.md +72 -0
- package/examples/extensions/ash-mcp-bridge/index.ts +164 -0
- package/examples/extensions/ash-mcp-bridge/package.json +9 -0
- package/examples/extensions/claude-code-bridge/index.ts +198 -51
- package/examples/extensions/claude-code-bridge/package.json +1 -0
- package/examples/extensions/interactive-prompts.ts +98 -112
- package/examples/extensions/overlay-agent.ts +84 -38
- package/examples/extensions/peer-mesh.ts +565 -0
- package/examples/extensions/pi-bridge/index.ts +2 -2
- package/examples/extensions/questionnaire.ts +260 -0
- package/examples/extensions/subagents.ts +19 -4
- package/examples/extensions/terminal-buffer.ts +32 -53
- package/examples/extensions/tmux-pane.ts +307 -0
- package/examples/extensions/user-shell.ts +136 -0
- package/examples/extensions/web-access.ts +335 -0
- package/package.json +44 -2
- package/dist/agent/tools/display.d.ts +0 -13
- package/dist/agent/tools/display.js +0 -70
- package/dist/agent/tools/user-shell.d.ts +0 -13
- package/dist/agent/tools/user-shell.js +0 -87
- package/dist/extensions/overlay-agent.d.ts +0 -14
- package/dist/extensions/overlay-agent.js +0 -147
- package/dist/extensions/terminal-buffer.d.ts +0 -14
- package/dist/extensions/terminal-buffer.js +0 -125
|
@@ -1,15 +1,66 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { discoverProjectSkills } from "./skills.js";
|
|
5
|
+
/**
|
|
6
|
+
* Format skills for inline display in prompt.
|
|
7
|
+
* Shows name, description, and file path so the model can decide immediately
|
|
8
|
+
* whether to load a skill — no extra round-trip needed.
|
|
9
|
+
*/
|
|
10
|
+
export function formatSkillsBlock(skills) {
|
|
11
|
+
if (skills.length === 0)
|
|
12
|
+
return "";
|
|
13
|
+
return "# Available Skills\n\n"
|
|
14
|
+
+ "Load a skill's full content with read_file on its file path when needed.\n\n"
|
|
15
|
+
+ skills.map(s => `- **${s.name}**: ${s.description}\n Path: ${s.filePath}`).join("\n\n");
|
|
16
|
+
}
|
|
17
|
+
// Resolve to the user's home-based config dir — user's standing instructions to the agent
|
|
18
|
+
import * as os from "node:os";
|
|
19
|
+
const GLOBAL_AGENTS_MD = path.join(os.homedir(), ".agent-sh", "AGENTS.md");
|
|
20
|
+
// ── File caches ─────────────────────────────────────────────────────
|
|
21
|
+
// Convention files (CLAUDE.md/AGENT.md) are walked synchronously from
|
|
22
|
+
// CWD to root on every query. In practice they almost never change,
|
|
23
|
+
// so a short TTL cache keyed by CWD avoids redundant filesystem walks.
|
|
24
|
+
// The 5-second TTL is short enough to pick up edits quickly but long
|
|
25
|
+
// enough to eliminate repeated walks within a multi-tool agent loop.
|
|
26
|
+
const CACHE_TTL_MS = 5_000;
|
|
27
|
+
/** TTL cache for convention files, keyed by resolved CWD. */
|
|
28
|
+
let conventionCache = null;
|
|
29
|
+
/** TTL cache for global AGENTS.md — changes extremely rarely. */
|
|
30
|
+
let agentsMdCache = null;
|
|
31
|
+
export function loadGlobalAgentsMd() {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
if (agentsMdCache && now < agentsMdCache.expiry) {
|
|
34
|
+
return agentsMdCache.result;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const content = fs.readFileSync(GLOBAL_AGENTS_MD, "utf-8").trim();
|
|
38
|
+
const result = content || null;
|
|
39
|
+
agentsMdCache = { result, expiry: now + CACHE_TTL_MS };
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
agentsMdCache = { result: null, expiry: now + CACHE_TTL_MS };
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Resolve the absolute path to agent-sh's own docs directory. */
|
|
48
|
+
const CODE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../");
|
|
4
49
|
/** File names to scan for project conventions (checked in order). */
|
|
5
50
|
const CONVENTION_FILES = ["CLAUDE.md", "AGENT.md"];
|
|
6
51
|
/**
|
|
7
52
|
* Scan from `dir` upward for project convention files.
|
|
8
53
|
* Returns contents ordered root-first (general → specific).
|
|
54
|
+
* Results are cached for CACHE_TTL_MS, keyed by resolved directory.
|
|
9
55
|
*/
|
|
10
56
|
function loadConventionFiles(dir) {
|
|
57
|
+
const cwd = path.resolve(dir);
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
if (conventionCache && conventionCache.cwd === cwd && now < conventionCache.expiry) {
|
|
60
|
+
return conventionCache.result;
|
|
61
|
+
}
|
|
11
62
|
const files = [];
|
|
12
|
-
let current =
|
|
63
|
+
let current = cwd;
|
|
13
64
|
while (true) {
|
|
14
65
|
for (const name of CONVENTION_FILES) {
|
|
15
66
|
const candidate = path.join(current, name);
|
|
@@ -30,95 +81,77 @@ function loadConventionFiles(dir) {
|
|
|
30
81
|
current = parent;
|
|
31
82
|
}
|
|
32
83
|
files.reverse();
|
|
33
|
-
|
|
84
|
+
const result = files.map(f => `<!-- ${f.path} -->\n${f.content}`);
|
|
85
|
+
conventionCache = { cwd, result, expiry: now + CACHE_TTL_MS };
|
|
86
|
+
return result;
|
|
34
87
|
}
|
|
35
88
|
/**
|
|
36
89
|
* Static system prompt — identical across all queries, cacheable.
|
|
37
90
|
* Contains only identity and behavioral instructions.
|
|
38
91
|
*/
|
|
39
|
-
export const STATIC_SYSTEM_PROMPT = `You are an AI coding assistant
|
|
92
|
+
export const STATIC_SYSTEM_PROMPT = `You are an AI coding assistant running inside agent-sh, a terminal shell.
|
|
40
93
|
You have access to the user's shell environment and can read, write, and execute code.
|
|
41
94
|
You share the user's working directory, environment variables, and shell history.
|
|
95
|
+
agent-sh documentation is at ${path.join(CODE_DIR, "docs")} — start with README.md for an index. Read the docs when you need to understand how the runtime works.
|
|
42
96
|
|
|
43
97
|
# Tool Decision Guide
|
|
44
|
-
|
|
45
|
-
You have three categories of tools — choose based on who needs the output and
|
|
46
|
-
whether the command has lasting effects:
|
|
47
|
-
|
|
48
|
-
**Scratchpad tools** (bash, read_file, grep, glob, ls, edit_file, write_file):
|
|
98
|
+
bash, read_file, grep, glob, ls, edit_file, write_file::
|
|
49
99
|
Use these to investigate, search, read, and modify files. Output is returned
|
|
50
100
|
to you for reasoning — the user doesn't see it directly.
|
|
51
101
|
|
|
52
|
-
|
|
53
|
-
Use this to show output to the user in their terminal. The user sees the
|
|
54
|
-
output directly, but it is NOT returned to you. Use when:
|
|
55
|
-
- The user asks to see something (cat a file, git log, git diff, man page)
|
|
56
|
-
- The output is for the user to read, not for you to process
|
|
57
|
-
|
|
58
|
-
**Live shell** (user_shell):
|
|
59
|
-
Use this to run complete, non-interactive commands in the user's real shell. Use for:
|
|
60
|
-
- Commands that affect shell state (cd, export, source)
|
|
61
|
-
- Installing packages, starting servers, running builds
|
|
62
|
-
- Any command where the user wants real side effects
|
|
63
|
-
- Set return_output=true only if you need to inspect the result
|
|
64
|
-
|
|
65
|
-
**Terminal interaction** (terminal_read, terminal_keys):
|
|
66
|
-
Use these to observe and interact with what is currently on the user's terminal screen.
|
|
67
|
-
- terminal_read: see what the user sees (current screen contents, cursor position)
|
|
68
|
-
- terminal_keys: send keystrokes as if the user typed them
|
|
69
|
-
Use for: driving interactive programs (vim, htop, less, ssh, REPLs), answering questions
|
|
70
|
-
about what's on screen, or typing at the shell prompt when a program is already running.
|
|
71
|
-
Do NOT use user_shell to interact with an already-running program — use these instead.
|
|
72
|
-
|
|
73
|
-
Default to scratchpad tools for your own investigation. Use display when the
|
|
74
|
-
user is the intended audience. Use user_shell when the command has real effects.
|
|
75
|
-
Use terminal_read/terminal_keys when interacting with what's already on screen.
|
|
76
|
-
|
|
77
|
-
# Interactive Overlay Sessions
|
|
78
|
-
|
|
79
|
-
When the dynamic context includes \`interactive-session: true\`, the user has summoned you
|
|
80
|
-
via a hotkey overlay from inside their live terminal. They may be in the middle of using
|
|
81
|
-
a program (vim, ssh, a REPL, etc.) or at a shell prompt. In this mode:
|
|
82
|
-
- Start with terminal_read if you need to understand what's on screen.
|
|
83
|
-
- Prefer terminal_keys to interact with whatever is currently running.
|
|
84
|
-
- Use user_shell only for running new, standalone commands — not for interacting with
|
|
85
|
-
what's already on screen.
|
|
86
|
-
- Keep responses concise — the user is in the middle of a workflow.
|
|
102
|
+
Extensions may register additional tools — follow their instructions.
|
|
87
103
|
|
|
88
104
|
# Tool Usage Guidelines
|
|
89
105
|
- Use read_file before editing a file you haven't seen
|
|
90
106
|
- Prefer edit_file over write_file for modifying existing files
|
|
91
107
|
- Use grep/glob to find files before reading them
|
|
92
108
|
- Keep bash commands focused; avoid long-running blocking commands
|
|
93
|
-
- Always check command exit codes for errors
|
|
109
|
+
- Always check command exit codes for errors
|
|
110
|
+
|
|
111
|
+
# Preference Learning
|
|
112
|
+
|
|
113
|
+
Treat the user's past commands as standing preferences. Before acting, check shell history
|
|
114
|
+
and conversation context for recurring patterns — apply them proactively and do not wait to
|
|
115
|
+
be reminded.`;
|
|
94
116
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* Runs through the "agent:dynamic-context" pipe so extensions can append.
|
|
117
|
+
* CWD-scoped static context: project conventions (CLAUDE.md / AGENT.md)
|
|
118
|
+
* and discovered skills. Stable for a given cwd — callers should cache
|
|
119
|
+
* on cwd identity rather than rebuilding per LLM iteration.
|
|
99
120
|
*/
|
|
100
|
-
export function
|
|
121
|
+
export function buildStaticByCwd(cwd) {
|
|
101
122
|
const sections = [];
|
|
102
|
-
|
|
103
|
-
sections.push("# Available Tools\n" +
|
|
104
|
-
tools.map((t) => `- ${t.name}: ${t.description}`).join("\n"));
|
|
105
|
-
// Project conventions (CLAUDE.md / AGENT.md)
|
|
106
|
-
const conventions = loadConventionFiles(contextManager.getCwd());
|
|
123
|
+
const conventions = loadConventionFiles(cwd);
|
|
107
124
|
if (conventions.length > 0) {
|
|
108
125
|
sections.push("# Project Conventions\n\n" + conventions.join("\n\n"));
|
|
109
126
|
}
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
sections.push(
|
|
127
|
+
const projectSkills = discoverProjectSkills(cwd);
|
|
128
|
+
const skillsBlock = formatSkillsBlock(projectSkills);
|
|
129
|
+
if (skillsBlock) {
|
|
130
|
+
sections.push(skillsBlock);
|
|
114
131
|
}
|
|
115
|
-
// Shell context — pass token budget converted to bytes (~4 chars/token)
|
|
116
|
-
const shellBudgetBytes = shellBudgetTokens != null ? shellBudgetTokens * 4 : undefined;
|
|
117
|
-
const shellContext = contextManager.getContext(shellBudgetBytes);
|
|
118
|
-
if (shellContext) {
|
|
119
|
-
sections.push(shellContext);
|
|
120
|
-
}
|
|
121
|
-
// Metadata
|
|
122
|
-
sections.push(`Current date: ${new Date().toISOString().split("T")[0]}\nWorking directory: ${contextManager.getCwd()}`);
|
|
123
132
|
return sections.join("\n\n");
|
|
124
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Per-iteration dynamic context: date, working directory, token usage.
|
|
136
|
+
* Rebuilt every LLM call. Extension advisors add more sections (budget,
|
|
137
|
+
* subagents, metacognitive signals, etc.) on top.
|
|
138
|
+
*
|
|
139
|
+
* Skills, AGENTS.md, and project conventions live in the system prompt
|
|
140
|
+
* (see `system-prompt:build` in agent-loop) so they enter the provider's
|
|
141
|
+
* prefix cache instead of being rebuilt and re-sent every turn.
|
|
142
|
+
*
|
|
143
|
+
* Shell context is likewise not injected here — it flows into the
|
|
144
|
+
* conversation as incremental <shell-events> messages (see
|
|
145
|
+
* AgentLoop.injectShellDelta) for the same reason.
|
|
146
|
+
*/
|
|
147
|
+
export function buildDynamicContext(contextManager, tokenStatus) {
|
|
148
|
+
const envLines = [
|
|
149
|
+
`Current date: ${new Date().toISOString().split("T")[0]}`,
|
|
150
|
+
`Working directory: ${contextManager.getCwd()}`,
|
|
151
|
+
];
|
|
152
|
+
const usedK = (tokenStatus.promptTokens / 1000).toFixed(1);
|
|
153
|
+
const maxK = (tokenStatus.contextWindow / 1000).toFixed(0);
|
|
154
|
+
const pct = Math.min(100, Math.round((tokenStatus.promptTokens / tokenStatus.contextWindow) * 100));
|
|
155
|
+
envLines.push(`Token usage: ${usedK}k/${maxK}k (${pct}%)`);
|
|
156
|
+
return `<environment>\n${envLines.join("\n")}\n</environment>`;
|
|
157
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
/** Response reserve — tokens reserved for the model's output. */
|
|
2
|
+
declare const RESPONSE_RESERVE = 8192;
|
|
3
|
+
/** Fallback when contextWindow is unknown. */
|
|
4
|
+
declare const DEFAULT_CONTEXT_WINDOW = 60000;
|
|
5
|
+
export { RESPONSE_RESERVE, DEFAULT_CONTEXT_WINDOW };
|
|
1
6
|
export declare class TokenBudget {
|
|
2
7
|
private contextWindow;
|
|
3
8
|
private toolCount;
|
|
4
9
|
constructor(contextWindow?: number, toolCount?: number);
|
|
5
10
|
/** Update when model or tool set changes. */
|
|
6
11
|
update(contextWindow?: number, toolCount?: number): void;
|
|
7
|
-
/** Total tokens available for shell context + conversation content. */
|
|
8
|
-
get contentBudget(): number;
|
|
9
12
|
/** Token budget for the shell context stream. */
|
|
10
13
|
get shellBudgetTokens(): number;
|
|
11
|
-
/** Token budget for the conversation messages stream. */
|
|
12
|
-
get conversationBudgetTokens(): number;
|
|
13
14
|
}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Token budget for shell context sizing.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Computes how much of the context window to allocate to shell history
|
|
5
|
+
* (user commands and outputs — situational awareness). The remaining
|
|
6
|
+
* space is for the conversation, system prompt, tools, and response.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Shell context is sized loosely — chars/4 accuracy is fine for this.
|
|
9
|
+
* Conversation and compaction decisions use API-grounded token counts
|
|
10
|
+
* (see ConversationState.estimatePromptTokens).
|
|
10
11
|
*/
|
|
11
|
-
import { getSettings } from "
|
|
12
|
-
/** Overhead estimates (tokens). */
|
|
12
|
+
import { getSettings } from "../settings.js";
|
|
13
13
|
const SYSTEM_PROMPT_OVERHEAD = 800;
|
|
14
14
|
const DYNAMIC_CONTEXT_OVERHEAD = 500; // conventions, metadata, skills list
|
|
15
15
|
const TOKENS_PER_TOOL_DEFINITION = 50;
|
|
16
|
-
|
|
16
|
+
/** Response reserve — tokens reserved for the model's output. */
|
|
17
|
+
const RESPONSE_RESERVE = 8192;
|
|
17
18
|
/** Fallback when contextWindow is unknown. */
|
|
18
19
|
const DEFAULT_CONTEXT_WINDOW = 60_000;
|
|
20
|
+
export { RESPONSE_RESERVE, DEFAULT_CONTEXT_WINDOW };
|
|
19
21
|
export class TokenBudget {
|
|
20
22
|
contextWindow;
|
|
21
23
|
toolCount;
|
|
@@ -30,21 +32,14 @@ export class TokenBudget {
|
|
|
30
32
|
if (toolCount != null)
|
|
31
33
|
this.toolCount = toolCount;
|
|
32
34
|
}
|
|
33
|
-
/**
|
|
34
|
-
get
|
|
35
|
+
/** Token budget for the shell context stream. */
|
|
36
|
+
get shellBudgetTokens() {
|
|
35
37
|
const overhead = SYSTEM_PROMPT_OVERHEAD +
|
|
36
38
|
DYNAMIC_CONTEXT_OVERHEAD +
|
|
37
39
|
this.toolCount * TOKENS_PER_TOOL_DEFINITION +
|
|
38
40
|
RESPONSE_RESERVE;
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
/** Token budget for the shell context stream. */
|
|
42
|
-
get shellBudgetTokens() {
|
|
41
|
+
const contentBudget = Math.max(0, this.contextWindow - overhead);
|
|
43
42
|
const ratio = getSettings().shellContextRatio;
|
|
44
|
-
return Math.floor(
|
|
45
|
-
}
|
|
46
|
-
/** Token budget for the conversation messages stream. */
|
|
47
|
-
get conversationBudgetTokens() {
|
|
48
|
-
return this.contentBudget - this.shellBudgetTokens;
|
|
43
|
+
return Math.floor(contentBudget * ratio);
|
|
49
44
|
}
|
|
50
45
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolProtocol — abstracts how tools are presented to the LLM and how
|
|
3
|
+
* tool calls are parsed from responses.
|
|
4
|
+
*
|
|
5
|
+
* Two modes:
|
|
6
|
+
* "api" — tools sent via OpenAI tools param, parsed from delta.tool_calls
|
|
7
|
+
* "inline" — tools described as text, tool calls are JSON code blocks
|
|
8
|
+
*
|
|
9
|
+
* The agent loop uses this interface uniformly so the rest of the code
|
|
10
|
+
* doesn't need to know which mode is active.
|
|
11
|
+
*/
|
|
12
|
+
import type { ChatCompletionTool } from "../utils/llm-client.js";
|
|
13
|
+
import type { ToolDefinition } from "./types.js";
|
|
14
|
+
import type { ConversationState } from "./conversation-state.js";
|
|
15
|
+
export interface PendingToolCall {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
argumentsJson: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ToolResult {
|
|
21
|
+
callId: string;
|
|
22
|
+
toolName: string;
|
|
23
|
+
content: string;
|
|
24
|
+
isError: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Streaming filter — strips tool calls from display output. */
|
|
27
|
+
export interface StreamFilter {
|
|
28
|
+
feed(chunk: string): string;
|
|
29
|
+
flush(): string;
|
|
30
|
+
}
|
|
31
|
+
export interface ToolProtocol {
|
|
32
|
+
readonly mode: string;
|
|
33
|
+
/** Tools to pass in the API request's `tools` parameter. undefined = omit. */
|
|
34
|
+
getApiTools(tools: ToolDefinition[]): ChatCompletionTool[] | undefined;
|
|
35
|
+
/** Extra text for dynamic context (tool catalog for inline mode). */
|
|
36
|
+
getToolPrompt(tools: ToolDefinition[]): string;
|
|
37
|
+
/** Extract tool calls from a completed response. */
|
|
38
|
+
extractToolCalls(responseText: string, streamedCalls: PendingToolCall[]): PendingToolCall[];
|
|
39
|
+
/** Rewrite a tool call before execution (e.g., unwrap meta-tool). */
|
|
40
|
+
rewriteToolCall(tc: PendingToolCall): PendingToolCall;
|
|
41
|
+
/** Record the assistant turn in conversation state. */
|
|
42
|
+
recordAssistant(conv: ConversationState, text: string, toolCalls: PendingToolCall[]): void;
|
|
43
|
+
/** Record all tool results for a batch as conversation messages. */
|
|
44
|
+
recordResults(conv: ConversationState, results: ToolResult[]): void;
|
|
45
|
+
/** Create a stream filter for stripping tool calls from display. null = pass-through. */
|
|
46
|
+
createStreamFilter(toolNames: string[]): StreamFilter | null;
|
|
47
|
+
/**
|
|
48
|
+
* Extra tool definitions the protocol wants registered in the tool registry.
|
|
49
|
+
* Used by deferred-lookup mode to register its `load_tool` meta-tool.
|
|
50
|
+
* Default: none.
|
|
51
|
+
*/
|
|
52
|
+
getProtocolTools?(): ToolDefinition[];
|
|
53
|
+
}
|
|
54
|
+
export declare class ApiToolProtocol implements ToolProtocol {
|
|
55
|
+
readonly mode: "api";
|
|
56
|
+
getApiTools(tools: ToolDefinition[]): ChatCompletionTool[] | undefined;
|
|
57
|
+
getToolPrompt(): string;
|
|
58
|
+
extractToolCalls(_text: string, streamedCalls: PendingToolCall[]): PendingToolCall[];
|
|
59
|
+
rewriteToolCall(tc: PendingToolCall): PendingToolCall;
|
|
60
|
+
recordAssistant(conv: ConversationState, text: string, toolCalls: PendingToolCall[]): void;
|
|
61
|
+
recordResults(conv: ConversationState, results: ToolResult[]): void;
|
|
62
|
+
createStreamFilter(): null;
|
|
63
|
+
}
|
|
64
|
+
export declare class InlineToolProtocol implements ToolProtocol {
|
|
65
|
+
readonly mode: "inline";
|
|
66
|
+
private callCounter;
|
|
67
|
+
getApiTools(): undefined;
|
|
68
|
+
getToolPrompt(tools: ToolDefinition[]): string;
|
|
69
|
+
rewriteToolCall(tc: PendingToolCall): PendingToolCall;
|
|
70
|
+
extractToolCalls(text: string, _streamedCalls: PendingToolCall[]): PendingToolCall[];
|
|
71
|
+
recordAssistant(conv: ConversationState, text: string, _toolCalls: PendingToolCall[]): void;
|
|
72
|
+
recordResults(conv: ConversationState, results: ToolResult[]): void;
|
|
73
|
+
createStreamFilter(_toolNames: string[]): StreamFilter;
|
|
74
|
+
}
|
|
75
|
+
export declare class DeferredToolProtocol implements ToolProtocol {
|
|
76
|
+
readonly mode: "deferred";
|
|
77
|
+
private coreNames;
|
|
78
|
+
/** Cached extension tool schemas for arg validation. */
|
|
79
|
+
private extSchemas;
|
|
80
|
+
constructor(coreNames: string[]);
|
|
81
|
+
getApiTools(tools: ToolDefinition[]): ChatCompletionTool[] | undefined;
|
|
82
|
+
getToolPrompt(): string;
|
|
83
|
+
extractToolCalls(_text: string, streamedCalls: PendingToolCall[]): PendingToolCall[];
|
|
84
|
+
rewriteToolCall(tc: PendingToolCall): PendingToolCall;
|
|
85
|
+
recordAssistant(conv: ConversationState, text: string, toolCalls: PendingToolCall[]): void;
|
|
86
|
+
recordResults(conv: ConversationState, results: ToolResult[]): void;
|
|
87
|
+
createStreamFilter(): null;
|
|
88
|
+
}
|
|
89
|
+
export declare class DeferredLookupProtocol implements ToolProtocol {
|
|
90
|
+
readonly mode: "deferred-lookup";
|
|
91
|
+
private coreNames;
|
|
92
|
+
private loadedExt;
|
|
93
|
+
/** Cache of the current tools list so load_tool's execute can find schemas. */
|
|
94
|
+
private toolsRef;
|
|
95
|
+
constructor(coreNames: string[]);
|
|
96
|
+
getApiTools(tools: ToolDefinition[]): ChatCompletionTool[] | undefined;
|
|
97
|
+
getToolPrompt(): string;
|
|
98
|
+
extractToolCalls(_text: string, streamedCalls: PendingToolCall[]): PendingToolCall[];
|
|
99
|
+
rewriteToolCall(tc: PendingToolCall): PendingToolCall;
|
|
100
|
+
recordAssistant(conv: ConversationState, text: string, toolCalls: PendingToolCall[]): void;
|
|
101
|
+
recordResults(conv: ConversationState, results: ToolResult[]): void;
|
|
102
|
+
createStreamFilter(): null;
|
|
103
|
+
getProtocolTools(): ToolDefinition[];
|
|
104
|
+
}
|
|
105
|
+
export declare function createToolProtocol(mode: "api" | "inline" | "deferred" | "deferred-lookup"): ToolProtocol;
|