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.
Files changed (151) hide show
  1. package/bin/.gitkeep +0 -0
  2. package/bin/ztc-audio-darwin-arm64 +0 -0
  3. package/dist/App.d.ts.map +1 -1
  4. package/dist/App.js +63 -2
  5. package/dist/App.js.map +1 -1
  6. package/dist/agent/commands/dictation.d.ts +3 -0
  7. package/dist/agent/commands/dictation.d.ts.map +1 -0
  8. package/dist/agent/commands/dictation.js +10 -0
  9. package/dist/agent/commands/dictation.js.map +1 -0
  10. package/dist/agent/commands/index.d.ts.map +1 -1
  11. package/dist/agent/commands/index.js +2 -1
  12. package/dist/agent/commands/index.js.map +1 -1
  13. package/dist/agent/commands/types.d.ts +7 -0
  14. package/dist/agent/commands/types.d.ts.map +1 -1
  15. package/dist/components/InputArea.d.ts +1 -0
  16. package/dist/components/InputArea.d.ts.map +1 -1
  17. package/dist/components/InputArea.js +591 -43
  18. package/dist/components/InputArea.js.map +1 -1
  19. package/dist/components/SingleMessage.d.ts.map +1 -1
  20. package/dist/components/SingleMessage.js +157 -7
  21. package/dist/components/SingleMessage.js.map +1 -1
  22. package/dist/config/types.d.ts +6 -0
  23. package/dist/config/types.d.ts.map +1 -1
  24. package/dist/ui/views/status_bar.js +2 -2
  25. package/dist/ui/views/status_bar.js.map +1 -1
  26. package/dist/utils/dictation.d.ts +46 -0
  27. package/dist/utils/dictation.d.ts.map +1 -0
  28. package/dist/utils/dictation.js +409 -0
  29. package/dist/utils/dictation.js.map +1 -0
  30. package/dist/utils/dictation_native.d.ts +51 -0
  31. package/dist/utils/dictation_native.d.ts.map +1 -0
  32. package/dist/utils/dictation_native.js +236 -0
  33. package/dist/utils/dictation_native.js.map +1 -0
  34. package/dist/utils/path_format.d.ts +20 -0
  35. package/dist/utils/path_format.d.ts.map +1 -0
  36. package/dist/utils/path_format.js +90 -0
  37. package/dist/utils/path_format.js.map +1 -0
  38. package/dist/utils/table.d.ts +38 -0
  39. package/dist/utils/table.d.ts.map +1 -0
  40. package/dist/utils/table.js +133 -0
  41. package/dist/utils/table.js.map +1 -0
  42. package/dist/utils/tool_trace.d.ts +7 -2
  43. package/dist/utils/tool_trace.d.ts.map +1 -1
  44. package/dist/utils/tool_trace.js +156 -51
  45. package/dist/utils/tool_trace.js.map +1 -1
  46. package/package.json +5 -1
  47. package/src/App.tsx +0 -813
  48. package/src/agent/agent.ts +0 -534
  49. package/src/agent/backends/anthropic.ts +0 -86
  50. package/src/agent/backends/gemini.ts +0 -119
  51. package/src/agent/backends/inception.ts +0 -23
  52. package/src/agent/backends/index.ts +0 -17
  53. package/src/agent/backends/openai.ts +0 -23
  54. package/src/agent/backends/openai_compatible.ts +0 -143
  55. package/src/agent/backends/types.ts +0 -83
  56. package/src/agent/commands/clipboard.ts +0 -77
  57. package/src/agent/commands/config.ts +0 -204
  58. package/src/agent/commands/debug.ts +0 -23
  59. package/src/agent/commands/emulation.ts +0 -80
  60. package/src/agent/commands/execution.ts +0 -9
  61. package/src/agent/commands/help.ts +0 -20
  62. package/src/agent/commands/history.ts +0 -13
  63. package/src/agent/commands/index.ts +0 -46
  64. package/src/agent/commands/input_mode.ts +0 -22
  65. package/src/agent/commands/keybindings.ts +0 -40
  66. package/src/agent/commands/model.ts +0 -11
  67. package/src/agent/commands/models.ts +0 -116
  68. package/src/agent/commands/permissions.ts +0 -64
  69. package/src/agent/commands/retry.ts +0 -9
  70. package/src/agent/commands/shell.ts +0 -68
  71. package/src/agent/commands/skills.ts +0 -54
  72. package/src/agent/commands/status.ts +0 -19
  73. package/src/agent/commands/types.ts +0 -80
  74. package/src/agent/commands/update.ts +0 -32
  75. package/src/agent/factory.ts +0 -60
  76. package/src/agent/index.ts +0 -20
  77. package/src/agent/runtime/capabilities.ts +0 -7
  78. package/src/agent/runtime/memory.ts +0 -23
  79. package/src/agent/runtime/policy.ts +0 -48
  80. package/src/agent/runtime/session.ts +0 -18
  81. package/src/agent/runtime/tracing.ts +0 -23
  82. package/src/agent/tools/file.ts +0 -178
  83. package/src/agent/tools/index.ts +0 -52
  84. package/src/agent/tools/screenshot.ts +0 -821
  85. package/src/agent/tools/search.ts +0 -138
  86. package/src/agent/tools/shell.ts +0 -69
  87. package/src/agent/tools/skills.ts +0 -28
  88. package/src/agent/tools/types.ts +0 -14
  89. package/src/agent/tools/zerg.ts +0 -50
  90. package/src/cli.tsx +0 -163
  91. package/src/components/ActivityLine.tsx +0 -23
  92. package/src/components/FullScreen.tsx +0 -79
  93. package/src/components/Header.tsx +0 -27
  94. package/src/components/InputArea.tsx +0 -1096
  95. package/src/components/MessageList.tsx +0 -71
  96. package/src/components/SingleMessage.tsx +0 -59
  97. package/src/components/StatusBar.tsx +0 -55
  98. package/src/components/index.tsx +0 -8
  99. package/src/config/types.ts +0 -12
  100. package/src/config.ts +0 -186
  101. package/src/debug/logger.ts +0 -14
  102. package/src/emulation/README.md +0 -24
  103. package/src/emulation/catalog.ts +0 -82
  104. package/src/emulation/trace_style.ts +0 -8
  105. package/src/emulation/types.ts +0 -7
  106. package/src/skills/index.ts +0 -36
  107. package/src/skills/loader.ts +0 -135
  108. package/src/skills/registry.ts +0 -6
  109. package/src/skills/types.ts +0 -10
  110. package/src/types.ts +0 -84
  111. package/src/ui/README.md +0 -44
  112. package/src/ui/core/factory.ts +0 -9
  113. package/src/ui/core/index.ts +0 -4
  114. package/src/ui/core/input.ts +0 -38
  115. package/src/ui/core/input_segments.ts +0 -410
  116. package/src/ui/core/input_state.ts +0 -17
  117. package/src/ui/core/layout_yoga.ts +0 -122
  118. package/src/ui/core/style.ts +0 -38
  119. package/src/ui/core/types.ts +0 -54
  120. package/src/ui/ink/index.tsx +0 -1
  121. package/src/ui/ink/render.tsx +0 -60
  122. package/src/ui/views/activity_line.ts +0 -33
  123. package/src/ui/views/app.ts +0 -111
  124. package/src/ui/views/header.ts +0 -44
  125. package/src/ui/views/input_area.ts +0 -255
  126. package/src/ui/views/message_list.ts +0 -443
  127. package/src/ui/views/status_bar.ts +0 -114
  128. package/src/ui/vue/index.ts +0 -53
  129. package/src/ui/web/frame_render.tsx +0 -148
  130. package/src/ui/web/index.tsx +0 -1
  131. package/src/ui/web/render.tsx +0 -41
  132. package/src/utils/clipboard.ts +0 -39
  133. package/src/utils/clipboard_image.ts +0 -40
  134. package/src/utils/diff.ts +0 -52
  135. package/src/utils/image_preview.ts +0 -36
  136. package/src/utils/models.ts +0 -98
  137. package/src/utils/path_complete.ts +0 -173
  138. package/src/utils/shell.ts +0 -72
  139. package/src/utils/spinner_frames.ts +0 -1
  140. package/src/utils/spinner_verbs.ts +0 -23
  141. package/src/utils/tool_summary.ts +0 -56
  142. package/src/utils/tool_trace.ts +0 -216
  143. package/src/utils/update.ts +0 -44
  144. package/src/utils/version.ts +0 -15
  145. package/src/web/index.html +0 -352
  146. package/src/web/mirror-favicon.svg +0 -4
  147. package/src/web/mirror.html +0 -641
  148. package/src/web/mirror_hook.ts +0 -25
  149. package/src/web/mirror_server.ts +0 -204
  150. package/tsconfig.json +0 -22
  151. package/vite.config.ts +0 -363
@@ -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
- }