aegiscode 3.1.10 → 3.2.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.
Files changed (209) hide show
  1. package/bin/aegis-cli-linux-x64 +0 -0
  2. package/bin/cli.js +68 -0
  3. package/package.json +11 -48
  4. package/scripts/download-binary.mjs +155 -0
  5. package/dist/main.js +0 -2171
  6. package/gui/run-gui.cjs +0 -13
  7. package/src/agent/Agent.ts +0 -903
  8. package/src/agent/SimpleAgent.ts +0 -48
  9. package/src/agent/index.ts +0 -54
  10. package/src/agent/orchestrator/AppBuilder.ts +0 -443
  11. package/src/agent/orchestrator/CouncilAgent.ts +0 -310
  12. package/src/agent/orchestrator/DiscussionRoom.ts +0 -462
  13. package/src/agent/orchestrator/OrchestratorAgent.ts +0 -637
  14. package/src/agent/orchestrator/index.ts +0 -38
  15. package/src/agent/orchestrator/utils.ts +0 -397
  16. package/src/agent/pricing.ts +0 -115
  17. package/src/agent/router.ts +0 -74
  18. package/src/agent/routerStats.ts +0 -121
  19. package/src/agent/types.ts +0 -318
  20. package/src/auth/login.ts +0 -383
  21. package/src/cli/config.ts +0 -189
  22. package/src/cli/index.ts +0 -17
  23. package/src/cli/middleware.ts +0 -119
  24. package/src/cli/types.ts +0 -75
  25. package/src/config/ConfigManager.ts +0 -587
  26. package/src/config/index.ts +0 -7
  27. package/src/config/types.ts +0 -584
  28. package/src/context/CompactionService.ts +0 -300
  29. package/src/context/ContextManager.ts +0 -450
  30. package/src/context/FileAnalyzer.ts +0 -267
  31. package/src/context/TokenCounter.ts +0 -265
  32. package/src/context/index.ts +0 -27
  33. package/src/context/storage/CacheStore.ts +0 -176
  34. package/src/context/storage/JSONLStore.ts +0 -201
  35. package/src/context/storage/MemoryStore.ts +0 -205
  36. package/src/context/storage/PersistentStore.ts +0 -327
  37. package/src/context/storage/index.ts +0 -9
  38. package/src/context/storage/pathUtils.ts +0 -114
  39. package/src/context/test.ts +0 -309
  40. package/src/context/types.ts +0 -268
  41. package/src/hooks/HookExecutor.ts +0 -434
  42. package/src/hooks/HookManager.ts +0 -596
  43. package/src/hooks/HookService.ts +0 -269
  44. package/src/hooks/Matcher.ts +0 -157
  45. package/src/hooks/index.ts +0 -63
  46. package/src/hooks/types.ts +0 -424
  47. package/src/main.tsx +0 -596
  48. package/src/mcp/HealthMonitor.ts +0 -150
  49. package/src/mcp/McpClient.ts +0 -491
  50. package/src/mcp/McpRegistry.ts +0 -321
  51. package/src/mcp/createMcpTool.ts +0 -251
  52. package/src/mcp/index.ts +0 -15
  53. package/src/mcp/server.ts +0 -334
  54. package/src/mcp/test-server.ts +0 -88
  55. package/src/mcp/test.ts +0 -372
  56. package/src/mcp/types.ts +0 -247
  57. package/src/memory/AgentMemoryBus.ts +0 -432
  58. package/src/memory/CloudSync.ts +0 -99
  59. package/src/memory/DriveSync.ts +0 -106
  60. package/src/memory/SharedMemory.ts +0 -951
  61. package/src/memory/index.ts +0 -14
  62. package/src/memory/machineFingerprint.ts +0 -40
  63. package/src/orchestrator/SubAgentMetadata.ts +0 -136
  64. package/src/prompts/builder.ts +0 -213
  65. package/src/prompts/default.ts +0 -144
  66. package/src/prompts/index.ts +0 -16
  67. package/src/prompts/plan.ts +0 -64
  68. package/src/prompts/test.ts +0 -78
  69. package/src/services/AnthropicChatService.ts +0 -341
  70. package/src/services/ChatService.ts +0 -347
  71. package/src/services/ClaudeCliChatService.ts +0 -256
  72. package/src/services/CloudSync.ts +0 -168
  73. package/src/services/CostLedger.ts +0 -211
  74. package/src/services/Heartbeat.ts +0 -135
  75. package/src/services/LearningCollector.ts +0 -291
  76. package/src/services/OllamaInstaller.ts +0 -342
  77. package/src/services/VersionChecker.ts +0 -445
  78. package/src/services/index.ts +0 -57
  79. package/src/services/streaming/OpenAIEventAdapter.ts +0 -126
  80. package/src/services/streaming/RenderingProfile.ts +0 -90
  81. package/src/services/streaming/StreamEventParser.ts +0 -181
  82. package/src/services/streaming/ThrottledRenderer.ts +0 -139
  83. package/src/services/streaming/TranscriptBuffer.ts +0 -574
  84. package/src/services/streaming/eventStatusMap.ts +0 -52
  85. package/src/services/streaming/index.ts +0 -46
  86. package/src/services/streaming/renderFormatting.ts +0 -79
  87. package/src/services/streaming/types.ts +0 -234
  88. package/src/skills/SkillLoader.ts +0 -126
  89. package/src/skills/SkillRegistry.ts +0 -366
  90. package/src/skills/index.ts +0 -48
  91. package/src/skills/types.ts +0 -146
  92. package/src/slash-commands/billing.ts +0 -70
  93. package/src/slash-commands/build.ts +0 -413
  94. package/src/slash-commands/builtinCommands.ts +0 -2733
  95. package/src/slash-commands/clone.ts +0 -242
  96. package/src/slash-commands/council.ts +0 -125
  97. package/src/slash-commands/custom/CustomCommandExecutor.ts +0 -143
  98. package/src/slash-commands/custom/CustomCommandLoader.ts +0 -251
  99. package/src/slash-commands/custom/CustomCommandRegistry.ts +0 -144
  100. package/src/slash-commands/custom/index.ts +0 -7
  101. package/src/slash-commands/debate.ts +0 -254
  102. package/src/slash-commands/gmail.ts +0 -105
  103. package/src/slash-commands/index.ts +0 -388
  104. package/src/slash-commands/mcpCommand.ts +0 -205
  105. package/src/slash-commands/types.ts +0 -201
  106. package/src/store/index.ts +0 -76
  107. package/src/store/selectors.ts +0 -246
  108. package/src/store/slices/appSlice.ts +0 -205
  109. package/src/store/slices/commandSlice.ts +0 -115
  110. package/src/store/slices/configSlice.ts +0 -46
  111. package/src/store/slices/focusSlice.ts +0 -64
  112. package/src/store/slices/index.ts +0 -9
  113. package/src/store/slices/sessionSlice.ts +0 -424
  114. package/src/store/streaming-buffer.ts +0 -425
  115. package/src/store/test.ts +0 -296
  116. package/src/store/types.ts +0 -274
  117. package/src/store/vanilla.ts +0 -186
  118. package/src/tools/builtin/bash.ts +0 -236
  119. package/src/tools/builtin/council.ts +0 -105
  120. package/src/tools/builtin/edit.ts +0 -213
  121. package/src/tools/builtin/glob.ts +0 -136
  122. package/src/tools/builtin/grep.ts +0 -263
  123. package/src/tools/builtin/index.ts +0 -61
  124. package/src/tools/builtin/memory.ts +0 -66
  125. package/src/tools/builtin/read.ts +0 -168
  126. package/src/tools/builtin/skill.ts +0 -97
  127. package/src/tools/builtin/snapshot.ts +0 -40
  128. package/src/tools/builtin/task.ts +0 -106
  129. package/src/tools/builtin/write.ts +0 -134
  130. package/src/tools/createTool.ts +0 -221
  131. package/src/tools/execution/ExecutionPipeline.ts +0 -263
  132. package/src/tools/execution/index.ts +0 -40
  133. package/src/tools/execution/stages/CacheStage.ts +0 -131
  134. package/src/tools/execution/stages/ConfirmationStage.ts +0 -203
  135. package/src/tools/execution/stages/DiscoveryStage.ts +0 -46
  136. package/src/tools/execution/stages/ExecutionStage.ts +0 -48
  137. package/src/tools/execution/stages/FormattingStage.ts +0 -44
  138. package/src/tools/execution/stages/HookStage.ts +0 -72
  139. package/src/tools/execution/stages/PermissionStage.ts +0 -287
  140. package/src/tools/execution/stages/PostHookStage.ts +0 -71
  141. package/src/tools/execution/stages/index.ts +0 -12
  142. package/src/tools/execution/test.ts +0 -266
  143. package/src/tools/execution/types.ts +0 -273
  144. package/src/tools/index.ts +0 -81
  145. package/src/tools/registry.ts +0 -304
  146. package/src/tools/schemas.ts +0 -109
  147. package/src/tools/test.ts +0 -220
  148. package/src/tools/types.ts +0 -175
  149. package/src/tools/validation/PermissionChecker.ts +0 -242
  150. package/src/tools/validation/SensitiveFileDetector.ts +0 -210
  151. package/src/tools/validation/index.ts +0 -11
  152. package/src/ui/App.tsx +0 -166
  153. package/src/ui/components/AegisInterface.tsx +0 -484
  154. package/src/ui/components/common/ChatSearch.tsx +0 -150
  155. package/src/ui/components/common/ErrorBoundary.tsx +0 -82
  156. package/src/ui/components/common/ExitMessage.tsx +0 -120
  157. package/src/ui/components/common/LoadingIndicator.tsx +0 -49
  158. package/src/ui/components/common/index.ts +0 -6
  159. package/src/ui/components/dialog/ConfirmationPrompt.tsx +0 -208
  160. package/src/ui/components/dialog/InteractiveSelector.tsx +0 -149
  161. package/src/ui/components/dialog/SetupWizard.tsx +0 -297
  162. package/src/ui/components/dialog/UpdatePrompt.tsx +0 -155
  163. package/src/ui/components/dialog/index.ts +0 -8
  164. package/src/ui/components/index.ts +0 -28
  165. package/src/ui/components/input/CommandSuggestions.tsx +0 -139
  166. package/src/ui/components/input/CustomTextInput.tsx +0 -220
  167. package/src/ui/components/input/InputArea.tsx +0 -361
  168. package/src/ui/components/input/PromptSuggestions.tsx +0 -66
  169. package/src/ui/components/input/index.ts +0 -6
  170. package/src/ui/components/layout/ChatStatusBar.tsx +0 -90
  171. package/src/ui/components/layout/ContextBar.tsx +0 -79
  172. package/src/ui/components/layout/MessageArea.tsx +0 -96
  173. package/src/ui/components/layout/MessageList.tsx +0 -647
  174. package/src/ui/components/layout/MessageSeparator.tsx +0 -26
  175. package/src/ui/components/layout/WelcomeMessage.tsx +0 -93
  176. package/src/ui/components/layout/index.ts +0 -7
  177. package/src/ui/components/markdown/CodeHighlighter.tsx +0 -292
  178. package/src/ui/components/markdown/MessageRenderer.tsx +0 -1211
  179. package/src/ui/components/markdown/index.ts +0 -8
  180. package/src/ui/components/markdown/parser.ts +0 -336
  181. package/src/ui/components/markdown/types.ts +0 -66
  182. package/src/ui/focus/FocusManager.ts +0 -137
  183. package/src/ui/focus/index.ts +0 -13
  184. package/src/ui/focus/types.ts +0 -54
  185. package/src/ui/focus/useFocus.ts +0 -75
  186. package/src/ui/hooks/index.ts +0 -11
  187. package/src/ui/hooks/useAgent.ts +0 -284
  188. package/src/ui/hooks/useCommandHistory.ts +0 -87
  189. package/src/ui/hooks/useCommandProcessor.ts +0 -443
  190. package/src/ui/hooks/useConfirmation.ts +0 -99
  191. package/src/ui/hooks/useCtrlCHandler.ts +0 -100
  192. package/src/ui/hooks/useInputBuffer.ts +0 -122
  193. package/src/ui/hooks/useTerminalSize.ts +0 -68
  194. package/src/ui/hooks/useTerminalWidth.ts +0 -5
  195. package/src/ui/hooks/useWindowedList.ts +0 -118
  196. package/src/ui/render-debugger.ts +0 -621
  197. package/src/ui/test.ts +0 -189
  198. package/src/ui/themes/ThemeManager.ts +0 -332
  199. package/src/ui/themes/aegisTheme.ts +0 -87
  200. package/src/ui/themes/darkTheme.ts +0 -87
  201. package/src/ui/themes/defaultTheme.ts +0 -85
  202. package/src/ui/themes/index.ts +0 -10
  203. package/src/ui/themes/lightTheme.ts +0 -89
  204. package/src/ui/themes/popularThemes.ts +0 -187
  205. package/src/ui/themes/types.ts +0 -130
  206. package/src/utils/clipboard.ts +0 -48
  207. package/src/utils/debug.ts +0 -43
  208. package/src/utils/environment.ts +0 -68
  209. package/src/utils/index.ts +0 -10
