Skip to content

Commit 409761d

Browse files
Add support for reasoning in the UI
Add UI settings to set reasoning effort / token budget Add UI to show reasoning tokens from Anthropic Claude 3.7 Sonnet and DeepSeek R1
1 parent 1255b21 commit 409761d

28 files changed

+1507
-373
lines changed

core/index.d.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,22 @@ export type ImageMessagePart = {
324324
imageUrl: { url: string };
325325
};
326326

327-
export type MessagePart = TextMessagePart | ImageMessagePart;
327+
export type ThinkingMessagePart = {
328+
type: "thinking";
329+
thinking: string;
330+
signature: string;
331+
};
332+
333+
export type RedactedThinkingMessagePart = {
334+
type: "redacted_thinking";
335+
data: string;
336+
};
337+
338+
export type MessagePart =
339+
| TextMessagePart
340+
| ImageMessagePart
341+
| ThinkingMessagePart
342+
| RedactedThinkingMessagePart;
328343

329344
export type MessageContent = string | MessagePart[];
330345

@@ -360,6 +375,7 @@ export interface UserChatMessage {
360375
export interface AssistantChatMessage {
361376
role: "assistant";
362377
content: MessageContent;
378+
reasoning_content?: string;
363379
toolCalls?: ToolCallDelta[];
364380
}
365381

@@ -921,11 +937,17 @@ export interface BaseCompletionOptions {
921937
prediction?: Prediction;
922938
tools?: Tool[];
923939
toolChoice?: ToolChoice;
940+
thinking?: {
941+
type: "enabled" | "disabled";
942+
budget_tokens?: number;
943+
};
944+
reasoning_effort?: "high" | "medium" | "low";
924945
}
925946

926947
export interface ModelCapability {
927948
uploadImage?: boolean;
928949
tools?: boolean;
950+
thinking?: boolean;
929951
}
930952

931953
export interface ModelDescription {

core/llm/autodetect.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ function autodetectTemplateType(model: string): TemplateType | undefined {
174174
lower.includes("pplx") ||
175175
lower.includes("gemini") ||
176176
lower.includes("grok") ||
177-
lower.includes("moonshot")
177+
lower.includes("moonshot") ||
178+
lower.includes("deepseek-reasoner")
178179
) {
179180
return undefined;
180181
}
@@ -373,11 +374,45 @@ function autodetectPromptTemplates(
373374
return templates;
374375
}
375376

377+
const PROVIDER_SUPPORTS_THINKING: string[] = ["anthropic", "openai", "deepseek"];
378+
379+
const MODEL_SUPPORTS_THINKING: string[] = [
380+
"claude-3-7-sonnet-20250219",
381+
"claude-3-7-sonnet-latest",
382+
"o3-mini",
383+
"o3-mini-2025-01-31",
384+
"o1",
385+
"o1-2024-12-17",
386+
"deepseek-reasoner",
387+
];
388+
389+
function modelSupportsThinking(
390+
provider: string,
391+
model: string,
392+
title: string | undefined,
393+
capabilities: ModelCapability | undefined,
394+
): boolean {
395+
if (capabilities?.thinking !== undefined) {
396+
return capabilities.thinking;
397+
}
398+
399+
if (!PROVIDER_SUPPORTS_THINKING.includes(provider)) {
400+
return false;
401+
}
402+
403+
const lower = model.toLowerCase();
404+
return MODEL_SUPPORTS_THINKING.some(
405+
(modelName) => lower.includes(modelName) || title?.includes(modelName),
406+
);
407+
}
408+
376409
export {
377410
autodetectPromptTemplates,
378411
autodetectTemplateFunction,
379412
autodetectTemplateType,
380413
llmCanGenerateInParallel,
381414
modelSupportsImages,
382-
modelSupportsTools,
415+
modelSupportsThinking,
416+
modelSupportsTools
383417
};
418+

core/llm/countTokens.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,15 @@ async function countTokensAsync(
9090
const promises = content.map(async (part) => {
9191
if (part.type === "imageUrl") {
9292
return countImageTokens(part);
93+
} else if (part.type === "thinking") {
94+
return (await encoding.encode(part.thinking ?? "")).length;
95+
} else if (part.type === "redacted_thinking") {
96+
// For redacted thinking, don't count any tokens
97+
return 0;
98+
} else if (part.type === "text") {
99+
return (await encoding.encode(part.text ?? "")).length;
93100
}
94-
return (await encoding.encode(part.text ?? "")).length;
101+
return 0;
95102
});
96103
return (await Promise.all(promises)).reduce((sum, val) => sum + val, 0);
97104
}
@@ -106,12 +113,17 @@ function countTokens(
106113
const encoding = encodingForModel(modelName);
107114
if (Array.isArray(content)) {
108115
return content.reduce((acc, part) => {
109-
return (
110-
acc +
111-
(part.type === "text"
112-
? encoding.encode(part.text ?? "", "all", []).length
113-
: countImageTokens(part))
114-
);
116+
if (part.type === "text") {
117+
return acc + encoding.encode(part.text ?? "", "all", []).length;
118+
} else if (part.type === "imageUrl") {
119+
return acc + countImageTokens(part);
120+
} else if (part.type === "thinking") {
121+
return acc + encoding.encode(part.thinking ?? "", "all", []).length;
122+
} else if (part.type === "redacted_thinking") {
123+
// For redacted thinking, don't count any tokens
124+
return acc;
125+
}
126+
return acc;
115127
}, 0);
116128
} else {
117129
return encoding.encode(content ?? "", "all", []).length;
@@ -469,5 +481,6 @@ export {
469481
pruneLinesFromTop,
470482
pruneRawPromptFromTop,
471483
pruneStringFromBottom,
472-
pruneStringFromTop,
484+
pruneStringFromTop
473485
};
486+

0 commit comments

Comments
 (0)