@ebowwa/coder 0.7.64 → 0.7.65
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/dist/index.js +36168 -32
- package/dist/interfaces/ui/terminal/cli/index.js +34253 -158
- package/dist/interfaces/ui/terminal/native/README.md +53 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
- package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
- package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
- package/dist/interfaces/ui/terminal/native/index.js +43 -0
- package/dist/interfaces/ui/terminal/native/index.node +0 -0
- package/dist/interfaces/ui/terminal/native/package.json +34 -0
- package/dist/native/README.md +53 -0
- package/dist/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/native/claude_code_native.dylib +0 -0
- package/dist/native/index.d.ts +0 -480
- package/dist/native/index.darwin-arm64.node +0 -0
- package/dist/native/index.js +43 -1625
- package/dist/native/index.node +0 -0
- package/dist/native/package.json +34 -0
- package/native/index.darwin-arm64.node +0 -0
- package/native/index.js +33 -19
- package/package.json +3 -2
- package/packages/src/core/agent-loop/__tests__/compaction.test.ts +17 -14
- package/packages/src/core/agent-loop/compaction.ts +6 -2
- package/packages/src/core/agent-loop/index.ts +2 -0
- package/packages/src/core/agent-loop/loop-state.ts +1 -1
- package/packages/src/core/agent-loop/turn-executor.ts +4 -0
- package/packages/src/core/agent-loop/types.ts +4 -0
- package/packages/src/core/api-client-impl.ts +283 -173
- package/packages/src/core/cognitive-security/hooks.ts +2 -1
- package/packages/src/core/config/todo +7 -0
- package/packages/src/core/context/__tests__/integration.test.ts +334 -0
- package/packages/src/core/context/compaction.ts +170 -0
- package/packages/src/core/context/constants.ts +58 -0
- package/packages/src/core/context/extraction.ts +85 -0
- package/packages/src/core/context/index.ts +66 -0
- package/packages/src/core/context/summarization.ts +251 -0
- package/packages/src/core/context/token-estimation.ts +98 -0
- package/packages/src/core/context/types.ts +59 -0
- package/packages/src/core/models.ts +81 -4
- package/packages/src/core/normalizers/todo +5 -1
- package/packages/src/core/providers/README.md +230 -0
- package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
- package/packages/src/core/providers/index.ts +419 -0
- package/packages/src/core/providers/types.ts +132 -0
- package/packages/src/core/retry.ts +10 -0
- package/packages/src/ecosystem/tools/index.ts +174 -0
- package/packages/src/index.ts +23 -2
- package/packages/src/interfaces/ui/index.ts +17 -20
- package/packages/src/interfaces/ui/spinner.ts +2 -2
- package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
- package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
- package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
- package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
- package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
- package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
- package/packages/src/interfaces/ui/terminal/cli/index.ts +200 -13
- package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +393 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
- package/packages/src/interfaces/ui/terminal/shared/index.ts +13 -0
- package/packages/src/interfaces/ui/terminal/shared/query.ts +9 -3
- package/packages/src/interfaces/ui/terminal/shared/setup.ts +5 -1
- package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
- package/packages/src/interfaces/ui/terminal/shared/status-line.ts +10 -2
- package/packages/src/native/index.ts +404 -27
- package/packages/src/native/tui_v2_types.ts +39 -0
- package/packages/src/teammates/coordination.test.ts +279 -0
- package/packages/src/teammates/coordination.ts +646 -0
- package/packages/src/teammates/index.ts +95 -25
- package/packages/src/teammates/integration.test.ts +272 -0
- package/packages/src/teammates/runner.test.ts +235 -0
- package/packages/src/teammates/runner.ts +750 -0
- package/packages/src/teammates/schemas.ts +673 -0
- package/packages/src/types/index.ts +1 -0
- package/packages/src/core/context-compaction.ts +0 -578
- package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
- package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
- package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +0 -262
- package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +0 -232
- package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +0 -62
- package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +0 -537
- package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +0 -107
- package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +0 -240
- package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +0 -54
- package/packages/src/interfaces/ui/terminal/tui/commands.ts +0 -438
- package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +0 -584
- package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +0 -614
- package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +0 -333
- package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +0 -604
- package/packages/src/interfaces/ui/terminal/tui/components/index.ts +0 -118
- package/packages/src/interfaces/ui/terminal/tui/console.ts +0 -49
- package/packages/src/interfaces/ui/terminal/tui/index.ts +0 -90
- package/packages/src/interfaces/ui/terminal/tui/run.tsx +0 -42
- package/packages/src/interfaces/ui/terminal/tui/spinner.ts +0 -69
- package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +0 -390
- package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +0 -422
- package/packages/src/interfaces/ui/terminal/tui/types.ts +0 -186
- package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +0 -104
- package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +0 -239
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API Client - SSE streaming for LLM APIs
|
|
3
|
+
*
|
|
4
|
+
* Supports multiple providers:
|
|
5
|
+
* - Zhipu (Z.AI / GLM models) - OpenAI format
|
|
6
|
+
* - MiniMax (M2.5) - Anthropic format
|
|
7
|
+
* - OpenAI (future)
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
import type {
|
|
@@ -33,6 +38,30 @@ import {
|
|
|
33
38
|
DEFAULT_MODEL,
|
|
34
39
|
supportsExtendedThinking,
|
|
35
40
|
} from "./models.js";
|
|
41
|
+
import {
|
|
42
|
+
resolveProvider,
|
|
43
|
+
getProviderForModel,
|
|
44
|
+
recordProviderSuccess,
|
|
45
|
+
recordProviderFailure,
|
|
46
|
+
type ProviderName,
|
|
47
|
+
type ProviderConfig,
|
|
48
|
+
} from "./providers/index.js";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert Anthropic-style tools to OpenAI-style tools
|
|
52
|
+
* Anthropic: { name, description, input_schema }
|
|
53
|
+
* OpenAI: { type: "function", function: { name, description, parameters } }
|
|
54
|
+
*/
|
|
55
|
+
function convertToolsToOpenAIFormat(tools: APITool[]): unknown[] {
|
|
56
|
+
return tools.map((tool) => ({
|
|
57
|
+
type: "function",
|
|
58
|
+
function: {
|
|
59
|
+
name: tool.name,
|
|
60
|
+
description: tool.description,
|
|
61
|
+
parameters: tool.input_schema,
|
|
62
|
+
},
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
36
65
|
|
|
37
66
|
export interface StreamOptions {
|
|
38
67
|
apiKey: string;
|
|
@@ -50,6 +79,8 @@ export interface StreamOptions {
|
|
|
50
79
|
/** Called when redacted thinking is received (data is base64) */
|
|
51
80
|
onRedactedThinking?: (data: string) => void;
|
|
52
81
|
onToolUse?: (toolUse: { id: string; name: string; input: unknown }) => void;
|
|
82
|
+
/** Called when a retry is about to start - UI should reset streaming state */
|
|
83
|
+
onRetryStart?: () => void;
|
|
53
84
|
signal?: AbortSignal;
|
|
54
85
|
}
|
|
55
86
|
|
|
@@ -197,153 +228,55 @@ export function calculateCacheMetrics(usage: UsageMetrics): CacheMetrics {
|
|
|
197
228
|
}
|
|
198
229
|
|
|
199
230
|
/**
|
|
200
|
-
*
|
|
231
|
+
* Callbacks to emit during streaming (passed in, not buffered)
|
|
201
232
|
*/
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
maxTokens = 4096,
|
|
210
|
-
tools,
|
|
211
|
-
systemPrompt,
|
|
212
|
-
cacheConfig = DEFAULT_CACHE_CONFIG,
|
|
213
|
-
thinking,
|
|
214
|
-
extendedThinking,
|
|
215
|
-
onToken,
|
|
216
|
-
onThinking,
|
|
217
|
-
onRedactedThinking,
|
|
218
|
-
onToolUse,
|
|
219
|
-
signal,
|
|
220
|
-
} = options;
|
|
221
|
-
|
|
222
|
-
const startTime = Date.now();
|
|
223
|
-
let ttft = 0;
|
|
224
|
-
let firstToken = true;
|
|
225
|
-
let totalThinkingTokens = 0;
|
|
226
|
-
|
|
227
|
-
// Build cached messages
|
|
228
|
-
const cachedMessages = buildCachedMessages(messages, cacheConfig);
|
|
229
|
-
|
|
230
|
-
// Build system prompt with cache control
|
|
231
|
-
const cachedSystemPrompt = buildSystemPrompt(systemPrompt, cacheConfig);
|
|
232
|
-
|
|
233
|
-
// Build request
|
|
234
|
-
const request: APIRequest = {
|
|
235
|
-
model,
|
|
236
|
-
max_tokens: maxTokens,
|
|
237
|
-
messages: cachedMessages.map((m) => ({
|
|
238
|
-
role: m.role,
|
|
239
|
-
content: m.content,
|
|
240
|
-
})),
|
|
241
|
-
stream: true,
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Add system prompt if provided
|
|
245
|
-
if (cachedSystemPrompt) {
|
|
246
|
-
request.system = cachedSystemPrompt;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Add tools if provided (with optional caching)
|
|
250
|
-
if (tools && tools.length > 0) {
|
|
251
|
-
request.tools = tools;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Determine API endpoint (support custom base URL for GLM, etc.)
|
|
255
|
-
const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
|
|
256
|
-
const apiEndpoint = `${baseUrl}/v1/messages`;
|
|
257
|
-
|
|
258
|
-
// Build headers
|
|
259
|
-
const headers: Record<string, string> = {
|
|
260
|
-
"Content-Type": "application/json",
|
|
261
|
-
"x-api-key": apiKey,
|
|
262
|
-
"anthropic-version": "2023-06-01",
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
// Determine thinking configuration
|
|
266
|
-
const shouldUseExtendedThinking =
|
|
267
|
-
(extendedThinking?.enabled ?? false) ||
|
|
268
|
-
(thinking && thinking.type !== "disabled");
|
|
269
|
-
|
|
270
|
-
if (shouldUseExtendedThinking && supportsExtendedThinking(model)) {
|
|
271
|
-
// Calculate budget tokens
|
|
272
|
-
let budgetTokens: number;
|
|
273
|
-
|
|
274
|
-
if (extendedThinking?.budgetTokens) {
|
|
275
|
-
budgetTokens = extendedThinking.budgetTokens;
|
|
276
|
-
} else if (thinking?.type === "enabled") {
|
|
277
|
-
budgetTokens = thinking.budget_tokens;
|
|
278
|
-
} else {
|
|
279
|
-
// Use effort level to determine budget
|
|
280
|
-
const effort = extendedThinking?.effort || "medium";
|
|
281
|
-
budgetTokens = calculateBudgetTokens(
|
|
282
|
-
{
|
|
283
|
-
enabled: true,
|
|
284
|
-
effort,
|
|
285
|
-
modelMultiplier: model.includes("opus") ? 2 : 1,
|
|
286
|
-
},
|
|
287
|
-
model
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Clamp budget to valid range
|
|
292
|
-
budgetTokens = Math.max(1024, Math.min(budgetTokens, 100000));
|
|
293
|
-
|
|
294
|
-
request.thinking = {
|
|
295
|
-
type: "enabled",
|
|
296
|
-
budget_tokens: budgetTokens,
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
// Add beta headers for extended thinking features
|
|
300
|
-
const betaFeatures: string[] = ["extended-thinking-2025-01-24"];
|
|
301
|
-
|
|
302
|
-
// Add interleaved thinking support if enabled
|
|
303
|
-
if (extendedThinking?.interleaved !== false) {
|
|
304
|
-
betaFeatures.push("interleaved-thinking-2025-01-24");
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
headers["anthropic-beta"] = betaFeatures.join(",");
|
|
308
|
-
} else {
|
|
309
|
-
// Default beta header
|
|
310
|
-
headers["anthropic-beta"] = "max-tokens-3-5-sonnet-2024-07-15";
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Make API request with retry logic
|
|
314
|
-
const retryOptions: RetryOptions = {
|
|
315
|
-
maxRetries: 3,
|
|
316
|
-
baseDelayMs: 1000,
|
|
317
|
-
maxDelayMs: 30000,
|
|
318
|
-
retryableStatusCodes: [429, 500, 502, 503, 504, 529],
|
|
319
|
-
onRetry: (attempt, error, delayMs) => {
|
|
320
|
-
console.log(`\x1b[33mAPI retry ${attempt}/3 after ${delayMs}ms: ${error.message}\x1b[0m`);
|
|
321
|
-
},
|
|
322
|
-
};
|
|
233
|
+
interface StreamCallbacks {
|
|
234
|
+
onToken?: (text: string) => void;
|
|
235
|
+
onThinking?: (thinking: string) => void;
|
|
236
|
+
onRedactedThinking?: (data: string) => void;
|
|
237
|
+
onToolUse?: (toolUse: { id: string; name: string; input: unknown }) => void;
|
|
238
|
+
onRetryStart?: () => void;
|
|
239
|
+
}
|
|
323
240
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (!res.ok && retryOptions.retryableStatusCodes?.includes(res.status)) {
|
|
335
|
-
const errorText = await res.text();
|
|
336
|
-
throw new Error(`API error: ${res.status} - ${errorText}`);
|
|
337
|
-
}
|
|
241
|
+
/**
|
|
242
|
+
* Internal result from a single stream attempt
|
|
243
|
+
*/
|
|
244
|
+
interface StreamAttemptResult {
|
|
245
|
+
message: APIResponse | null;
|
|
246
|
+
content: ContentBlock[];
|
|
247
|
+
usage: UsageMetrics;
|
|
248
|
+
thinkingTokens: number;
|
|
249
|
+
ttftMs: number;
|
|
250
|
+
}
|
|
338
251
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Execute a single streaming API attempt
|
|
254
|
+
* Emits callbacks in real-time for streaming display
|
|
255
|
+
*/
|
|
256
|
+
async function executeStreamAttempt(
|
|
257
|
+
request: APIRequest,
|
|
258
|
+
headers: Record<string, string>,
|
|
259
|
+
apiEndpoint: string,
|
|
260
|
+
signal: AbortSignal | undefined,
|
|
261
|
+
model: string,
|
|
262
|
+
retryableStatusCodes: number[],
|
|
263
|
+
startTime: number,
|
|
264
|
+
callbacks: StreamCallbacks
|
|
265
|
+
): Promise<StreamAttemptResult> {
|
|
266
|
+
const response = await fetch(apiEndpoint, {
|
|
267
|
+
method: "POST",
|
|
268
|
+
headers,
|
|
269
|
+
body: JSON.stringify(request),
|
|
270
|
+
signal,
|
|
271
|
+
});
|
|
343
272
|
|
|
273
|
+
// Throw for retryable status codes so withRetry can handle them
|
|
344
274
|
if (!response.ok) {
|
|
345
|
-
const
|
|
346
|
-
|
|
275
|
+
const errorText = await response.text();
|
|
276
|
+
if (retryableStatusCodes.includes(response.status)) {
|
|
277
|
+
throw new Error(`API error: ${response.status} - ${errorText}`);
|
|
278
|
+
}
|
|
279
|
+
throw new Error(`API error: ${response.status} - ${errorText}`);
|
|
347
280
|
}
|
|
348
281
|
|
|
349
282
|
if (!response.body) {
|
|
@@ -363,7 +296,9 @@ export async function createMessageStream(
|
|
|
363
296
|
let currentToolUseBlock: ToolUseBlock | null = null;
|
|
364
297
|
let toolUseInput = "";
|
|
365
298
|
|
|
366
|
-
|
|
299
|
+
let ttft = 0;
|
|
300
|
+
let firstToken = true;
|
|
301
|
+
let totalThinkingTokens = 0;
|
|
367
302
|
|
|
368
303
|
try {
|
|
369
304
|
let buffer = "";
|
|
@@ -440,7 +375,7 @@ export async function createMessageStream(
|
|
|
440
375
|
if (delta.type === "text_delta" && currentTextBlock) {
|
|
441
376
|
const text = delta.text as string;
|
|
442
377
|
currentTextBlock.text += text;
|
|
443
|
-
onToken?.(text);
|
|
378
|
+
callbacks.onToken?.(text); // Emit in real-time
|
|
444
379
|
|
|
445
380
|
if (firstToken) {
|
|
446
381
|
ttft = Date.now() - startTime;
|
|
@@ -449,14 +384,13 @@ export async function createMessageStream(
|
|
|
449
384
|
} else if (delta.type === "thinking_delta" && currentThinkingBlock) {
|
|
450
385
|
const thinking = delta.thinking as string;
|
|
451
386
|
currentThinkingBlock.thinking += thinking;
|
|
452
|
-
onThinking?.(thinking);
|
|
453
|
-
totalThinkingTokens += Math.ceil(thinking.length / 4);
|
|
387
|
+
callbacks.onThinking?.(thinking); // Emit in real-time
|
|
388
|
+
totalThinkingTokens += Math.ceil(thinking.length / 4);
|
|
454
389
|
} else if (delta.type === "redacted_thinking_delta" && currentRedactedThinkingBlock) {
|
|
455
|
-
// Handle redacted thinking deltas
|
|
456
390
|
const redactedData = delta.data as string;
|
|
457
391
|
currentRedactedThinkingBlock.data += redactedData;
|
|
458
|
-
onRedactedThinking?.(redactedData);
|
|
459
|
-
totalThinkingTokens += Math.ceil(redactedData.length / 4);
|
|
392
|
+
callbacks.onRedactedThinking?.(redactedData); // Emit in real-time
|
|
393
|
+
totalThinkingTokens += Math.ceil(redactedData.length / 4);
|
|
460
394
|
} else if (delta.type === "input_json_delta" && currentToolUseBlock) {
|
|
461
395
|
toolUseInput += delta.partial_json as string;
|
|
462
396
|
}
|
|
@@ -464,8 +398,6 @@ export async function createMessageStream(
|
|
|
464
398
|
}
|
|
465
399
|
|
|
466
400
|
case "content_block_stop": {
|
|
467
|
-
// content_block_stop event has { index: number }, not the block itself
|
|
468
|
-
// We need to check which current block is active and push it
|
|
469
401
|
if (currentTextBlock !== null) {
|
|
470
402
|
currentContent.push(currentTextBlock);
|
|
471
403
|
currentTextBlock = null;
|
|
@@ -474,7 +406,6 @@ export async function createMessageStream(
|
|
|
474
406
|
currentThinkingBlock = null;
|
|
475
407
|
} else if (currentRedactedThinkingBlock !== null) {
|
|
476
408
|
currentContent.push(currentRedactedThinkingBlock);
|
|
477
|
-
onRedactedThinking?.(currentRedactedThinkingBlock.data);
|
|
478
409
|
currentRedactedThinkingBlock = null;
|
|
479
410
|
} else if (currentToolUseBlock !== null) {
|
|
480
411
|
try {
|
|
@@ -483,11 +414,11 @@ export async function createMessageStream(
|
|
|
483
414
|
currentToolUseBlock.input = {};
|
|
484
415
|
}
|
|
485
416
|
currentContent.push(currentToolUseBlock);
|
|
486
|
-
onToolUse?.({
|
|
417
|
+
callbacks.onToolUse?.({
|
|
487
418
|
id: currentToolUseBlock.id,
|
|
488
419
|
name: currentToolUseBlock.name,
|
|
489
420
|
input: currentToolUseBlock.input,
|
|
490
|
-
});
|
|
421
|
+
}); // Emit in real-time
|
|
491
422
|
currentToolUseBlock = null;
|
|
492
423
|
toolUseInput = "";
|
|
493
424
|
}
|
|
@@ -506,13 +437,10 @@ export async function createMessageStream(
|
|
|
506
437
|
}
|
|
507
438
|
|
|
508
439
|
case "message_stop":
|
|
509
|
-
// Message complete
|
|
510
440
|
break;
|
|
511
441
|
|
|
512
442
|
// OpenAI/Z.AI compatible format (for GLM-5, etc.)
|
|
513
|
-
// OpenAI streaming sends chunks with choices array
|
|
514
443
|
default: {
|
|
515
|
-
// Check for OpenAI format: { choices: [{ delta: { content: "..." } }], usage: {...} }
|
|
516
444
|
if (event.choices && Array.isArray(event.choices)) {
|
|
517
445
|
const choice = event.choices[0] as { delta?: { content?: string }; finish_reason?: string } | undefined;
|
|
518
446
|
if (choice?.delta?.content) {
|
|
@@ -522,13 +450,12 @@ export async function createMessageStream(
|
|
|
522
450
|
} else {
|
|
523
451
|
currentTextBlock = { type: "text", text };
|
|
524
452
|
}
|
|
525
|
-
onToken?.(text);
|
|
453
|
+
callbacks.onToken?.(text); // Emit in real-time
|
|
526
454
|
if (firstToken) {
|
|
527
455
|
ttft = Date.now() - startTime;
|
|
528
456
|
firstToken = false;
|
|
529
457
|
}
|
|
530
458
|
}
|
|
531
|
-
// Check for finish
|
|
532
459
|
if (choice?.finish_reason) {
|
|
533
460
|
if (currentTextBlock) {
|
|
534
461
|
currentContent.push(currentTextBlock);
|
|
@@ -550,7 +477,6 @@ export async function createMessageStream(
|
|
|
550
477
|
}
|
|
551
478
|
}
|
|
552
479
|
}
|
|
553
|
-
// OpenAI usage format (often in final chunk)
|
|
554
480
|
if (event.usage) {
|
|
555
481
|
const openaiUsage = event.usage as { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number };
|
|
556
482
|
usage.input_tokens = openaiUsage.prompt_tokens || 0;
|
|
@@ -560,11 +486,13 @@ export async function createMessageStream(
|
|
|
560
486
|
}
|
|
561
487
|
}
|
|
562
488
|
} catch (err: unknown) {
|
|
563
|
-
//
|
|
489
|
+
// Only rethrow if it's an API error, not a JSON parse error
|
|
490
|
+
if (err instanceof Error && err.message.startsWith("API error:")) {
|
|
491
|
+
throw err;
|
|
492
|
+
}
|
|
564
493
|
if (process.env.DEBUG_API === '1') {
|
|
565
494
|
console.error('\x1b[91m[DEBUG] JSON parse error:', err);
|
|
566
495
|
console.error('\x1b[91m[DEBUG] Error parsing SSE data:', data.substring(0, 200));
|
|
567
|
-
console.error('\x1b[91m[DEBUG] Original buffer:', buffer.substring(0, 500));
|
|
568
496
|
}
|
|
569
497
|
}
|
|
570
498
|
}
|
|
@@ -573,8 +501,8 @@ export async function createMessageStream(
|
|
|
573
501
|
reader.releaseLock();
|
|
574
502
|
}
|
|
575
503
|
|
|
504
|
+
// Handle "No message received" case - this is retryable
|
|
576
505
|
if (!message) {
|
|
577
|
-
// If we received content via OpenAI format but no message_start, create a message
|
|
578
506
|
if (currentContent.length > 0) {
|
|
579
507
|
message = {
|
|
580
508
|
id: `msg-${Date.now()}`,
|
|
@@ -587,31 +515,213 @@ export async function createMessageStream(
|
|
|
587
515
|
usage: { input_tokens: 0, output_tokens: 0 },
|
|
588
516
|
};
|
|
589
517
|
} else {
|
|
590
|
-
//
|
|
591
|
-
if (process.env.DEBUG_API === '1') {
|
|
592
|
-
console.log('\x1b[91m[DEBUG] No message_start event received. Buffer:\x1b[0m', buffer.substring(0, 500));
|
|
593
|
-
}
|
|
518
|
+
// This is a transient error - throw to trigger retry
|
|
594
519
|
throw new Error("No message received from API");
|
|
595
520
|
}
|
|
596
521
|
}
|
|
597
522
|
|
|
598
|
-
|
|
523
|
+
return {
|
|
524
|
+
message,
|
|
525
|
+
content: currentContent,
|
|
526
|
+
usage,
|
|
527
|
+
thinkingTokens: totalThinkingTokens,
|
|
528
|
+
ttftMs: ttft,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Create a streaming message request to Anthropic API
|
|
534
|
+
* Full retry support including stream parsing errors
|
|
535
|
+
*/
|
|
536
|
+
export async function createMessageStream(
|
|
537
|
+
messages: Message[],
|
|
538
|
+
options: StreamOptions
|
|
539
|
+
): Promise<StreamResult> {
|
|
540
|
+
const {
|
|
541
|
+
apiKey,
|
|
542
|
+
model = "claude-sonnet-4-6",
|
|
543
|
+
maxTokens = 4096,
|
|
544
|
+
tools,
|
|
545
|
+
systemPrompt,
|
|
546
|
+
cacheConfig = DEFAULT_CACHE_CONFIG,
|
|
547
|
+
thinking,
|
|
548
|
+
extendedThinking,
|
|
549
|
+
onToken,
|
|
550
|
+
onThinking,
|
|
551
|
+
onRedactedThinking,
|
|
552
|
+
onToolUse,
|
|
553
|
+
onRetryStart,
|
|
554
|
+
signal,
|
|
555
|
+
} = options;
|
|
556
|
+
|
|
557
|
+
const startTime = Date.now();
|
|
558
|
+
|
|
559
|
+
// Build cached messages
|
|
560
|
+
const cachedMessages = buildCachedMessages(messages, cacheConfig);
|
|
561
|
+
|
|
562
|
+
// Build system prompt with cache control
|
|
563
|
+
const cachedSystemPrompt = buildSystemPrompt(systemPrompt, cacheConfig);
|
|
564
|
+
|
|
565
|
+
// Build request
|
|
566
|
+
const request: APIRequest = {
|
|
567
|
+
model,
|
|
568
|
+
max_tokens: maxTokens,
|
|
569
|
+
messages: cachedMessages.map((m) => ({
|
|
570
|
+
role: m.role,
|
|
571
|
+
content: m.content,
|
|
572
|
+
})),
|
|
573
|
+
stream: true,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
if (cachedSystemPrompt) {
|
|
577
|
+
request.system = cachedSystemPrompt;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Tools will be set after determining API format (for format conversion)
|
|
581
|
+
|
|
582
|
+
// Resolve provider based on model name
|
|
583
|
+
const providerInfo = resolveProvider(model);
|
|
584
|
+
|
|
585
|
+
// Determine API endpoint and headers based on provider
|
|
586
|
+
let apiEndpoint: string;
|
|
587
|
+
let headers: Record<string, string>;
|
|
588
|
+
let apiFormat: "anthropic" | "openai";
|
|
589
|
+
|
|
590
|
+
if (providerInfo) {
|
|
591
|
+
// Use provider-specific configuration
|
|
592
|
+
apiEndpoint = providerInfo.endpoint;
|
|
593
|
+
apiFormat = providerInfo.config.format;
|
|
594
|
+
|
|
595
|
+
if (apiFormat === "anthropic") {
|
|
596
|
+
// Anthropic/MiniMax format
|
|
597
|
+
headers = {
|
|
598
|
+
"Content-Type": "application/json",
|
|
599
|
+
[providerInfo.config.authHeader]: providerInfo.apiKey,
|
|
600
|
+
"anthropic-version": "2023-06-01",
|
|
601
|
+
};
|
|
602
|
+
} else {
|
|
603
|
+
// OpenAI/Zhipu format
|
|
604
|
+
headers = {
|
|
605
|
+
"Content-Type": "application/json",
|
|
606
|
+
[providerInfo.config.authHeader]: `Bearer ${providerInfo.apiKey}`,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
// Fallback to environment-based configuration (legacy)
|
|
611
|
+
const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
|
|
612
|
+
apiEndpoint = `${baseUrl}/v1/messages`;
|
|
613
|
+
apiFormat = "anthropic";
|
|
614
|
+
|
|
615
|
+
headers = {
|
|
616
|
+
"Content-Type": "application/json",
|
|
617
|
+
"x-api-key": apiKey,
|
|
618
|
+
"anthropic-version": "2023-06-01",
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Set tools with format conversion if needed
|
|
623
|
+
if (tools && tools.length > 0) {
|
|
624
|
+
if (apiFormat === "openai") {
|
|
625
|
+
// Convert Anthropic-style tools to OpenAI format
|
|
626
|
+
// Cast needed because OpenAI format differs from APITool
|
|
627
|
+
(request as unknown as Record<string, unknown>).tools = convertToolsToOpenAIFormat(tools);
|
|
628
|
+
} else {
|
|
629
|
+
// Keep Anthropic format as-is
|
|
630
|
+
request.tools = tools;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const shouldUseExtendedThinking =
|
|
635
|
+
(extendedThinking?.enabled ?? false) ||
|
|
636
|
+
(thinking && thinking.type !== "disabled");
|
|
637
|
+
|
|
638
|
+
if (shouldUseExtendedThinking && supportsExtendedThinking(model)) {
|
|
639
|
+
let budgetTokens: number;
|
|
640
|
+
|
|
641
|
+
if (extendedThinking?.budgetTokens) {
|
|
642
|
+
budgetTokens = extendedThinking.budgetTokens;
|
|
643
|
+
} else if (thinking?.type === "enabled") {
|
|
644
|
+
budgetTokens = thinking.budget_tokens;
|
|
645
|
+
} else {
|
|
646
|
+
const effort = extendedThinking?.effort || "medium";
|
|
647
|
+
budgetTokens = calculateBudgetTokens(
|
|
648
|
+
{ enabled: true, effort, modelMultiplier: model.includes("opus") ? 2 : 1 },
|
|
649
|
+
model
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
budgetTokens = Math.max(1024, Math.min(budgetTokens, 100000));
|
|
654
|
+
|
|
655
|
+
request.thinking = { type: "enabled", budget_tokens: budgetTokens };
|
|
656
|
+
|
|
657
|
+
const betaFeatures: string[] = ["extended-thinking-2025-01-24"];
|
|
658
|
+
if (extendedThinking?.interleaved !== false) {
|
|
659
|
+
betaFeatures.push("interleaved-thinking-2025-01-24");
|
|
660
|
+
}
|
|
661
|
+
headers["anthropic-beta"] = betaFeatures.join(",");
|
|
662
|
+
} else if (apiFormat === "anthropic") {
|
|
663
|
+
headers["anthropic-beta"] = "max-tokens-3-5-sonnet-2024-07-15";
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Retry options - now covers entire stream parsing
|
|
667
|
+
const retryOptions: RetryOptions = {
|
|
668
|
+
maxRetries: 10,
|
|
669
|
+
baseDelayMs: 1000,
|
|
670
|
+
maxDelayMs: 60000,
|
|
671
|
+
retryableStatusCodes: [429, 500, 502, 503, 504, 529],
|
|
672
|
+
onRetry: (attempt, error, delayMs) => {
|
|
673
|
+
console.log(`\x1b[33mAPI retry ${attempt}/10 after ${delayMs}ms: ${error.message}\x1b[0m`);
|
|
674
|
+
// Notify UI to reset streaming state before retry
|
|
675
|
+
onRetryStart?.();
|
|
676
|
+
// Track provider failure on retry
|
|
677
|
+
const providerName = getProviderForModel(model);
|
|
678
|
+
if (providerName) {
|
|
679
|
+
recordProviderFailure(providerName);
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// Execute with retry - wraps entire fetch + stream parsing
|
|
685
|
+
// Callbacks are emitted in real-time during streaming
|
|
686
|
+
const result = await withRetry(
|
|
687
|
+
() => executeStreamAttempt(
|
|
688
|
+
request,
|
|
689
|
+
headers,
|
|
690
|
+
apiEndpoint,
|
|
691
|
+
signal,
|
|
692
|
+
model,
|
|
693
|
+
retryOptions.retryableStatusCodes ?? [],
|
|
694
|
+
startTime,
|
|
695
|
+
{ onToken, onThinking, onRedactedThinking, onToolUse }
|
|
696
|
+
),
|
|
697
|
+
retryOptions
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
// Build final message
|
|
701
|
+
const message = result.message!;
|
|
702
|
+
message.content = result.content;
|
|
599
703
|
|
|
600
704
|
// Calculate cost and cache metrics
|
|
601
|
-
const { costUSD, estimatedSavingsUSD } = calculateCost(model, usage);
|
|
602
|
-
const cacheMetrics = calculateCacheMetrics(usage);
|
|
705
|
+
const { costUSD, estimatedSavingsUSD } = calculateCost(model, result.usage);
|
|
706
|
+
const cacheMetrics = calculateCacheMetrics(result.usage);
|
|
603
707
|
cacheMetrics.estimatedSavingsUSD = estimatedSavingsUSD;
|
|
604
708
|
|
|
605
709
|
const durationMs = Date.now() - startTime;
|
|
606
710
|
|
|
711
|
+
// Track provider health on success
|
|
712
|
+
const providerName = getProviderForModel(model);
|
|
713
|
+
if (providerName) {
|
|
714
|
+
recordProviderSuccess(providerName, durationMs);
|
|
715
|
+
}
|
|
716
|
+
|
|
607
717
|
return {
|
|
608
718
|
message,
|
|
609
|
-
usage,
|
|
719
|
+
usage: result.usage,
|
|
610
720
|
cacheMetrics,
|
|
611
721
|
costUSD,
|
|
612
722
|
durationMs,
|
|
613
|
-
ttftMs:
|
|
614
|
-
thinkingTokens:
|
|
723
|
+
ttftMs: result.ttftMs || durationMs,
|
|
724
|
+
thinkingTokens: result.thinkingTokens,
|
|
615
725
|
};
|
|
616
726
|
}
|
|
617
727
|
|
|
@@ -493,7 +493,8 @@ export class CognitiveSecurityHooks {
|
|
|
493
493
|
if (this.config.logEvents) {
|
|
494
494
|
const prefix = action === "deny" ? "\x1b[31m[Security]\x1b[0m" : "\x1b[90m[Security]\x1b[0m";
|
|
495
495
|
const toolStr = tool ? ` ${tool}:` : "";
|
|
496
|
-
console.
|
|
496
|
+
// Use console.error to avoid interfering with TUI (stdout is used by renderer)
|
|
497
|
+
console.error(`${prefix}${toolStr} ${reason}`);
|
|
497
498
|
}
|
|
498
499
|
}
|
|
499
500
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
mv
|
|
2
|
+
- /Users/ebowwa/Desktop/codespaces/packages/src/coder/packages/src/core/claude-md.ts
|
|
3
|
+
- /Users/ebowwa/Desktop/codespaces/packages/src/coder/packages/src/core/config-loader.ts
|
|
4
|
+
- /Users/ebowwa/Desktop/codespaces/packages/src/coder/packages/src/core/models.ts
|
|
5
|
+
- /Users/ebowwa/Desktop/codespaces/packages/src/coder/packages/src/core/permissions.ts
|
|
6
|
+
|
|
7
|
+
here aqi
|