···
18
18
ANTHROPIC_MODEL=
19
19
ANTHROPIC_MAX_TOKENS=8192
20
20
ANTHROPIC_VERSION=2024-10-22
21
21
+
# Budget tokens for Anthropic extended thinking (≥1024, < max_tokens). Only used when ENABLE_THINKING=true.
22
22
+
ANTHROPIC_THINKING_BUDGET=4096
21
23
22
24
# ── Memory embeddings ────────────────────────────────────────────────────────
23
25
# Used for semantic memory reranking and low-information chatter suppression.
···
2
2
import OpenAI from "openai"
3
3
import { recordMetric } from "../metrics"
4
4
import { emit } from "../stream"
5
5
+
import { ENABLE_THINKING } from "./util"
5
6
import type { CompletionRequest, CompletionTurnResult, ToolCallAssembly } from "./loop-shared"
6
7
7
8
// ── config ──────────────────────────────────────────────────────────────
···
11
12
export const ANTHROPIC_MODEL = process.env.ANTHROPIC_MODEL ?? ""
12
13
export const ANTHROPIC_MAX_TOKENS = Math.max(1, Number.parseInt(process.env.ANTHROPIC_MAX_TOKENS ?? "8192", 10)) || 8192
13
14
export const ANTHROPIC_VERSION = process.env.ANTHROPIC_VERSION ?? "2024-10-22"
15
15
+
export const ANTHROPIC_THINKING_BUDGET = Math.max(
16
16
+
1024,
17
17
+
Number.parseInt(process.env.ANTHROPIC_THINKING_BUDGET ?? "4096", 10) || 4096,
18
18
+
)
14
19
15
20
const client = new Anthropic({
16
21
apiKey: ANTHROPIC_API_KEY,
···
204
209
205
210
// ── streaming conversion (Anthropic SSE → OpenAI chunks) ────────────────
206
211
207
207
-
function makeChunk(partial: Omit<OpenAI.Chat.ChatCompletionChunk.Choice.Delta, "role" | "content" | "tool_calls"> & {
212
212
+
function makeChunk(partial: Omit<OpenAI.Chat.ChatCompletionChunk.Choice.Delta, "role" | "content" | "tool_calls" | "reasoning_content"> & {
208
213
role?: "assistant"
209
214
content?: string | null
215
215
+
reasoning_content?: string
210
216
tool_calls?: OpenAI.Chat.ChatCompletionChunk.Choice.Delta.ToolCall[]
211
217
}): OpenAI.Chat.ChatCompletionChunk {
212
218
return {
···
259
265
const delta = event.delta
260
266
if (delta.type === "text_delta") {
261
267
yield makeChunk({ content: delta.text })
268
268
+
} else if (delta.type === "thinking_delta") {
269
269
+
yield makeChunk({ reasoning_content: delta.thinking })
262
270
} else if (delta.type === "input_json_delta") {
263
271
const tool = toolBuffers.get(index)
264
272
if (tool) {
···
319
327
let usage: OpenAI.Completions.CompletionUsage | undefined
320
328
let emittedText = false
321
329
let emittedThinking = false
330
330
+
const reasoningParts: string[] = []
322
331
323
332
for await (const chunk of stream) {
324
333
if (chunk.usage) usage = chunk.usage
···
330
339
reasoning_content?: string
331
340
}
332
341
342
342
+
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
343
343
+
reasoningParts.push(delta.reasoning_content)
344
344
+
}
345
345
+
333
346
if (typeof delta.content === "string" && delta.content.length > 0) {
347
347
+
if (ENABLE_THINKING && !emittedThinking && reasoningParts.length > 0) {
348
348
+
emit({ type: "thinking", text: reasoningParts.join("") })
349
349
+
emittedThinking = true
350
350
+
}
334
351
contentParts.push(delta.content)
335
352
emit({ type: "text", text: delta.content })
336
353
emittedText = true
···
368
385
...(finalToolCalls.length > 0 ? { tool_calls: finalToolCalls } : {}),
369
386
}
370
387
388
388
+
if (reasoningParts.length > 0) {
389
389
+
;(message as OpenAI.Chat.ChatCompletionMessage & { reasoning_content?: string }).reasoning_content =
390
390
+
reasoningParts.join("")
391
391
+
}
392
392
+
371
393
const elapsedMs = Math.max(0, Date.now() - startedAt)
372
394
const tokensPerSecond =
373
395
usage && elapsedMs > 0 ? usage.completion_tokens / (elapsedMs / 1000) : undefined
···
377
399
usage,
378
400
emittedText,
379
401
emittedThinking,
380
380
-
bufferedThinking: "",
402
402
+
bufferedThinking: reasoningParts.join(""),
381
403
elapsedMs,
382
404
tokensPerSecond,
383
405
}
···
395
417
max_tokens: ANTHROPIC_MAX_TOKENS,
396
418
messages,
397
419
...(system ? { system } : {}),
420
420
+
}
421
421
+
422
422
+
if (ENABLE_THINKING) {
423
423
+
const thinkingBudget = Math.min(ANTHROPIC_MAX_TOKENS - 1, ANTHROPIC_THINKING_BUDGET)
424
424
+
if (thinkingBudget >= 1024) {
425
425
+
;(body as unknown as Record<string, unknown>).thinking = {
426
426
+
type: "enabled",
427
427
+
budget_tokens: thinkingBudget,
428
428
+
}
429
429
+
}
398
430
}
399
431
400
432
if (request.tools.length > 0 && request.tool_choice !== "none") {