byterover-cli 3.10.3 → 3.12.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.
- package/README.md +4 -2
- package/dist/agent/core/domain/llm/registry.d.ts +12 -0
- package/dist/agent/core/domain/llm/registry.js +49 -0
- package/dist/agent/core/domain/llm/types.d.ts +6 -0
- package/dist/agent/core/interfaces/i-content-generator.d.ts +8 -0
- package/dist/agent/infra/llm/agent-llm-service.js +18 -6
- package/dist/agent/infra/llm/context/context-manager.d.ts +4 -1
- package/dist/agent/infra/llm/context/context-manager.js +5 -1
- package/dist/agent/infra/llm/generators/ai-sdk-content-generator.d.ts +13 -0
- package/dist/agent/infra/llm/generators/ai-sdk-content-generator.js +19 -6
- package/dist/agent/infra/llm/generators/ai-sdk-message-converter.js +16 -4
- package/dist/agent/infra/llm/generators/byterover-content-generator.d.ts +1 -0
- package/dist/agent/infra/llm/generators/byterover-content-generator.js +4 -1
- package/dist/agent/infra/llm/model-capabilities.d.ts +2 -1
- package/dist/agent/infra/llm/model-capabilities.js +6 -4
- package/dist/agent/infra/llm/providers/anthropic.js +2 -0
- package/dist/agent/infra/llm/providers/deepseek.d.ts +10 -0
- package/dist/agent/infra/llm/providers/deepseek.js +33 -0
- package/dist/agent/infra/llm/providers/glm-coding-plan.d.ts +9 -0
- package/dist/agent/infra/llm/providers/glm-coding-plan.js +32 -0
- package/dist/agent/infra/llm/providers/index.js +4 -0
- package/dist/agent/infra/llm/providers/openrouter.js +2 -0
- package/dist/agent/infra/tools/implementations/curate-tool.js +18 -8
- package/dist/oclif/commands/query.js +7 -1
- package/dist/oclif/lib/task-client.d.ts +9 -0
- package/dist/oclif/lib/task-client.js +11 -1
- package/dist/server/constants.d.ts +6 -0
- package/dist/server/constants.js +11 -0
- package/dist/server/core/domain/entities/provider-registry.js +26 -0
- package/dist/server/core/domain/entities/task-history-entry.d.ts +775 -0
- package/dist/server/core/domain/entities/task-history-entry.js +88 -0
- package/dist/server/core/domain/transport/schemas.d.ts +1403 -11
- package/dist/server/core/domain/transport/schemas.js +157 -6
- package/dist/server/core/domain/transport/task-info.d.ts +18 -0
- package/dist/server/core/interfaces/process/i-task-lifecycle-hook.d.ts +7 -0
- package/dist/server/core/interfaces/storage/i-task-history-store.d.ts +62 -0
- package/dist/server/core/interfaces/storage/i-task-history-store.js +1 -0
- package/dist/server/infra/daemon/brv-server.js +43 -18
- package/dist/server/infra/dream/dream-response-schemas.d.ts +24 -0
- package/dist/server/infra/dream/dream-response-schemas.js +7 -0
- package/dist/server/infra/dream/operations/consolidate.js +21 -8
- package/dist/server/infra/dream/operations/synthesize.js +35 -8
- package/dist/server/infra/http/provider-model-fetcher-registry.js +5 -0
- package/dist/server/infra/http/provider-model-fetchers.js +54 -27
- package/dist/server/infra/process/query-log-handler.d.ts +6 -0
- package/dist/server/infra/process/query-log-handler.js +23 -0
- package/dist/server/infra/process/task-history-entry-builder.d.ts +36 -0
- package/dist/server/infra/process/task-history-entry-builder.js +101 -0
- package/dist/server/infra/process/task-history-hook.d.ts +37 -0
- package/dist/server/infra/process/task-history-hook.js +70 -0
- package/dist/server/infra/process/task-history-store-cache.d.ts +25 -0
- package/dist/server/infra/process/task-history-store-cache.js +106 -0
- package/dist/server/infra/process/task-router.d.ts +72 -0
- package/dist/server/infra/process/task-router.js +690 -15
- package/dist/server/infra/process/transport-handlers.d.ts +8 -0
- package/dist/server/infra/process/transport-handlers.js +2 -0
- package/dist/server/infra/storage/file-task-history-store.d.ts +294 -0
- package/dist/server/infra/storage/file-task-history-store.js +912 -0
- package/dist/shared/transport/events/index.d.ts +5 -0
- package/dist/shared/transport/events/task-events.d.ts +204 -1
- package/dist/shared/transport/events/task-events.js +11 -0
- package/dist/tui/features/tasks/hooks/use-task-subscriptions.js +7 -0
- package/dist/tui/features/tasks/stores/tasks-store.d.ts +4 -16
- package/dist/tui/features/tasks/stores/tasks-store.js +7 -0
- package/dist/tui/types/messages.d.ts +2 -9
- package/dist/webui/assets/index-DyVvFoM6.css +1 -0
- package/dist/webui/assets/index-lr0byHh9.js +130 -0
- package/dist/webui/index.html +2 -2
- package/dist/webui/sw.js +1 -1
- package/dist/webui/workbox-9c191d2f.js +1 -0
- package/oclif.manifest.json +985 -985
- package/package.json +1 -1
- package/dist/webui/assets/index-CvcqpMYn.css +0 -1
- package/dist/webui/assets/index-thSZZahh.js +0 -130
- package/dist/webui/workbox-8c29f6e4.js +0 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Or download our self-hosted PDF version of the paper [here](https://byterover.de
|
|
|
34
34
|
- 🖥️ Interactive TUI with REPL interface (React/Ink)
|
|
35
35
|
- 🧠 Context tree and knowledge storage management
|
|
36
36
|
- 🔀 Git-like version control for the context tree (branch, commit, merge, push/pull)
|
|
37
|
-
- 🤖
|
|
37
|
+
- 🤖 20 LLM providers (Anthropic, OpenAI, Google, Groq, Mistral, xAI, DeepSeek, and more)
|
|
38
38
|
- 🛠️ 24 built-in agent tools (code exec, file ops, knowledge search, memory management)
|
|
39
39
|
- 🔄 Cloud sync with push/pull
|
|
40
40
|
- 👀 Review workflow for curate operations (approve/reject pending changes)
|
|
@@ -220,7 +220,7 @@ Run `brv --help` for the full command reference.
|
|
|
220
220
|
<details>
|
|
221
221
|
<summary><h2>Supported LLM Providers</h2></summary>
|
|
222
222
|
|
|
223
|
-
ByteRover CLI supports
|
|
223
|
+
ByteRover CLI supports 20 LLM providers out of the box. Connect and switch providers from the dashboard, or use `brv providers connect` / `brv providers switch`.
|
|
224
224
|
|
|
225
225
|
| Provider | Description |
|
|
226
226
|
|----------|-------------|
|
|
@@ -233,6 +233,7 @@ ByteRover CLI supports 18 LLM providers out of the box. Connect and switch provi
|
|
|
233
233
|
| Cerebras | Fast inference |
|
|
234
234
|
| Cohere | Command models |
|
|
235
235
|
| DeepInfra | Open-source model hosting |
|
|
236
|
+
| DeepSeek | DeepSeek V3 and R1 reasoning models |
|
|
236
237
|
| OpenRouter | Multi-provider gateway |
|
|
237
238
|
| Perplexity | Search-augmented models |
|
|
238
239
|
| TogetherAI | Open-source model hosting |
|
|
@@ -240,6 +241,7 @@ ByteRover CLI supports 18 LLM providers out of the box. Connect and switch provi
|
|
|
240
241
|
| Minimax | Minimax models |
|
|
241
242
|
| Moonshot | Kimi models |
|
|
242
243
|
| GLM | GLM models |
|
|
244
|
+
| GLM Coding Plan | GLM models on Z.AI Coding Plan subscription |
|
|
243
245
|
| OpenAI-Compatible | Any OpenAI-compatible API |
|
|
244
246
|
| ByteRover | ByteRover's hosted models |
|
|
245
247
|
|
|
@@ -119,3 +119,15 @@ export declare function resolveRegistryProvider(model: string, explicitProvider?
|
|
|
119
119
|
* @returns true if provider accepts arbitrary models
|
|
120
120
|
*/
|
|
121
121
|
export declare function acceptsAnyModel(provider: LLMProvider): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Check whether a model accepts the sampling parameters `temperature`, `top_p`,
|
|
124
|
+
* and `top_k`.
|
|
125
|
+
*
|
|
126
|
+
* Some models (e.g. Claude Opus 4.7) reject any non-default value with a 400
|
|
127
|
+
* error — callers must omit these parameters entirely when this returns false.
|
|
128
|
+
*
|
|
129
|
+
* Handles both bare model ids (`claude-opus-4-7`) and OpenRouter-style prefixed
|
|
130
|
+
* ids (`anthropic/claude-opus-4-7`). Unknown models default to true so we don't
|
|
131
|
+
* regress arbitrary OpenRouter-routed models.
|
|
132
|
+
*/
|
|
133
|
+
export declare function modelAcceptsSamplingParameters(modelId: string): boolean;
|
|
@@ -25,6 +25,21 @@ export const LLM_REGISTRY = {
|
|
|
25
25
|
defaultModel: '',
|
|
26
26
|
models: [
|
|
27
27
|
// Claude 4.x series
|
|
28
|
+
{
|
|
29
|
+
capabilities: {
|
|
30
|
+
acceptsSamplingParameters: false,
|
|
31
|
+
supportsAudio: false,
|
|
32
|
+
supportsImages: true,
|
|
33
|
+
supportsPdf: true,
|
|
34
|
+
supportsStreaming: true,
|
|
35
|
+
},
|
|
36
|
+
charsPerToken: 3.5,
|
|
37
|
+
displayName: 'Claude Opus 4.7',
|
|
38
|
+
maxInputTokens: 200_000,
|
|
39
|
+
maxOutputTokens: 128_000,
|
|
40
|
+
name: 'claude-opus-4-7',
|
|
41
|
+
supportedFileTypes: ['image', 'pdf'],
|
|
42
|
+
},
|
|
28
43
|
{
|
|
29
44
|
capabilities: { supportsAudio: false, supportsImages: true, supportsPdf: true, supportsStreaming: true },
|
|
30
45
|
charsPerToken: 3.5,
|
|
@@ -684,3 +699,37 @@ export function acceptsAnyModel(provider) {
|
|
|
684
699
|
// OpenAI provider type accepts arbitrary models (OpenRouter, direct OpenAI, xAI, etc.)
|
|
685
700
|
return provider === 'openai';
|
|
686
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* Strip an OpenRouter-style `provider/` prefix from a model id.
|
|
704
|
+
* Returns the input unchanged if no slash is present.
|
|
705
|
+
*/
|
|
706
|
+
function stripRouterPrefix(modelId) {
|
|
707
|
+
const slash = modelId.lastIndexOf('/');
|
|
708
|
+
return slash === -1 ? modelId : modelId.slice(slash + 1);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Check whether a model accepts the sampling parameters `temperature`, `top_p`,
|
|
712
|
+
* and `top_k`.
|
|
713
|
+
*
|
|
714
|
+
* Some models (e.g. Claude Opus 4.7) reject any non-default value with a 400
|
|
715
|
+
* error — callers must omit these parameters entirely when this returns false.
|
|
716
|
+
*
|
|
717
|
+
* Handles both bare model ids (`claude-opus-4-7`) and OpenRouter-style prefixed
|
|
718
|
+
* ids (`anthropic/claude-opus-4-7`). Unknown models default to true so we don't
|
|
719
|
+
* regress arbitrary OpenRouter-routed models.
|
|
720
|
+
*/
|
|
721
|
+
export function modelAcceptsSamplingParameters(modelId) {
|
|
722
|
+
const bare = stripRouterPrefix(modelId);
|
|
723
|
+
for (const provider of PROVIDER_TYPES) {
|
|
724
|
+
const info = getModelInfo(provider, bare);
|
|
725
|
+
if (info) {
|
|
726
|
+
return info.capabilities.acceptsSamplingParameters !== false;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// Family-level fallback: catches date-suffixed snapshots not yet in the registry
|
|
730
|
+
// (e.g. `claude-opus-4-7-20260101`). Extend this list as new families deprecate
|
|
731
|
+
// sampling params.
|
|
732
|
+
if (bare.startsWith('claude-opus-4-7'))
|
|
733
|
+
return false;
|
|
734
|
+
return true;
|
|
735
|
+
}
|
|
@@ -34,6 +34,12 @@ export type SupportedFileType = (typeof SUPPORTED_FILE_TYPES)[number];
|
|
|
34
34
|
* Defines what features a specific model supports.
|
|
35
35
|
*/
|
|
36
36
|
export interface ModelCapabilities {
|
|
37
|
+
/**
|
|
38
|
+
* Whether the model accepts the sampling parameters `temperature`, `top_p`, and `top_k`.
|
|
39
|
+
* When false, callers must omit these from the request — Claude Opus 4.7 returns 400
|
|
40
|
+
* on any non-default value. Defaults to true when omitted.
|
|
41
|
+
*/
|
|
42
|
+
acceptsSamplingParameters?: boolean;
|
|
37
43
|
/** Whether the model supports audio input */
|
|
38
44
|
supportsAudio: boolean;
|
|
39
45
|
/** Whether the model supports image input */
|
|
@@ -65,6 +65,14 @@ export interface GenerateContentResponse {
|
|
|
65
65
|
finishReason: 'error' | 'max_tokens' | 'stop' | 'tool_calls';
|
|
66
66
|
/** Raw response from provider (for debugging) */
|
|
67
67
|
rawResponse?: unknown;
|
|
68
|
+
/**
|
|
69
|
+
* Reasoning / thinking text emitted by the model (e.g. DeepSeek-R1's
|
|
70
|
+
* `reasoning_content`, OpenAI o1's reasoning summary). Required to be
|
|
71
|
+
* passed back to the API on the next turn for some providers — DeepSeek-R1
|
|
72
|
+
* rejects the next call with "The reasoning_content in the thinking mode
|
|
73
|
+
* must be passed back to the API" if absent.
|
|
74
|
+
*/
|
|
75
|
+
reasoning?: string;
|
|
68
76
|
/** Tool calls requested by the model */
|
|
69
77
|
toolCalls?: ToolCall[];
|
|
70
78
|
/** Token usage statistics */
|
|
@@ -388,9 +388,13 @@ export class AgentLLMService {
|
|
|
388
388
|
async callLLMAndParseResponse(request) {
|
|
389
389
|
try {
|
|
390
390
|
const response = await this.generator.generateContent(request);
|
|
391
|
-
// Convert response to InternalMessage format
|
|
391
|
+
// Convert response to InternalMessage format. The reasoning field must
|
|
392
|
+
// round-trip on the next turn for some providers (e.g. DeepSeek-R1
|
|
393
|
+
// rejects with "reasoning_content must be passed back to the API"
|
|
394
|
+
// otherwise).
|
|
392
395
|
const message = {
|
|
393
396
|
content: response.content,
|
|
397
|
+
...(response.reasoning && { reasoning: response.reasoning }),
|
|
394
398
|
role: 'assistant',
|
|
395
399
|
toolCalls: response.toolCalls,
|
|
396
400
|
};
|
|
@@ -423,11 +427,15 @@ export class AgentLLMService {
|
|
|
423
427
|
async callLLMAndParseResponseStreaming(request, taskId) {
|
|
424
428
|
try {
|
|
425
429
|
let accumulatedContent = '';
|
|
430
|
+
let accumulatedReasoning = '';
|
|
426
431
|
let accumulatedToolCalls = [];
|
|
427
432
|
// Stream chunks and accumulate content
|
|
428
433
|
for await (const chunk of this.generator.generateContentStream(request)) {
|
|
429
|
-
// Emit thinking/reasoning chunks as events for TUI display
|
|
434
|
+
// Emit thinking/reasoning chunks as events for TUI display + accumulate
|
|
435
|
+
// for the InternalMessage so it round-trips on the next turn (DeepSeek-R1
|
|
436
|
+
// requires reasoning_content to be passed back).
|
|
430
437
|
if (chunk.type === StreamChunkType.THINKING && chunk.reasoning) {
|
|
438
|
+
accumulatedReasoning += chunk.reasoning;
|
|
431
439
|
this.sessionEventBus.emit('llmservice:chunk', {
|
|
432
440
|
content: chunk.reasoning,
|
|
433
441
|
isComplete: chunk.isComplete,
|
|
@@ -454,6 +462,7 @@ export class AgentLLMService {
|
|
|
454
462
|
// Convert accumulated response to InternalMessage format
|
|
455
463
|
const message = {
|
|
456
464
|
content: accumulatedContent || null,
|
|
465
|
+
...(accumulatedReasoning && { reasoning: accumulatedReasoning }),
|
|
457
466
|
role: 'assistant',
|
|
458
467
|
toolCalls: accumulatedToolCalls.length > 0 ? accumulatedToolCalls : undefined,
|
|
459
468
|
};
|
|
@@ -960,8 +969,10 @@ export class AgentLLMService {
|
|
|
960
969
|
provider: this.providerId,
|
|
961
970
|
taskId: taskId || undefined,
|
|
962
971
|
});
|
|
963
|
-
// Add assistant message to context
|
|
964
|
-
|
|
972
|
+
// Add assistant message to context. Pass reasoning so it round-trips to
|
|
973
|
+
// providers that demand it (DeepSeek-R1 rejects with "reasoning_content
|
|
974
|
+
// must be passed back to the API" otherwise).
|
|
975
|
+
await this.contextManager.addAssistantMessage(content, undefined, lastMessage.reasoning);
|
|
965
976
|
return content;
|
|
966
977
|
}
|
|
967
978
|
/**
|
|
@@ -1084,9 +1095,10 @@ export class AgentLLMService {
|
|
|
1084
1095
|
}
|
|
1085
1096
|
// Emit thought events if present
|
|
1086
1097
|
this.handleThoughts(lastMessage, taskId);
|
|
1087
|
-
// Has tool calls - add assistant message with tool calls
|
|
1098
|
+
// Has tool calls - add assistant message with tool calls. Pass reasoning
|
|
1099
|
+
// so it round-trips to providers that demand it.
|
|
1088
1100
|
const assistantContent = this.extractTextContent(lastMessage);
|
|
1089
|
-
await this.contextManager.addAssistantMessage(assistantContent, lastMessage.toolCalls);
|
|
1101
|
+
await this.contextManager.addAssistantMessage(assistantContent, lastMessage.toolCalls, lastMessage.reasoning);
|
|
1090
1102
|
// Step 1: Create pending tool parts for all tool calls
|
|
1091
1103
|
for (const toolCall of lastMessage.toolCalls) {
|
|
1092
1104
|
const toolArgs = JSON.parse(toolCall.function.arguments);
|
|
@@ -140,8 +140,11 @@ export declare class ContextManager<T> {
|
|
|
140
140
|
*
|
|
141
141
|
* @param content - Message content (text or null if only tool calls)
|
|
142
142
|
* @param toolCalls - Optional tool calls made by the assistant
|
|
143
|
+
* @param reasoning - Optional reasoning/thinking trace from the model.
|
|
144
|
+
* Required to round-trip for providers like DeepSeek-R1 that reject
|
|
145
|
+
* the next turn unless reasoning_content is replayed.
|
|
143
146
|
*/
|
|
144
|
-
addAssistantMessage(content: null | string, toolCalls?: InternalMessage['toolCalls']): Promise<void>;
|
|
147
|
+
addAssistantMessage(content: null | string, toolCalls?: InternalMessage['toolCalls'], reasoning?: string): Promise<void>;
|
|
145
148
|
/**
|
|
146
149
|
* Add a system message to the conversation.
|
|
147
150
|
*
|
|
@@ -83,10 +83,14 @@ export class ContextManager {
|
|
|
83
83
|
*
|
|
84
84
|
* @param content - Message content (text or null if only tool calls)
|
|
85
85
|
* @param toolCalls - Optional tool calls made by the assistant
|
|
86
|
+
* @param reasoning - Optional reasoning/thinking trace from the model.
|
|
87
|
+
* Required to round-trip for providers like DeepSeek-R1 that reject
|
|
88
|
+
* the next turn unless reasoning_content is replayed.
|
|
86
89
|
*/
|
|
87
|
-
async addAssistantMessage(content, toolCalls) {
|
|
90
|
+
async addAssistantMessage(content, toolCalls, reasoning) {
|
|
88
91
|
const message = {
|
|
89
92
|
content,
|
|
93
|
+
...(reasoning && { reasoning }),
|
|
90
94
|
role: 'assistant',
|
|
91
95
|
toolCalls,
|
|
92
96
|
};
|
|
@@ -21,6 +21,12 @@ export declare function prependCachedSystemMessage(systemPrompt: string | undefi
|
|
|
21
21
|
export interface AiSdkContentGeneratorConfig {
|
|
22
22
|
/** Characters per token ratio for token estimation */
|
|
23
23
|
charsPerToken?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Drop the sampling request parameters (`temperature`, `top_p`, `top_k`)
|
|
26
|
+
* before calling the model. Set when targeting models that reject these
|
|
27
|
+
* (e.g. Claude Opus 4.7 returns 400 on any non-default value).
|
|
28
|
+
*/
|
|
29
|
+
excludeSamplingParameters?: boolean;
|
|
24
30
|
/** AI SDK LanguageModel instance */
|
|
25
31
|
model: LanguageModel;
|
|
26
32
|
}
|
|
@@ -34,11 +40,18 @@ export interface AiSdkContentGeneratorConfig {
|
|
|
34
40
|
*/
|
|
35
41
|
export declare class AiSdkContentGenerator implements IContentGenerator {
|
|
36
42
|
private readonly charsPerToken;
|
|
43
|
+
private readonly excludeSamplingParameters;
|
|
37
44
|
private readonly model;
|
|
38
45
|
constructor(config: AiSdkContentGeneratorConfig);
|
|
39
46
|
estimateTokensSync(content: string): number;
|
|
40
47
|
generateContent(request: GenerateContentRequest): Promise<GenerateContentResponse>;
|
|
41
48
|
generateContentStream(request: GenerateContentRequest): AsyncGenerator<GenerateContentChunk>;
|
|
49
|
+
/**
|
|
50
|
+
* Build the sampling-parameter slice for an AI SDK request. Returns an
|
|
51
|
+
* empty object when the model-level exclusion is set, so callers can spread
|
|
52
|
+
* the result into the request payload without conditionally emitting fields.
|
|
53
|
+
*/
|
|
54
|
+
private buildSamplingParams;
|
|
42
55
|
}
|
|
43
56
|
/**
|
|
44
57
|
* Extract a human-readable message from an AI SDK stream error.
|
|
@@ -37,10 +37,12 @@ export function prependCachedSystemMessage(systemPrompt, messages) {
|
|
|
37
37
|
*/
|
|
38
38
|
export class AiSdkContentGenerator {
|
|
39
39
|
charsPerToken;
|
|
40
|
+
excludeSamplingParameters;
|
|
40
41
|
model;
|
|
41
42
|
constructor(config) {
|
|
42
43
|
this.model = config.model;
|
|
43
44
|
this.charsPerToken = config.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
|
|
45
|
+
this.excludeSamplingParameters = config.excludeSamplingParameters ?? false;
|
|
44
46
|
}
|
|
45
47
|
estimateTokensSync(content) {
|
|
46
48
|
return Math.ceil(content.length / this.charsPerToken);
|
|
@@ -53,10 +55,8 @@ export class AiSdkContentGenerator {
|
|
|
53
55
|
maxRetries: 0, // RetryableContentGenerator handles retries
|
|
54
56
|
messages,
|
|
55
57
|
model: this.model,
|
|
56
|
-
|
|
58
|
+
...this.buildSamplingParams(request.config),
|
|
57
59
|
...(tools && { tools }),
|
|
58
|
-
...(request.config.topK !== undefined && { topK: request.config.topK }),
|
|
59
|
-
...(request.config.topP !== undefined && { topP: request.config.topP }),
|
|
60
60
|
});
|
|
61
61
|
// Map AI SDK tool calls to our ToolCall format
|
|
62
62
|
// Preserve thoughtSignature from providerMetadata (required by Gemini 3+ models)
|
|
@@ -77,6 +77,7 @@ export class AiSdkContentGenerator {
|
|
|
77
77
|
content: result.text,
|
|
78
78
|
finishReason: mapFinishReason(result.finishReason, toolCalls.length > 0),
|
|
79
79
|
rawResponse: result.response,
|
|
80
|
+
...(result.reasoningText && { reasoning: result.reasoningText }),
|
|
80
81
|
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
81
82
|
usage: {
|
|
82
83
|
completionTokens: result.usage.outputTokens ?? 0,
|
|
@@ -93,10 +94,8 @@ export class AiSdkContentGenerator {
|
|
|
93
94
|
maxRetries: 0,
|
|
94
95
|
messages,
|
|
95
96
|
model: this.model,
|
|
96
|
-
|
|
97
|
+
...this.buildSamplingParams(request.config),
|
|
97
98
|
...(tools && { tools }),
|
|
98
|
-
...(request.config.topK !== undefined && { topK: request.config.topK }),
|
|
99
|
-
...(request.config.topP !== undefined && { topP: request.config.topP }),
|
|
100
99
|
});
|
|
101
100
|
// Accumulate tool calls during streaming
|
|
102
101
|
const pendingToolCalls = [];
|
|
@@ -155,6 +154,20 @@ export class AiSdkContentGenerator {
|
|
|
155
154
|
}
|
|
156
155
|
}
|
|
157
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Build the sampling-parameter slice for an AI SDK request. Returns an
|
|
159
|
+
* empty object when the model-level exclusion is set, so callers can spread
|
|
160
|
+
* the result into the request payload without conditionally emitting fields.
|
|
161
|
+
*/
|
|
162
|
+
buildSamplingParams(config) {
|
|
163
|
+
if (this.excludeSamplingParameters)
|
|
164
|
+
return {};
|
|
165
|
+
return {
|
|
166
|
+
...(config.temperature !== undefined && { temperature: config.temperature }),
|
|
167
|
+
...(config.topK !== undefined && { topK: config.topK }),
|
|
168
|
+
...(config.topP !== undefined && { topP: config.topP }),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
158
171
|
}
|
|
159
172
|
/**
|
|
160
173
|
* Extract a human-readable message from an AI SDK stream error.
|
|
@@ -129,19 +129,31 @@ function convertUserMessage(msg) {
|
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* Convert an internal assistant message to AI SDK format.
|
|
132
|
-
* Handles text content and tool calls.
|
|
132
|
+
* Handles reasoning, text content, and tool calls.
|
|
133
|
+
*
|
|
134
|
+
* The reasoning part is required when the message is replayed to providers
|
|
135
|
+
* that demand the previous turn's thinking trace round-trip back — DeepSeek-R1
|
|
136
|
+
* rejects requests with "The reasoning_content in the thinking mode must be
|
|
137
|
+
* passed back to the API" if the assistant message in history lacks the
|
|
138
|
+
* reasoning that was emitted on the prior turn.
|
|
133
139
|
*/
|
|
134
140
|
function convertAssistantMessage(msg) {
|
|
135
141
|
const textContent = extractTextContent(msg);
|
|
136
142
|
const hasToolCalls = msg.toolCalls && msg.toolCalls.length > 0;
|
|
137
|
-
|
|
143
|
+
const hasReasoning = Boolean(msg.reasoning);
|
|
144
|
+
if (!textContent && !hasToolCalls && !hasReasoning) {
|
|
138
145
|
return undefined;
|
|
139
146
|
}
|
|
140
|
-
// Simple text-only case
|
|
141
|
-
if (textContent && !hasToolCalls) {
|
|
147
|
+
// Simple text-only case (no reasoning, no tools)
|
|
148
|
+
if (textContent && !hasToolCalls && !hasReasoning) {
|
|
142
149
|
return { content: textContent, role: 'assistant' };
|
|
143
150
|
}
|
|
144
151
|
const parts = [];
|
|
152
|
+
// Reasoning must come first — providers that consume it expect it at the
|
|
153
|
+
// start of the assistant turn, before any text/tool-call output.
|
|
154
|
+
if (msg.reasoning) {
|
|
155
|
+
parts.push({ text: msg.reasoning, type: 'reasoning' });
|
|
156
|
+
}
|
|
145
157
|
if (textContent) {
|
|
146
158
|
parts.push({ text: textContent, type: 'text' });
|
|
147
159
|
}
|
|
@@ -31,6 +31,7 @@ export interface ByteRoverContentGeneratorConfig {
|
|
|
31
31
|
* - Response parsing to unified format
|
|
32
32
|
*/
|
|
33
33
|
export declare class ByteRoverContentGenerator implements IContentGenerator {
|
|
34
|
+
private readonly acceptsSamplingParameters;
|
|
34
35
|
private readonly config;
|
|
35
36
|
private readonly formatter;
|
|
36
37
|
private readonly httpService;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Supports both Claude and Gemini models through the unified HTTP interface.
|
|
6
6
|
*/
|
|
7
7
|
import { FunctionCallingConfigMode } from '@google/genai';
|
|
8
|
+
import { modelAcceptsSamplingParameters } from '../../../core/domain/llm/registry.js';
|
|
8
9
|
import { ClaudeMessageFormatter } from '../formatters/claude-formatter.js';
|
|
9
10
|
import { ensureActiveLoopHasThoughtSignatures, GeminiMessageFormatter } from '../formatters/gemini-formatter.js';
|
|
10
11
|
import { ThinkingConfigManager } from '../thought-parser.js';
|
|
@@ -21,6 +22,7 @@ import { GeminiTokenizer } from '../tokenizers/gemini-tokenizer.js';
|
|
|
21
22
|
* - Response parsing to unified format
|
|
22
23
|
*/
|
|
23
24
|
export class ByteRoverContentGenerator {
|
|
25
|
+
acceptsSamplingParameters;
|
|
24
26
|
config;
|
|
25
27
|
formatter;
|
|
26
28
|
httpService;
|
|
@@ -40,6 +42,7 @@ export class ByteRoverContentGenerator {
|
|
|
40
42
|
temperature: config.temperature ?? 0.7,
|
|
41
43
|
thinkingConfig: config.thinkingConfig,
|
|
42
44
|
};
|
|
45
|
+
this.acceptsSamplingParameters = modelAcceptsSamplingParameters(this.config.model);
|
|
43
46
|
// Detect provider type from model name
|
|
44
47
|
this.providerType = this.detectProviderType(this.config.model);
|
|
45
48
|
// Initialize formatter and tokenizer based on provider type
|
|
@@ -160,7 +163,7 @@ export class ByteRoverContentGenerator {
|
|
|
160
163
|
messages,
|
|
161
164
|
model: this.config.model,
|
|
162
165
|
system: systemPrompt,
|
|
163
|
-
temperature: this.config.temperature,
|
|
166
|
+
...(this.acceptsSamplingParameters && { temperature: this.config.temperature }),
|
|
164
167
|
...(claudeTools.length > 0 && { tools: claudeTools }),
|
|
165
168
|
};
|
|
166
169
|
/* eslint-enable camelcase */
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* - Grok: `reasoning_content` or `reasoning_details` fields
|
|
10
10
|
* - Gemini via OpenRouter: `reasoning_details` array or `thoughts` field
|
|
11
11
|
* - GLM (Zhipu AI): `reasoning_content` field in API response
|
|
12
|
-
* -
|
|
12
|
+
* - DeepSeek (R1/Reasoner): `reasoning_content` field in API response (OpenAI-compatible)
|
|
13
|
+
* - Claude/MiniMax: `<think>...</think>` XML tags in content
|
|
13
14
|
*/
|
|
14
15
|
/**
|
|
15
16
|
* Reasoning format types
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* - Grok: `reasoning_content` or `reasoning_details` fields
|
|
10
10
|
* - Gemini via OpenRouter: `reasoning_details` array or `thoughts` field
|
|
11
11
|
* - GLM (Zhipu AI): `reasoning_content` field in API response
|
|
12
|
-
* -
|
|
12
|
+
* - DeepSeek (R1/Reasoner): `reasoning_content` field in API response (OpenAI-compatible)
|
|
13
|
+
* - Claude/MiniMax: `<think>...</think>` XML tags in content
|
|
13
14
|
*/
|
|
14
15
|
/**
|
|
15
16
|
* Get model capabilities for a given model ID.
|
|
@@ -95,13 +96,14 @@ export function getModelCapabilities(modelId) {
|
|
|
95
96
|
reasoningFormat: 'think-tags',
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
|
-
// DeepSeek models
|
|
99
|
+
// DeepSeek models — reasoning models stream `reasoning_content` natively
|
|
100
|
+
// (OpenAI-compatible field), not <think> tags.
|
|
99
101
|
if (id.includes('deepseek')) {
|
|
100
|
-
// DeepSeek-R1 and reasoning models
|
|
101
102
|
if (id.includes('r1') || id.includes('reasoner')) {
|
|
102
103
|
return {
|
|
103
104
|
reasoning: true,
|
|
104
|
-
|
|
105
|
+
reasoningField: 'reasoning_content',
|
|
106
|
+
reasoningFormat: 'native-field',
|
|
105
107
|
};
|
|
106
108
|
}
|
|
107
109
|
return {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Direct access to Claude models via @ai-sdk/anthropic.
|
|
5
5
|
*/
|
|
6
6
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
7
|
+
import { modelAcceptsSamplingParameters } from '../../../core/domain/llm/registry.js';
|
|
7
8
|
import { AiSdkContentGenerator } from '../generators/ai-sdk-content-generator.js';
|
|
8
9
|
export const anthropicProvider = {
|
|
9
10
|
apiKeyUrl: 'https://console.anthropic.com/settings/keys',
|
|
@@ -14,6 +15,7 @@ export const anthropicProvider = {
|
|
|
14
15
|
const provider = createAnthropic({ apiKey: config.apiKey });
|
|
15
16
|
return new AiSdkContentGenerator({
|
|
16
17
|
charsPerToken: 3.5,
|
|
18
|
+
excludeSamplingParameters: !modelAcceptsSamplingParameters(config.model),
|
|
17
19
|
model: provider(config.model),
|
|
18
20
|
});
|
|
19
21
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Provider Module
|
|
3
|
+
*
|
|
4
|
+
* Access to DeepSeek V3 (deepseek-chat) and R1 (deepseek-reasoner) via their
|
|
5
|
+
* OpenAI-compatible API. The reasoner model streams thinking through the
|
|
6
|
+
* native `reasoning_content` field rather than `<think>` tags — see
|
|
7
|
+
* model-capabilities.ts for the parser routing.
|
|
8
|
+
*/
|
|
9
|
+
import type { ProviderModule } from './types.js';
|
|
10
|
+
export declare const deepseekProvider: ProviderModule;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Provider Module
|
|
3
|
+
*
|
|
4
|
+
* Access to DeepSeek V3 (deepseek-chat) and R1 (deepseek-reasoner) via their
|
|
5
|
+
* OpenAI-compatible API. The reasoner model streams thinking through the
|
|
6
|
+
* native `reasoning_content` field rather than `<think>` tags — see
|
|
7
|
+
* model-capabilities.ts for the parser routing.
|
|
8
|
+
*/
|
|
9
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
10
|
+
import { AiSdkContentGenerator } from '../generators/ai-sdk-content-generator.js';
|
|
11
|
+
export const deepseekProvider = {
|
|
12
|
+
apiKeyUrl: 'https://platform.deepseek.com/api_keys',
|
|
13
|
+
authType: 'api-key',
|
|
14
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
15
|
+
category: 'other',
|
|
16
|
+
createGenerator(config) {
|
|
17
|
+
const provider = createOpenAICompatible({
|
|
18
|
+
apiKey: config.apiKey,
|
|
19
|
+
baseURL: 'https://api.deepseek.com/v1',
|
|
20
|
+
name: 'deepseek',
|
|
21
|
+
});
|
|
22
|
+
return new AiSdkContentGenerator({
|
|
23
|
+
model: provider.chatModel(config.model),
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
defaultModel: 'deepseek-chat',
|
|
27
|
+
description: 'DeepSeek V3 and R1 reasoning models',
|
|
28
|
+
envVars: ['DEEPSEEK_API_KEY'],
|
|
29
|
+
id: 'deepseek',
|
|
30
|
+
name: 'DeepSeek',
|
|
31
|
+
priority: 19,
|
|
32
|
+
providerType: 'openai',
|
|
33
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLM Coding Plan (Z.AI) Provider Module
|
|
3
|
+
*
|
|
4
|
+
* Same Z.AI account as the standard `glm` provider but routes through the
|
|
5
|
+
* coding-plan endpoint so subscription quota is consumed instead of
|
|
6
|
+
* pay-per-token billing.
|
|
7
|
+
*/
|
|
8
|
+
import type { ProviderModule } from './types.js';
|
|
9
|
+
export declare const glmCodingPlanProvider: ProviderModule;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLM Coding Plan (Z.AI) Provider Module
|
|
3
|
+
*
|
|
4
|
+
* Same Z.AI account as the standard `glm` provider but routes through the
|
|
5
|
+
* coding-plan endpoint so subscription quota is consumed instead of
|
|
6
|
+
* pay-per-token billing.
|
|
7
|
+
*/
|
|
8
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
9
|
+
import { AiSdkContentGenerator } from '../generators/ai-sdk-content-generator.js';
|
|
10
|
+
export const glmCodingPlanProvider = {
|
|
11
|
+
apiKeyUrl: 'https://z.ai/manage-apikey/apikey-list',
|
|
12
|
+
authType: 'api-key',
|
|
13
|
+
baseUrl: 'https://api.z.ai/api/coding/paas/v4',
|
|
14
|
+
category: 'other',
|
|
15
|
+
createGenerator(config) {
|
|
16
|
+
const provider = createOpenAICompatible({
|
|
17
|
+
apiKey: config.apiKey,
|
|
18
|
+
baseURL: 'https://api.z.ai/api/coding/paas/v4',
|
|
19
|
+
name: 'glm-coding-plan',
|
|
20
|
+
});
|
|
21
|
+
return new AiSdkContentGenerator({
|
|
22
|
+
model: provider.chatModel(config.model),
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
defaultModel: 'glm-4.7',
|
|
26
|
+
description: 'GLM models on the Z.AI Coding Plan subscription',
|
|
27
|
+
envVars: ['ZHIPU_API_KEY'],
|
|
28
|
+
id: 'glm-coding-plan',
|
|
29
|
+
name: 'GLM Coding Plan (Z.AI)',
|
|
30
|
+
priority: 17.5,
|
|
31
|
+
providerType: 'openai',
|
|
32
|
+
};
|
|
@@ -10,6 +10,8 @@ import { byteroverProvider } from './byterover.js';
|
|
|
10
10
|
import { cerebrasProvider } from './cerebras.js';
|
|
11
11
|
import { cohereProvider } from './cohere.js';
|
|
12
12
|
import { deepinfraProvider } from './deepinfra.js';
|
|
13
|
+
import { deepseekProvider } from './deepseek.js';
|
|
14
|
+
import { glmCodingPlanProvider } from './glm-coding-plan.js';
|
|
13
15
|
import { glmProvider } from './glm.js';
|
|
14
16
|
import { googleProvider } from './google.js';
|
|
15
17
|
import { groqProvider } from './groq.js';
|
|
@@ -33,7 +35,9 @@ const PROVIDER_MODULES = {
|
|
|
33
35
|
cerebras: cerebrasProvider,
|
|
34
36
|
cohere: cohereProvider,
|
|
35
37
|
deepinfra: deepinfraProvider,
|
|
38
|
+
deepseek: deepseekProvider,
|
|
36
39
|
glm: glmProvider,
|
|
40
|
+
'glm-coding-plan': glmCodingPlanProvider,
|
|
37
41
|
google: googleProvider,
|
|
38
42
|
groq: groqProvider,
|
|
39
43
|
minimax: minimaxProvider,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Access 200+ models via the OpenRouter aggregator using @openrouter/ai-sdk-provider.
|
|
5
5
|
*/
|
|
6
6
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
7
|
+
import { modelAcceptsSamplingParameters } from '../../../core/domain/llm/registry.js';
|
|
7
8
|
import { AiSdkContentGenerator } from '../generators/ai-sdk-content-generator.js';
|
|
8
9
|
export const openrouterProvider = {
|
|
9
10
|
apiKeyUrl: 'https://openrouter.ai/keys',
|
|
@@ -13,6 +14,7 @@ export const openrouterProvider = {
|
|
|
13
14
|
createGenerator(config) {
|
|
14
15
|
const provider = createOpenRouter({ apiKey: config.apiKey });
|
|
15
16
|
return new AiSdkContentGenerator({
|
|
17
|
+
excludeSamplingParameters: !modelAcceptsSamplingParameters(config.model),
|
|
16
18
|
model: provider.chat(config.model),
|
|
17
19
|
});
|
|
18
20
|
},
|