zerg-ztc 0.1.11 → 0.1.13

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