zerg-ztc 0.1.10 → 0.1.12
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/bin/.gitkeep +0 -0
- package/bin/ztc-audio-darwin-arm64 +0 -0
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +63 -2
- package/dist/App.js.map +1 -1
- package/dist/agent/commands/dictation.d.ts +3 -0
- package/dist/agent/commands/dictation.d.ts.map +1 -0
- package/dist/agent/commands/dictation.js +10 -0
- package/dist/agent/commands/dictation.js.map +1 -0
- package/dist/agent/commands/index.d.ts.map +1 -1
- package/dist/agent/commands/index.js +2 -1
- package/dist/agent/commands/index.js.map +1 -1
- package/dist/agent/commands/types.d.ts +7 -0
- package/dist/agent/commands/types.d.ts.map +1 -1
- package/dist/components/InputArea.d.ts +1 -0
- package/dist/components/InputArea.d.ts.map +1 -1
- package/dist/components/InputArea.js +591 -43
- package/dist/components/InputArea.js.map +1 -1
- package/dist/components/SingleMessage.d.ts.map +1 -1
- package/dist/components/SingleMessage.js +157 -7
- package/dist/components/SingleMessage.js.map +1 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/ui/views/status_bar.js +2 -2
- package/dist/ui/views/status_bar.js.map +1 -1
- package/dist/utils/dictation.d.ts +46 -0
- package/dist/utils/dictation.d.ts.map +1 -0
- package/dist/utils/dictation.js +409 -0
- package/dist/utils/dictation.js.map +1 -0
- package/dist/utils/dictation_native.d.ts +51 -0
- package/dist/utils/dictation_native.d.ts.map +1 -0
- package/dist/utils/dictation_native.js +236 -0
- package/dist/utils/dictation_native.js.map +1 -0
- package/dist/utils/path_format.d.ts +20 -0
- package/dist/utils/path_format.d.ts.map +1 -0
- package/dist/utils/path_format.js +90 -0
- package/dist/utils/path_format.js.map +1 -0
- package/dist/utils/table.d.ts +38 -0
- package/dist/utils/table.d.ts.map +1 -0
- package/dist/utils/table.js +133 -0
- package/dist/utils/table.js.map +1 -0
- package/dist/utils/tool_trace.d.ts +7 -2
- package/dist/utils/tool_trace.d.ts.map +1 -1
- package/dist/utils/tool_trace.js +156 -51
- package/dist/utils/tool_trace.js.map +1 -1
- package/package.json +5 -1
- package/src/App.tsx +0 -813
- package/src/agent/agent.ts +0 -534
- package/src/agent/backends/anthropic.ts +0 -86
- package/src/agent/backends/gemini.ts +0 -119
- package/src/agent/backends/inception.ts +0 -23
- package/src/agent/backends/index.ts +0 -17
- package/src/agent/backends/openai.ts +0 -23
- package/src/agent/backends/openai_compatible.ts +0 -143
- package/src/agent/backends/types.ts +0 -83
- package/src/agent/commands/clipboard.ts +0 -77
- package/src/agent/commands/config.ts +0 -204
- package/src/agent/commands/debug.ts +0 -23
- package/src/agent/commands/emulation.ts +0 -80
- package/src/agent/commands/execution.ts +0 -9
- package/src/agent/commands/help.ts +0 -20
- package/src/agent/commands/history.ts +0 -13
- package/src/agent/commands/index.ts +0 -46
- package/src/agent/commands/input_mode.ts +0 -22
- package/src/agent/commands/keybindings.ts +0 -40
- package/src/agent/commands/model.ts +0 -11
- package/src/agent/commands/models.ts +0 -116
- package/src/agent/commands/permissions.ts +0 -64
- package/src/agent/commands/retry.ts +0 -9
- package/src/agent/commands/shell.ts +0 -68
- package/src/agent/commands/skills.ts +0 -54
- package/src/agent/commands/status.ts +0 -19
- package/src/agent/commands/types.ts +0 -80
- package/src/agent/commands/update.ts +0 -32
- package/src/agent/factory.ts +0 -60
- package/src/agent/index.ts +0 -20
- package/src/agent/runtime/capabilities.ts +0 -7
- package/src/agent/runtime/memory.ts +0 -23
- package/src/agent/runtime/policy.ts +0 -48
- package/src/agent/runtime/session.ts +0 -18
- package/src/agent/runtime/tracing.ts +0 -23
- package/src/agent/tools/file.ts +0 -178
- package/src/agent/tools/index.ts +0 -52
- package/src/agent/tools/screenshot.ts +0 -821
- package/src/agent/tools/search.ts +0 -138
- package/src/agent/tools/shell.ts +0 -69
- package/src/agent/tools/skills.ts +0 -28
- package/src/agent/tools/types.ts +0 -14
- package/src/agent/tools/zerg.ts +0 -50
- package/src/cli.tsx +0 -163
- package/src/components/ActivityLine.tsx +0 -23
- package/src/components/FullScreen.tsx +0 -79
- package/src/components/Header.tsx +0 -27
- package/src/components/InputArea.tsx +0 -1096
- package/src/components/MessageList.tsx +0 -71
- package/src/components/SingleMessage.tsx +0 -59
- package/src/components/StatusBar.tsx +0 -55
- package/src/components/index.tsx +0 -8
- package/src/config/types.ts +0 -12
- package/src/config.ts +0 -186
- package/src/debug/logger.ts +0 -14
- package/src/emulation/README.md +0 -24
- package/src/emulation/catalog.ts +0 -82
- package/src/emulation/trace_style.ts +0 -8
- package/src/emulation/types.ts +0 -7
- package/src/skills/index.ts +0 -36
- package/src/skills/loader.ts +0 -135
- package/src/skills/registry.ts +0 -6
- package/src/skills/types.ts +0 -10
- package/src/types.ts +0 -84
- package/src/ui/README.md +0 -44
- package/src/ui/core/factory.ts +0 -9
- package/src/ui/core/index.ts +0 -4
- package/src/ui/core/input.ts +0 -38
- package/src/ui/core/input_segments.ts +0 -410
- package/src/ui/core/input_state.ts +0 -17
- package/src/ui/core/layout_yoga.ts +0 -122
- package/src/ui/core/style.ts +0 -38
- package/src/ui/core/types.ts +0 -54
- package/src/ui/ink/index.tsx +0 -1
- package/src/ui/ink/render.tsx +0 -60
- package/src/ui/views/activity_line.ts +0 -33
- package/src/ui/views/app.ts +0 -111
- package/src/ui/views/header.ts +0 -44
- package/src/ui/views/input_area.ts +0 -255
- package/src/ui/views/message_list.ts +0 -443
- package/src/ui/views/status_bar.ts +0 -114
- package/src/ui/vue/index.ts +0 -53
- package/src/ui/web/frame_render.tsx +0 -148
- package/src/ui/web/index.tsx +0 -1
- package/src/ui/web/render.tsx +0 -41
- package/src/utils/clipboard.ts +0 -39
- package/src/utils/clipboard_image.ts +0 -40
- package/src/utils/diff.ts +0 -52
- package/src/utils/image_preview.ts +0 -36
- package/src/utils/models.ts +0 -98
- package/src/utils/path_complete.ts +0 -173
- package/src/utils/shell.ts +0 -72
- package/src/utils/spinner_frames.ts +0 -1
- package/src/utils/spinner_verbs.ts +0 -23
- package/src/utils/tool_summary.ts +0 -56
- package/src/utils/tool_trace.ts +0 -216
- package/src/utils/update.ts +0 -44
- package/src/utils/version.ts +0 -15
- package/src/web/index.html +0 -352
- package/src/web/mirror-favicon.svg +0 -4
- package/src/web/mirror.html +0 -641
- package/src/web/mirror_hook.ts +0 -25
- package/src/web/mirror_server.ts +0 -204
- package/tsconfig.json +0 -22
- package/vite.config.ts +0 -363
package/src/agent/agent.ts
DELETED
|
@@ -1,534 +0,0 @@
|
|
|
1
|
-
// Standard library
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
|
-
import { extname } from 'path';
|
|
4
|
-
|
|
5
|
-
// Local
|
|
6
|
-
import { Message, ToolCall, AgentEvent } from '../types.js';
|
|
7
|
-
import { AnthropicBackend, AgentBackend, BackendRequest, BackendResponse, ContentBlock, LlmMessage, RequestContentBlock, ToolResultBlock, TokenUsage } from './backends/index.js';
|
|
8
|
-
import { AllowAllPolicy, Policy } from './runtime/policy.js';
|
|
9
|
-
import { NoopTracer, Tracer } from './runtime/tracing.js';
|
|
10
|
-
import { defaultTools, executeTool, getToolDefinitions, getTool } from './tools/index.js';
|
|
11
|
-
|
|
12
|
-
// --- Types ---
|
|
13
|
-
|
|
14
|
-
export interface AgentConfig {
|
|
15
|
-
model?: string;
|
|
16
|
-
apiKey?: string;
|
|
17
|
-
apiEndpoint?: string;
|
|
18
|
-
maxTokens?: number;
|
|
19
|
-
maxIterations?: number;
|
|
20
|
-
tools?: typeof defaultTools;
|
|
21
|
-
systemPrompt?: string;
|
|
22
|
-
backend?: AgentBackend;
|
|
23
|
-
policy?: Policy;
|
|
24
|
-
tracer?: Tracer;
|
|
25
|
-
cwd?: string; // Working directory for tool execution
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class AgentError extends Error {
|
|
29
|
-
constructor(message: string, public code?: string) {
|
|
30
|
-
super(message);
|
|
31
|
-
this.name = 'AgentError';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type EventHandler = (event: AgentEvent) => void;
|
|
36
|
-
|
|
37
|
-
// --- Agent Class ---
|
|
38
|
-
|
|
39
|
-
export class Agent {
|
|
40
|
-
private config: Required<AgentConfig>;
|
|
41
|
-
private eventHandlers: Set<EventHandler> = new Set();
|
|
42
|
-
private backend: AgentBackend;
|
|
43
|
-
private policy: Policy;
|
|
44
|
-
private tracer: Tracer;
|
|
45
|
-
private streamChunkSize = 32;
|
|
46
|
-
private _cwd: string;
|
|
47
|
-
|
|
48
|
-
constructor(config: AgentConfig = {}) {
|
|
49
|
-
const apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY || '';
|
|
50
|
-
|
|
51
|
-
this.config = {
|
|
52
|
-
model: config.model || 'claude-opus-4-20250514',
|
|
53
|
-
apiKey,
|
|
54
|
-
apiEndpoint: config.apiEndpoint || 'https://api.anthropic.com/v1/messages',
|
|
55
|
-
maxTokens: config.maxTokens || 4096,
|
|
56
|
-
maxIterations: config.maxIterations || 10,
|
|
57
|
-
tools: config.tools || defaultTools,
|
|
58
|
-
systemPrompt: config.systemPrompt || this.getDefaultSystemPrompt(),
|
|
59
|
-
backend: config.backend || new AnthropicBackend({ apiKey, apiEndpoint: config.apiEndpoint }),
|
|
60
|
-
policy: config.policy || new AllowAllPolicy(),
|
|
61
|
-
tracer: config.tracer || new NoopTracer(),
|
|
62
|
-
cwd: config.cwd || process.cwd()
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
this.backend = this.config.backend;
|
|
66
|
-
this.policy = this.config.policy;
|
|
67
|
-
this.tracer = this.config.tracer;
|
|
68
|
-
this._cwd = this.config.cwd;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
hasApiKey(): boolean {
|
|
72
|
-
return !!this.config.apiKey && this.config.apiKey.length > 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
setCwd(cwd: string): void {
|
|
76
|
-
this._cwd = cwd;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
getCwd(): string {
|
|
80
|
-
return this._cwd;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private getDefaultSystemPrompt(): string {
|
|
84
|
-
return `You are ZTC (Zerg Terminal Client), an AI assistant that helps users interact with the Zerg continual AI system and manage local development tasks.
|
|
85
|
-
|
|
86
|
-
You have access to tools for:
|
|
87
|
-
- Reading and writing files
|
|
88
|
-
- Listing directory contents
|
|
89
|
-
- Running shell commands
|
|
90
|
-
- Taking screenshots (full screen or specific windows by app name, PID, or window ID)
|
|
91
|
-
- Listing open windows to find window IDs
|
|
92
|
-
- Launching apps and capturing their windows
|
|
93
|
-
- Querying the Zerg system
|
|
94
|
-
|
|
95
|
-
Be concise and helpful. When using tools, explain what you're doing briefly. If a task requires multiple steps, proceed through them systematically.
|
|
96
|
-
|
|
97
|
-
When a user intent maps to an available slash command, invoke the command directly (just the command) instead of explaining how to do it. Prefer executing commands over describing them.`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Event handling
|
|
101
|
-
on(handler: EventHandler): () => void {
|
|
102
|
-
this.eventHandlers.add(handler);
|
|
103
|
-
return () => this.eventHandlers.delete(handler);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private emit(event: AgentEvent): void {
|
|
107
|
-
for (const handler of this.eventHandlers) {
|
|
108
|
-
handler(event);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Convert messages to API format
|
|
113
|
-
private formatMessages(messages: Message[]): LlmMessage[] {
|
|
114
|
-
return messages
|
|
115
|
-
.filter((m): m is Message & { role: 'user' | 'assistant' } => {
|
|
116
|
-
// Only include user and assistant messages
|
|
117
|
-
if (m.role !== 'user' && m.role !== 'assistant') return false;
|
|
118
|
-
// Filter out assistant messages with empty content AND no tool calls
|
|
119
|
-
if (m.role === 'assistant' && (!m.content || m.content.trim() === '') && !m.toolCalls?.length) return false;
|
|
120
|
-
return true;
|
|
121
|
-
})
|
|
122
|
-
.map(m => {
|
|
123
|
-
if (m.role === 'user') {
|
|
124
|
-
return {
|
|
125
|
-
role: m.role,
|
|
126
|
-
content: this.buildContentBlocks(m.content)
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Assistant message - may need to include tool_use blocks
|
|
131
|
-
if (m.toolCalls && m.toolCalls.length > 0) {
|
|
132
|
-
// Build content array with text and tool_use blocks
|
|
133
|
-
const contentBlocks: Array<{ type: 'text'; text: string } | { type: 'tool_use'; id: string; name: string; input: Record<string, unknown> }> = [];
|
|
134
|
-
|
|
135
|
-
// Add text content if present
|
|
136
|
-
if (m.content && m.content.trim() && m.content !== '[Using tools...]') {
|
|
137
|
-
contentBlocks.push({ type: 'text', text: m.content });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Add tool_use blocks
|
|
141
|
-
for (const tc of m.toolCalls) {
|
|
142
|
-
contentBlocks.push({
|
|
143
|
-
type: 'tool_use',
|
|
144
|
-
id: tc.id,
|
|
145
|
-
name: tc.name,
|
|
146
|
-
input: tc.args
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
role: m.role,
|
|
152
|
-
content: contentBlocks as unknown as RequestContentBlock[]
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Plain text assistant message
|
|
157
|
-
return {
|
|
158
|
-
role: m.role,
|
|
159
|
-
content: m.content
|
|
160
|
-
};
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private contentLength(content: string | RequestContentBlock[]): number {
|
|
165
|
-
if (typeof content === 'string') return content.length;
|
|
166
|
-
return content.reduce((sum, block) => {
|
|
167
|
-
if (block.type === 'text') return sum + block.text.length;
|
|
168
|
-
if (block.type === 'image') return sum + block.data.length;
|
|
169
|
-
if (block.type === 'tool_result') {
|
|
170
|
-
// Estimate tool result content length
|
|
171
|
-
if (typeof block.content === 'string') return sum + block.content.length;
|
|
172
|
-
return sum + block.content.reduce((s, b) => {
|
|
173
|
-
if (b.type === 'text') return s + b.text.length;
|
|
174
|
-
if (b.type === 'image') return s + b.source.data.length;
|
|
175
|
-
return s;
|
|
176
|
-
}, 0);
|
|
177
|
-
}
|
|
178
|
-
return sum;
|
|
179
|
-
}, 0);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Parse tool result string to check for image data
|
|
183
|
-
private parseToolResultForImages(result: string): { hasImage: boolean; imageData?: { mediaType: string; data: string }; text: string } {
|
|
184
|
-
try {
|
|
185
|
-
const parsed = JSON.parse(result);
|
|
186
|
-
if (parsed && parsed.type === 'image' && parsed.data && parsed.mediaType) {
|
|
187
|
-
return {
|
|
188
|
-
hasImage: true,
|
|
189
|
-
imageData: { mediaType: parsed.mediaType, data: parsed.data },
|
|
190
|
-
text: parsed.description || 'Screenshot captured'
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
} catch {
|
|
194
|
-
// Not JSON or not an image result
|
|
195
|
-
}
|
|
196
|
-
return { hasImage: false, text: result };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Build tool result content blocks for the API
|
|
200
|
-
private buildToolResultBlocks(toolResults: Array<{ tool_use_id: string; content: string }>): ToolResultBlock[] {
|
|
201
|
-
return toolResults.map(result => {
|
|
202
|
-
const parsed = this.parseToolResultForImages(result.content);
|
|
203
|
-
|
|
204
|
-
if (parsed.hasImage && parsed.imageData) {
|
|
205
|
-
// Include both text and image in tool result
|
|
206
|
-
return {
|
|
207
|
-
type: 'tool_result' as const,
|
|
208
|
-
tool_use_id: result.tool_use_id,
|
|
209
|
-
content: [
|
|
210
|
-
{ type: 'text' as const, text: parsed.text },
|
|
211
|
-
{
|
|
212
|
-
type: 'image' as const,
|
|
213
|
-
source: {
|
|
214
|
-
type: 'base64' as const,
|
|
215
|
-
media_type: parsed.imageData.mediaType,
|
|
216
|
-
data: parsed.imageData.data
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
]
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Plain text result
|
|
224
|
-
return {
|
|
225
|
-
type: 'tool_result' as const,
|
|
226
|
-
tool_use_id: result.tool_use_id,
|
|
227
|
-
content: result.content
|
|
228
|
-
};
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private buildContentBlocks(content: string): string | RequestContentBlock[] {
|
|
233
|
-
const trimmed = content.trimStart();
|
|
234
|
-
|
|
235
|
-
// Check if this is a tool results message
|
|
236
|
-
if (trimmed.startsWith('[') && trimmed.includes('"tool_use_id"')) {
|
|
237
|
-
try {
|
|
238
|
-
const toolResults = JSON.parse(trimmed) as Array<{ tool_use_id: string; content: string }>;
|
|
239
|
-
return this.buildToolResultBlocks(toolResults);
|
|
240
|
-
} catch {
|
|
241
|
-
return content;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const imageRegex = /\[image ([^\]]+)\]/g;
|
|
246
|
-
let match: RegExpExecArray | null;
|
|
247
|
-
let cursor = 0;
|
|
248
|
-
const blocks: RequestContentBlock[] = [];
|
|
249
|
-
|
|
250
|
-
while ((match = imageRegex.exec(content)) !== null) {
|
|
251
|
-
const start = match.index;
|
|
252
|
-
const end = match.index + match[0].length;
|
|
253
|
-
if (start > cursor) {
|
|
254
|
-
blocks.push({ type: 'text', text: content.slice(cursor, start) });
|
|
255
|
-
}
|
|
256
|
-
const path = match[1].trim();
|
|
257
|
-
const imageBlock = this.loadImageBlock(path);
|
|
258
|
-
if (imageBlock) {
|
|
259
|
-
blocks.push(imageBlock);
|
|
260
|
-
} else {
|
|
261
|
-
blocks.push({ type: 'text', text: match[0] });
|
|
262
|
-
}
|
|
263
|
-
cursor = end;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (blocks.length === 0) return content;
|
|
267
|
-
if (cursor < content.length) {
|
|
268
|
-
blocks.push({ type: 'text', text: content.slice(cursor) });
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const merged: RequestContentBlock[] = [];
|
|
272
|
-
for (const block of blocks) {
|
|
273
|
-
const last = merged[merged.length - 1];
|
|
274
|
-
if (block.type === 'text' && last?.type === 'text') {
|
|
275
|
-
last.text += block.text;
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
merged.push(block);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const hasImage = merged.some(block => block.type === 'image');
|
|
282
|
-
return hasImage ? merged : content;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private loadImageBlock(path: string): RequestContentBlock | null {
|
|
286
|
-
try {
|
|
287
|
-
if (!existsSync(path)) return null;
|
|
288
|
-
const ext = extname(path).toLowerCase();
|
|
289
|
-
const mediaType = this.getImageMediaType(ext);
|
|
290
|
-
if (!mediaType) return null;
|
|
291
|
-
const buffer = readFileSync(path);
|
|
292
|
-
return {
|
|
293
|
-
type: 'image',
|
|
294
|
-
mediaType,
|
|
295
|
-
data: buffer.toString('base64'),
|
|
296
|
-
path
|
|
297
|
-
};
|
|
298
|
-
} catch {
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private getImageMediaType(ext: string): string | null {
|
|
304
|
-
switch (ext) {
|
|
305
|
-
case '.png':
|
|
306
|
-
return 'image/png';
|
|
307
|
-
case '.jpg':
|
|
308
|
-
case '.jpeg':
|
|
309
|
-
return 'image/jpeg';
|
|
310
|
-
case '.webp':
|
|
311
|
-
return 'image/webp';
|
|
312
|
-
case '.gif':
|
|
313
|
-
return 'image/gif';
|
|
314
|
-
case '.bmp':
|
|
315
|
-
return 'image/bmp';
|
|
316
|
-
case '.tif':
|
|
317
|
-
case '.tiff':
|
|
318
|
-
return 'image/tiff';
|
|
319
|
-
case '.heic':
|
|
320
|
-
case '.heif':
|
|
321
|
-
return 'image/heic';
|
|
322
|
-
default:
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Type guards
|
|
328
|
-
private isTextBlock(block: ContentBlock): block is { type: 'text'; text: string } {
|
|
329
|
-
return block.type === 'text';
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private isToolUseBlock(block: ContentBlock): block is { type: 'tool_use'; id: string; name: string; input: Record<string, unknown> } {
|
|
333
|
-
return block.type === 'tool_use';
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private buildRequest(messages: Message[]): BackendRequest {
|
|
337
|
-
return {
|
|
338
|
-
model: this.config.model,
|
|
339
|
-
maxTokens: this.config.maxTokens,
|
|
340
|
-
system: this.config.systemPrompt,
|
|
341
|
-
messages: this.formatMessages(messages),
|
|
342
|
-
tools: getToolDefinitions(this.config.tools)
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
private estimateUsage(request: BackendRequest, response: BackendResponse): TokenUsage {
|
|
347
|
-
const inputChars = request.system.length + request.messages.reduce((sum, msg) => sum + this.contentLength(msg.content), 0);
|
|
348
|
-
const outputChars = response.content
|
|
349
|
-
.filter(this.isTextBlock)
|
|
350
|
-
.reduce((sum, block) => sum + block.text.length, 0);
|
|
351
|
-
const inputTokens = Math.max(1, Math.ceil(inputChars / 4));
|
|
352
|
-
const outputTokens = Math.max(1, Math.ceil(outputChars / 4));
|
|
353
|
-
return {
|
|
354
|
-
inputTokens,
|
|
355
|
-
outputTokens,
|
|
356
|
-
totalTokens: inputTokens + outputTokens,
|
|
357
|
-
estimated: true
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
private mergeUsage(current: TokenUsage | null, next: TokenUsage): TokenUsage {
|
|
362
|
-
if (!current) return next;
|
|
363
|
-
return {
|
|
364
|
-
inputTokens: current.inputTokens + next.inputTokens,
|
|
365
|
-
outputTokens: current.outputTokens + next.outputTokens,
|
|
366
|
-
totalTokens: current.totalTokens + next.totalTokens,
|
|
367
|
-
estimated: current.estimated || next.estimated
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private async emitStreamText(text: string): Promise<void> {
|
|
372
|
-
this.emit({ type: 'stream_start' });
|
|
373
|
-
for (let i = 0; i < text.length; i += this.streamChunkSize) {
|
|
374
|
-
const chunk = text.slice(i, i + this.streamChunkSize);
|
|
375
|
-
this.emit({ type: 'stream_delta', content: chunk });
|
|
376
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
377
|
-
}
|
|
378
|
-
this.emit({ type: 'stream_end' });
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Main execution loop
|
|
382
|
-
async run(messages: Message[]): Promise<{ content: string; toolCalls: ToolCall[]; usage?: TokenUsage }> {
|
|
383
|
-
if (!this.hasApiKey()) {
|
|
384
|
-
throw new AgentError('No API key configured. Use /config key <key> to set one.', 'NO_API_KEY');
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const allToolCalls: ToolCall[] = [];
|
|
388
|
-
let iterations = 0;
|
|
389
|
-
let finalContent = '';
|
|
390
|
-
let totalUsage: TokenUsage | null = null;
|
|
391
|
-
|
|
392
|
-
while (iterations < this.config.maxIterations) {
|
|
393
|
-
iterations++;
|
|
394
|
-
this.emit({ type: 'thinking_start' });
|
|
395
|
-
|
|
396
|
-
try {
|
|
397
|
-
// Call the API
|
|
398
|
-
const request = this.buildRequest(messages);
|
|
399
|
-
this.tracer.event({ type: 'api_request', timestamp: new Date() });
|
|
400
|
-
const response = await this.callAPI(request);
|
|
401
|
-
this.tracer.event({ type: 'api_response', timestamp: new Date(), data: { stopReason: response.stopReason } });
|
|
402
|
-
this.emit({ type: 'thinking_end' });
|
|
403
|
-
|
|
404
|
-
const usage = response.usage ?? this.estimateUsage(request, response);
|
|
405
|
-
totalUsage = this.mergeUsage(totalUsage, usage);
|
|
406
|
-
this.emit({ type: 'token_usage', usage: totalUsage });
|
|
407
|
-
|
|
408
|
-
// Check for tool use
|
|
409
|
-
const toolUseBlocks = response.content.filter(this.isToolUseBlock);
|
|
410
|
-
|
|
411
|
-
if (toolUseBlocks.length === 0) {
|
|
412
|
-
// No tools, extract text and finish
|
|
413
|
-
const textBlocks = response.content.filter(this.isTextBlock);
|
|
414
|
-
finalContent = textBlocks.map(b => b.text).join('\n');
|
|
415
|
-
await this.emitStreamText(finalContent);
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Execute tools
|
|
420
|
-
const toolResults: Array<{ tool_use_id: string; content: string }> = [];
|
|
421
|
-
|
|
422
|
-
for (const toolBlock of toolUseBlocks) {
|
|
423
|
-
const toolCall: ToolCall = {
|
|
424
|
-
id: toolBlock.id,
|
|
425
|
-
name: toolBlock.name,
|
|
426
|
-
args: toolBlock.input,
|
|
427
|
-
status: 'running',
|
|
428
|
-
startedAt: new Date()
|
|
429
|
-
};
|
|
430
|
-
allToolCalls.push(toolCall);
|
|
431
|
-
|
|
432
|
-
const tool = getTool(toolBlock.name, this.config.tools);
|
|
433
|
-
const policyDecision = this.policy.evaluateTool({
|
|
434
|
-
name: toolBlock.name,
|
|
435
|
-
args: toolBlock.input,
|
|
436
|
-
capabilities: tool?.capabilities || []
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
if (!policyDecision.allowed) {
|
|
440
|
-
const reason = policyDecision.reason || 'Policy denied tool execution';
|
|
441
|
-
toolCall.status = 'error';
|
|
442
|
-
toolCall.error = reason;
|
|
443
|
-
toolCall.completedAt = new Date();
|
|
444
|
-
toolResults.push({ tool_use_id: toolBlock.id, content: `Error: ${reason}` });
|
|
445
|
-
this.emit({ type: 'tool_error', tool: toolBlock.name, error: reason });
|
|
446
|
-
this.tracer.event({
|
|
447
|
-
type: 'policy_denied',
|
|
448
|
-
timestamp: new Date(),
|
|
449
|
-
data: { tool: toolBlock.name, reason }
|
|
450
|
-
});
|
|
451
|
-
continue;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
this.tracer.event({
|
|
455
|
-
type: 'tool_start',
|
|
456
|
-
timestamp: new Date(),
|
|
457
|
-
data: { tool: toolBlock.name }
|
|
458
|
-
});
|
|
459
|
-
this.emit({ type: 'tool_start', tool: toolBlock.name, args: toolBlock.input });
|
|
460
|
-
|
|
461
|
-
try {
|
|
462
|
-
const result = await executeTool(
|
|
463
|
-
toolBlock.name,
|
|
464
|
-
toolBlock.input,
|
|
465
|
-
this.config.tools,
|
|
466
|
-
{ cwd: this._cwd }
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
toolCall.status = 'complete';
|
|
470
|
-
toolCall.result = result;
|
|
471
|
-
toolCall.completedAt = new Date();
|
|
472
|
-
|
|
473
|
-
toolResults.push({ tool_use_id: toolBlock.id, content: result });
|
|
474
|
-
this.emit({ type: 'tool_end', tool: toolBlock.name, result });
|
|
475
|
-
this.tracer.event({
|
|
476
|
-
type: 'tool_end',
|
|
477
|
-
timestamp: new Date(),
|
|
478
|
-
data: { tool: toolBlock.name }
|
|
479
|
-
});
|
|
480
|
-
} catch (err) {
|
|
481
|
-
const error = (err as Error).message;
|
|
482
|
-
toolCall.status = 'error';
|
|
483
|
-
toolCall.error = error;
|
|
484
|
-
toolCall.completedAt = new Date();
|
|
485
|
-
|
|
486
|
-
toolResults.push({ tool_use_id: toolBlock.id, content: `Error: ${error}` });
|
|
487
|
-
this.emit({ type: 'tool_error', tool: toolBlock.name, error });
|
|
488
|
-
this.tracer.event({
|
|
489
|
-
type: 'tool_error',
|
|
490
|
-
timestamp: new Date(),
|
|
491
|
-
data: { tool: toolBlock.name, error }
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Add assistant message with tool use
|
|
497
|
-
const textContent = response.content
|
|
498
|
-
.filter(this.isTextBlock)
|
|
499
|
-
.map(b => b.text)
|
|
500
|
-
.join('\n');
|
|
501
|
-
|
|
502
|
-
messages.push({
|
|
503
|
-
id: `assistant_${Date.now()}`,
|
|
504
|
-
role: 'assistant',
|
|
505
|
-
content: textContent || '[Using tools...]',
|
|
506
|
-
timestamp: new Date(),
|
|
507
|
-
toolCalls: allToolCalls.slice(-toolUseBlocks.length)
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// Add tool results as user message (API format)
|
|
511
|
-
messages.push({
|
|
512
|
-
id: `tool_${Date.now()}`,
|
|
513
|
-
role: 'user',
|
|
514
|
-
content: JSON.stringify(toolResults),
|
|
515
|
-
timestamp: new Date()
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
} catch (err) {
|
|
519
|
-
const error = (err as Error).message;
|
|
520
|
-
this.emit({ type: 'error', error });
|
|
521
|
-
throw err;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return { content: finalContent, toolCalls: allToolCalls, usage: totalUsage || undefined };
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// API call
|
|
529
|
-
private async callAPI(request: BackendRequest): Promise<BackendResponse> {
|
|
530
|
-
return this.backend.generate(request);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
export default Agent;
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { AgentBackend, BackendRequest, BackendResponse } from './types.js';
|
|
2
|
-
|
|
3
|
-
interface AnthropicBackendConfig {
|
|
4
|
-
apiKey: string;
|
|
5
|
-
apiEndpoint?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface AnthropicResponse {
|
|
9
|
-
content: BackendResponse['content'];
|
|
10
|
-
stop_reason: string;
|
|
11
|
-
usage?: {
|
|
12
|
-
input_tokens?: number;
|
|
13
|
-
output_tokens?: number;
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class AnthropicBackend implements AgentBackend {
|
|
18
|
-
private apiKey: string;
|
|
19
|
-
private apiEndpoint: string;
|
|
20
|
-
|
|
21
|
-
constructor(config: AnthropicBackendConfig) {
|
|
22
|
-
this.apiKey = config.apiKey;
|
|
23
|
-
this.apiEndpoint = config.apiEndpoint || 'https://api.anthropic.com/v1/messages';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async generate(request: BackendRequest): Promise<BackendResponse> {
|
|
27
|
-
const body = {
|
|
28
|
-
model: request.model,
|
|
29
|
-
max_tokens: request.maxTokens,
|
|
30
|
-
system: request.system,
|
|
31
|
-
messages: request.messages.map(message => ({
|
|
32
|
-
role: message.role,
|
|
33
|
-
content: typeof message.content === 'string'
|
|
34
|
-
? message.content
|
|
35
|
-
: message.content.map(block => {
|
|
36
|
-
if (block.type === 'text') {
|
|
37
|
-
return { type: 'text', text: block.text };
|
|
38
|
-
}
|
|
39
|
-
if (block.type === 'tool_result') {
|
|
40
|
-
// Pass tool results through in Anthropic format
|
|
41
|
-
return block;
|
|
42
|
-
}
|
|
43
|
-
if (block.type === 'tool_use') {
|
|
44
|
-
// Pass tool_use blocks through for assistant messages
|
|
45
|
-
return block;
|
|
46
|
-
}
|
|
47
|
-
// Image block
|
|
48
|
-
return { type: 'image', source: { type: 'base64', media_type: block.mediaType, data: block.data } };
|
|
49
|
-
})
|
|
50
|
-
})),
|
|
51
|
-
tools: request.tools.map(t => ({
|
|
52
|
-
name: t.name,
|
|
53
|
-
description: t.description,
|
|
54
|
-
input_schema: t.parameters
|
|
55
|
-
}))
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const response = await fetch(this.apiEndpoint, {
|
|
59
|
-
method: 'POST',
|
|
60
|
-
headers: {
|
|
61
|
-
'Content-Type': 'application/json',
|
|
62
|
-
'x-api-key': this.apiKey,
|
|
63
|
-
'anthropic-version': '2023-06-01'
|
|
64
|
-
},
|
|
65
|
-
body: JSON.stringify(body)
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (!response.ok) {
|
|
69
|
-
const errorText = await response.text();
|
|
70
|
-
throw new Error(`API error (${response.status}): ${errorText}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const data = await response.json() as AnthropicResponse;
|
|
74
|
-
const inputTokens = data.usage?.input_tokens ?? 0;
|
|
75
|
-
const outputTokens = data.usage?.output_tokens ?? 0;
|
|
76
|
-
return {
|
|
77
|
-
content: data.content,
|
|
78
|
-
stopReason: data.stop_reason,
|
|
79
|
-
usage: data.usage ? {
|
|
80
|
-
inputTokens,
|
|
81
|
-
outputTokens,
|
|
82
|
-
totalTokens: inputTokens + outputTokens
|
|
83
|
-
} : undefined
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
}
|