@@ -1,78 +0,0 @@
1
- /**
2
- *
3
- *
4
- *
5
- */
6
-
7
- import { buildSystemPrompt, getPromptStats } from './builder.js';
8
- import { getEnvironmentContext, getEnvironmentInfo } from '../utils/environment.js';
9
- import { DEFAULT_SYSTEM_PROMPT } from './default.js';
10
- import { PLAN_MODE_SYSTEM_PROMPT, createPlanModeReminder } from './plan.js';
11
-
12
- async function testPromptSystem() {
13
- console.log('=== 提示词系统测试 ===\n');
14
-
15
- // 1. 测试环境上下
16
- console.log('1. 环境信息:');
17
- const envInfo = getEnvironmentInfo();
18
- console.log(` 工作目录: ${envInfo.workingDirectory}`);
19
- console.log(` 平台: ${envInfo.platform}`);
20
- console.log(` 日期: ${envInfo.currentDate}`);
21
- console.log();
22
-
23
- // 2. 测试环境上下文生
24
- console.log('2. 环境上下文 (前 300 字符):');
25
- const envContext = getEnvironmentContext();
26
- console.log(envContext.slice(0, 300) + '...\n');
27
-
28
- // 3. 测试默认提示
29
- console.log('3. 默认提示词长度:', DEFAULT_SYSTEM_PROMPT.length, '字符');
30
- console.log(' 包含关键词:');
31
- console.log(' - AEGIS:', DEFAULT_SYSTEM_PROMPT.includes('AEGIS') ? '✅' : '❌');
32
- console.log(' - 4 lines:', DEFAULT_SYSTEM_PROMPT.includes('4 lines') ? '✅' : '❌');
33
- console.log(' - Chinese:', DEFAULT_SYSTEM_PROMPT.includes('Chinese') ? '✅' : '❌');
34
- console.log();
35
-
36
- // 4. 测试 Plan 模式提示
37
- console.log('4. Plan 模式提示词长度:', PLAN_MODE_SYSTEM_PROMPT.length, '字符');
38
- console.log(' 包含关键词:');
39
- console.log(' - PLAN MODE:', PLAN_MODE_SYSTEM_PROMPT.includes('PLAN MODE') ? '✅' : '❌');
40
- console.log(' - Read-only:', PLAN_MODE_SYSTEM_PROMPT.includes('Read-only') ? '✅' : '❌');
41
- console.log();
42
-
43
- // 5. 测试消息提醒注
44
- console.log('5. Plan 模式消息注入:');
45
- const reminder = createPlanModeReminder('帮我分析这段代码');
46
- console.log(' ' + reminder.slice(0, 100) + '...\n');
47
-
48
- // 6. 测试完整提示词构建(默认模
49
- console.log('6. 构建完整提示词(默认模式):');
50
- const defaultResult = await buildSystemPrompt({
51
- projectPath: process.cwd(),
52
- includeEnvironment: true,
53
- });
54
- console.log(getPromptStats(defaultResult));
55
- console.log();
56
-
57
- // 7. 测试 Plan 模式提示词构
58
- console.log('7. 构建完整提示词(Plan 模式):');
59
- const planResult = await buildSystemPrompt({
60
- projectPath: process.cwd(),
61
- mode: 'plan',
62
- includeEnvironment: true,
63
- });
64
- console.log(getPromptStats(planResult));
65
- console.log();
66
-
67
- // 8. 测试自定义追加内
68
- console.log('8. 构建带追加内容的提示词:');
69
- const appendResult = await buildSystemPrompt({
70
- append: '额外指令:始终使用 TypeScript',
71
- });
72
- console.log(getPromptStats(appendResult));
73
- console.log();
74
-
75
- console.log('=== 测试完成 ===');
76
- }
77
-
78
- testPromptSystem().catch(console.error);
@@ -1,341 +0,0 @@
1
- /**
2
- * AnthropicChatService — native Anthropic Messages API transport.
3
- *
4
- * The OpenAI-compatible shim (ChatService.ts) cannot carry `cache_control`,
5
- * `thinking`, or citations — Anthropic's OpenAI-compatibility layer silently
6
- * drops all three. This talks directly to /v1/messages over raw SSE so those
7
- * features actually work. Used only when the model is Anthropic and the key
8
- * isn't a Claude-CLI OAuth token (that path stays on ClaudeCliChatService).
9
- *
10
- * The wire-level stream events (content_block_start/delta/stop, message_*)
11
- * are forwarded as-is via onStreamEvent — AnthropicStreamEvent already
12
- * mirrors this exact shape, so no adapter layer is needed (contrast with
13
- * OpenAIEventAdapter, which exists only to translate other providers' shapes
14
- * into this one).
15
- */
16
-
17
- import type {
18
- Message,
19
- ToolDefinition,
20
- ToolCall,
21
- ChatResponse,
22
- IChatService,
23
- StreamCallbacks,
24
- } from '../agent/types.js';
25
- import type { ChatServiceConfig } from './ChatService.js';
26
- import type { AnthropicStreamEvent } from './streaming/types.js';
27
-
28
- const EFFORT_LEVELS = new Set(['low', 'medium', 'high']);
29
- /** 'max' maps to a fixed budget_tokens ceiling when sent via extended thinking. */
30
- const MAX_BUDGET_TOKENS = 128000;
31
-
32
- /** Effort + adaptive thinking are unsupported on Haiku (errors per Anthropic API). */
33
- function modelSupportsThinking(model: string): boolean {
34
- return !/haiku/i.test(model);
35
- }
36
-
37
- type AnthropicContentBlock = Record<string, unknown>;
38
- type AnthropicMessage = { role: 'user' | 'assistant'; content: AnthropicContentBlock[] };
39
-
40
- /**
41
- * Convert aegiscode's flat Message[] (OpenAI-shaped: system/user/assistant/tool
42
- * roles, tool_calls, tool_call_id) into Anthropic's content-block format:
43
- * system goes to a top-level field, tool results fold into 'user' messages,
44
- * and tool_use blocks live inside 'assistant' content.
45
- */
46
- function buildAnthropicMessages(messages: Message[]): { systemText: string; anthropicMessages: AnthropicMessage[] } {
47
- let systemText = '';
48
- const anthropicMessages: AnthropicMessage[] = [];
49
-
50
- // Tool names by call id — needed to mark Read-tool results as citable documents.
51
- const toolNameById = new Map<string, string>();
52
-
53
- // Consecutive 'tool' messages (parallel tool calls) collapse into one user message.
54
- let pendingToolResults: AnthropicContentBlock[] | null = null;
55
- const flushToolResults = () => {
56
- if (pendingToolResults && pendingToolResults.length > 0) {
57
- anthropicMessages.push({ role: 'user', content: pendingToolResults });
58
- }
59
- pendingToolResults = null;
60
- };
61
-
62
- for (const msg of messages) {
63
- if (msg.role === 'system') {
64
- systemText += (systemText ? '\n\n' : '') + msg.content;
65
- continue;
66
- }
67
-
68
- if (msg.role === 'tool') {
69
- if (!pendingToolResults) pendingToolResults = [];
70
- const toolName = msg.name || toolNameById.get(msg.tool_call_id || '') || '';
71
- const resultContent: AnthropicContentBlock = toolName === 'Read'
72
- ? {
73
- type: 'document',
74
- source: { type: 'text', media_type: 'text/plain', data: msg.content },
75
- citations: { enabled: true },
76
- }
77
- : { type: 'text', text: msg.content };
78
- pendingToolResults.push({
79
- type: 'tool_result',
80
- tool_use_id: msg.tool_call_id,
81
- content: [resultContent],
82
- });
83
- continue;
84
- }
85
-
86
- flushToolResults();
87
-
88
- if (msg.role === 'user') {
89
- anthropicMessages.push({ role: 'user', content: [{ type: 'text', text: msg.content }] });
90
- continue;
91
- }
92
-
93
- if (msg.role === 'assistant') {
94
- const content: AnthropicContentBlock[] = [];
95
- if (msg.content) content.push({ type: 'text', text: msg.content });
96
- for (const tc of msg.tool_calls || []) {
97
- toolNameById.set(tc.id, tc.function.name);
98
- let input: unknown = {};
99
- try {
100
- input = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
101
- } catch {
102
- input = {};
103
- }
104
- content.push({ type: 'tool_use', id: tc.id, name: tc.function.name, input });
105
- }
106
- anthropicMessages.push({ role: 'assistant', content });
107
- continue;
108
- }
109
- }
110
- flushToolResults();
111
-
112
- return { systemText, anthropicMessages };
113
- }
114
-
115
- export class AnthropicChatService implements IChatService {
116
- private apiKey: string;
117
- private baseURL: string;
118
- private model: string;
119
- private timeout: number;
120
- private maxRetries: number;
121
- private thinkingBudget: 'off' | 'low' | 'medium' | 'high' | 'max';
122
- private maxOutputTokens: number;
123
-
124
- constructor(config: ChatServiceConfig) {
125
- this.apiKey = config.apiKey;
126
- this.baseURL = (config.baseURL || 'https://api.anthropic.com/v1').replace(/\/$/, '');
127
- this.model = config.model || 'claude-sonnet-4-6';
128
- this.timeout = config.timeout ?? 60000;
129
- this.maxRetries = config.maxRetries ?? 2;
130
- this.thinkingBudget = config.thinkingBudget || 'off';
131
- this.maxOutputTokens = config.maxOutputTokens || 16384;
132
- }
133
-
134
- async chat(
135
- messages: Message[],
136
- tools?: ToolDefinition[],
137
- signal?: AbortSignal,
138
- streamCallbacks?: StreamCallbacks,
139
- attempt = 0,
140
- ): Promise<ChatResponse> {
141
- const { systemText, anthropicMessages } = buildAnthropicMessages(messages);
142
-
143
- // Cache the conversation-history prefix: mark the last content block of the
144
- // second-to-last message so everything before the newest turn can hit cache
145
- // instead of being reprocessed at full price/latency every turn.
146
- if (anthropicMessages.length >= 2) {
147
- const prefixMessage = anthropicMessages[anthropicMessages.length - 2];
148
- const lastBlock = prefixMessage.content[prefixMessage.content.length - 1];
149
- if (lastBlock) {
150
- (lastBlock as Record<string, unknown>).cache_control = { type: 'ephemeral' };
151
- }
152
- }
153
-
154
- const body: Record<string, unknown> = {
155
- model: this.model,
156
- max_tokens: this.maxOutputTokens,
157
- system: [{ type: 'text', text: systemText, cache_control: { type: 'ephemeral' } }],
158
- messages: anthropicMessages,
159
- stream: true,
160
- };
161
-
162
- if (tools && tools.length > 0) {
163
- const anthropicTools = tools.map(t => ({
164
- name: t.function.name,
165
- description: t.function.description,
166
- input_schema: t.function.parameters,
167
- })) as Array<Record<string, unknown>>;
168
- (anthropicTools[anthropicTools.length - 1] as Record<string, unknown>).cache_control = { type: 'ephemeral' };
169
- body.tools = anthropicTools;
170
- }
171
-
172
- if (this.thinkingBudget !== 'off' && modelSupportsThinking(this.model)) {
173
- if (this.thinkingBudget === 'max') {
174
- // 'max' is not a valid Anthropic effort value — use token-based approach instead
175
- body.thinking = { type: 'enabled', budget_tokens: MAX_BUDGET_TOKENS };
176
- } else if (EFFORT_LEVELS.has(this.thinkingBudget)) {
177
- body.thinking = { type: 'adaptive' };
178
- body.output_config = { effort: this.thinkingBudget };
179
- }
180
- }
181
-
182
- let content = '';
183
- let reasoningContent = '';
184
- let usage: ChatResponse['usage'] | undefined;
185
-
186
- try {
187
- const response = await fetch(`${this.baseURL}/messages`, {
188
- method: 'POST',
189
- signal,
190
- headers: {
191
- 'x-api-key': this.apiKey,
192
- 'anthropic-version': '2023-06-01',
193
- 'content-type': 'application/json',
194
- },
195
- body: JSON.stringify(body),
196
- });
197
-
198
- if (!response.ok || !response.body) {
199
- const text = await response.text().catch(() => '');
200
- const err: Error & { status?: number } = new Error(
201
- `Anthropic API error (${response.status}): ${text || response.statusText}`,
202
- );
203
- err.status = response.status;
204
- throw err;
205
- }
206
-
207
- const toolBlocks = new Map<number, { id: string; name: string; arguments: string }>();
208
- const citations: Array<{ text: string; title: string }> = [];
209
-
210
- const reader = response.body.getReader();
211
- const decoder = new TextDecoder();
212
- let buffer = '';
213
-
214
- while (true) {
215
- const { done, value } = await reader.read();
216
- if (done) break;
217
- buffer += decoder.decode(value, { stream: true });
218
-
219
- let sepIdx: number;
220
- while ((sepIdx = buffer.indexOf('\n\n')) !== -1) {
221
- const rawEvent = buffer.slice(0, sepIdx);
222
- buffer = buffer.slice(sepIdx + 2);
223
-
224
- const dataLine = rawEvent.split('\n').find(l => l.startsWith('data:'));
225
- if (!dataLine) continue;
226
- const json = dataLine.slice(5).trim();
227
- if (!json) continue;
228
-
229
- let event: AnthropicStreamEvent;
230
- try {
231
- event = JSON.parse(json);
232
- } catch {
233
- continue;
234
- }
235
-
236
- streamCallbacks?.onStreamEvent?.(event);
237
-
238
- if (event.type === 'content_block_start') {
239
- const block = event.content_block;
240
- if (block.type === 'tool_use') {
241
- toolBlocks.set(event.index, { id: block.id, name: block.name, arguments: '' });
242
- streamCallbacks?.onToolCallStart?.({
243
- id: block.id,
244
- type: 'function',
245
- function: { name: block.name, arguments: '' },
246
- });
247
- }
248
- } else if (event.type === 'content_block_delta') {
249
- const delta = event.delta;
250
- if (delta.type === 'text_delta') {
251
- content += delta.text;
252
- streamCallbacks?.onContentDelta?.(delta.text);
253
- } else if (delta.type === 'thinking_delta') {
254
- reasoningContent += delta.thinking;
255
- streamCallbacks?.onThinkingDelta?.(delta.thinking);
256
- } else if (delta.type === 'input_json_delta') {
257
- const tb = toolBlocks.get(event.index);
258
- if (tb) {
259
- tb.arguments += delta.partial_json;
260
- streamCallbacks?.onToolCallDelta?.(tb.id, delta.partial_json);
261
- }
262
- } else if (delta.type === 'citations_delta') {
263
- citations.push({ text: delta.citation.cited_text, title: delta.citation.document_title || 'source' });
264
- }
265
- } else if (event.type === 'message_start') {
266
- const u = event.message.usage as { input_tokens?: number } | undefined;
267
- if (u) {
268
- usage = {
269
- promptTokens: u.input_tokens || 0,
270
- completionTokens: 0,
271
- totalTokens: u.input_tokens || 0,
272
- };
273
- }
274
- } else if (event.type === 'message_delta') {
275
- const u = event.usage as { output_tokens?: number } | undefined;
276
- if (u && usage) {
277
- usage.completionTokens = u.output_tokens || 0;
278
- usage.totalTokens = usage.promptTokens + usage.completionTokens;
279
- }
280
- } else if (event.type === 'error') {
281
- throw new Error(event.error?.message || 'Anthropic stream error');
282
- }
283
- }
284
- }
285
-
286
- // Surface citations as a compact footer — reuses the existing text-rendering
287
- // path instead of requiring new UI plumbing through the transcript buffer.
288
- if (citations.length > 0) {
289
- const seen = new Set<string>();
290
- const lines: string[] = [];
291
- for (const c of citations) {
292
- const key = `${c.title}:${c.text}`;
293
- if (seen.has(key)) continue;
294
- seen.add(key);
295
- const snippet = c.text.length > 80 ? c.text.slice(0, 77) + '...' : c.text;
296
- lines.push(` ${c.title}: "${snippet}"`);
297
- }
298
- const footer = `\n\n— Cited:\n${lines.join('\n')}`;
299
- content += footer;
300
- streamCallbacks?.onContentDelta?.(footer);
301
- }
302
-
303
- const result: ChatResponse = { content, reasoningContent: reasoningContent || undefined, usage };
304
- const toolCalls: ToolCall[] = Array.from(toolBlocks.values()).map(tb => ({
305
- id: tb.id,
306
- type: 'function' as const,
307
- function: { name: tb.name, arguments: tb.arguments },
308
- }));
309
- if (toolCalls.length > 0) result.toolCalls = toolCalls;
310
- return result;
311
- } catch (error) {
312
- if (error instanceof Error && error.name === 'AbortError') throw error;
313
-
314
- const status = (error as { status?: number }).status;
315
- const isTransient = status !== undefined && [429, 500, 502, 503, 504].includes(status);
316
-
317
- if (isTransient) {
318
- if (content.length > 0) {
319
- return { content, reasoningContent: reasoningContent || undefined };
320
- }
321
- const MAX_RETRIES = 5;
322
- if (attempt >= MAX_RETRIES) {
323
- const reason = status === 429 ? 'Rate limited' : `Server error (${status})`;
324
- throw new Error(
325
- `${reason} — gave up after ${MAX_RETRIES} retries.\n` +
326
- (status === 429
327
- ? `Your account hit Anthropic's rate limit. Wait a few minutes and try again,\n` +
328
- `or check usage at https://claude.ai/settings/usage`
329
- : (error as Error).message),
330
- );
331
- }
332
- const delay = Math.min(1000 * 2 ** attempt, 16000);
333
- process.stderr.write(`[RETRY] attempt ${attempt + 1}/${MAX_RETRIES} after ${delay}ms\n`);
334
- await new Promise(r => setTimeout(r, delay));
335
- return this.chat(messages, tools, signal, streamCallbacks, attempt + 1);
336
- }
337
-
338
- throw error;
339
- }
340
- }
341
- }