byterover-cli 3.10.2 → 3.11.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 (90) hide show
  1. package/README.md +4 -2
  2. package/dist/agent/core/domain/llm/registry.d.ts +12 -0
  3. package/dist/agent/core/domain/llm/registry.js +49 -0
  4. package/dist/agent/core/domain/llm/types.d.ts +6 -0
  5. package/dist/agent/core/interfaces/i-content-generator.d.ts +8 -0
  6. package/dist/agent/infra/llm/agent-llm-service.js +18 -6
  7. package/dist/agent/infra/llm/context/context-manager.d.ts +4 -1
  8. package/dist/agent/infra/llm/context/context-manager.js +5 -1
  9. package/dist/agent/infra/llm/generators/ai-sdk-content-generator.d.ts +13 -0
  10. package/dist/agent/infra/llm/generators/ai-sdk-content-generator.js +19 -6
  11. package/dist/agent/infra/llm/generators/ai-sdk-message-converter.js +16 -4
  12. package/dist/agent/infra/llm/generators/byterover-content-generator.d.ts +1 -0
  13. package/dist/agent/infra/llm/generators/byterover-content-generator.js +4 -1
  14. package/dist/agent/infra/llm/model-capabilities.d.ts +2 -1
  15. package/dist/agent/infra/llm/model-capabilities.js +6 -4
  16. package/dist/agent/infra/llm/providers/anthropic.js +2 -0
  17. package/dist/agent/infra/llm/providers/deepseek.d.ts +10 -0
  18. package/dist/agent/infra/llm/providers/deepseek.js +33 -0
  19. package/dist/agent/infra/llm/providers/glm-coding-plan.d.ts +9 -0
  20. package/dist/agent/infra/llm/providers/glm-coding-plan.js +32 -0
  21. package/dist/agent/infra/llm/providers/index.js +4 -0
  22. package/dist/agent/infra/llm/providers/openrouter.js +2 -0
  23. package/dist/oclif/commands/query.js +7 -1
  24. package/dist/oclif/lib/task-client.d.ts +9 -0
  25. package/dist/oclif/lib/task-client.js +11 -1
  26. package/dist/server/core/domain/entities/provider-registry.js +26 -0
  27. package/dist/server/infra/daemon/brv-server.js +4 -0
  28. package/dist/server/infra/http/provider-model-fetcher-registry.js +5 -0
  29. package/dist/server/infra/http/provider-model-fetchers.js +54 -27
  30. package/dist/server/infra/mcp/mcp-server.d.ts +6 -0
  31. package/dist/server/infra/mcp/mcp-server.js +15 -3
  32. package/dist/server/infra/mcp/tools/brv-curate-tool.d.ts +1 -1
  33. package/dist/server/infra/mcp/tools/brv-curate-tool.js +4 -2
  34. package/dist/server/infra/mcp/tools/brv-query-tool.d.ts +1 -1
  35. package/dist/server/infra/mcp/tools/brv-query-tool.js +3 -2
  36. package/dist/server/infra/mcp/tools/drift-footer.d.ts +8 -0
  37. package/dist/server/infra/mcp/tools/drift-footer.js +16 -0
  38. package/dist/server/infra/process/connection-coordinator.d.ts +7 -0
  39. package/dist/server/infra/process/connection-coordinator.js +5 -0
  40. package/dist/server/infra/process/query-log-handler.d.ts +6 -0
  41. package/dist/server/infra/process/query-log-handler.js +23 -0
  42. package/dist/server/infra/process/transport-handlers.d.ts +5 -0
  43. package/dist/server/infra/process/transport-handlers.js +1 -0
  44. package/dist/tui/components/header.js +7 -1
  45. package/dist/tui/components/logo.d.ts +6 -0
  46. package/dist/tui/components/logo.js +18 -5
  47. package/dist/tui/features/transport/components/transport-initializer.js +8 -2
  48. package/dist/tui/stores/transport-store.d.ts +8 -0
  49. package/dist/tui/stores/transport-store.js +2 -0
  50. package/dist/webui/assets/index--sXE__bc.css +1 -0
  51. package/dist/webui/assets/{index-thSZZahh.js → index-Bkkx961b.js} +63 -63
  52. package/dist/webui/index.html +2 -2
  53. package/dist/webui/sw.js +1 -1
  54. package/dist/webui/workbox-9c191d2f.js +1 -0
  55. package/node_modules/@campfirein/brv-transport-client/dist/core/interfaces/i-client.d.ts +14 -0
  56. package/node_modules/@campfirein/brv-transport-client/dist/core/interfaces/i-client.d.ts.map +1 -1
  57. package/node_modules/@campfirein/brv-transport-client/dist/index.d.ts +1 -0
  58. package/node_modules/@campfirein/brv-transport-client/dist/index.d.ts.map +1 -1
  59. package/node_modules/@campfirein/brv-transport-client/dist/index.js +2 -0
  60. package/node_modules/@campfirein/brv-transport-client/dist/index.js.map +1 -1
  61. package/node_modules/@campfirein/brv-transport-client/dist/infra/client-factory.d.ts.map +1 -1
  62. package/node_modules/@campfirein/brv-transport-client/dist/infra/client-factory.js +5 -0
  63. package/node_modules/@campfirein/brv-transport-client/dist/infra/client-factory.js.map +1 -1
  64. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-discovery-sync.d.ts +9 -7
  65. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-discovery-sync.d.ts.map +1 -1
  66. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-discovery-sync.js +11 -9
  67. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-discovery-sync.js.map +1 -1
  68. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-health.d.ts +23 -6
  69. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-health.d.ts.map +1 -1
  70. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-health.js +11 -5
  71. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-health.js.map +1 -1
  72. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-spawner.js +7 -7
  73. package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-spawner.js.map +1 -1
  74. package/node_modules/@campfirein/brv-transport-client/dist/infra/schemas/schemas.d.ts +7 -0
  75. package/node_modules/@campfirein/brv-transport-client/dist/infra/schemas/schemas.d.ts.map +1 -1
  76. package/node_modules/@campfirein/brv-transport-client/dist/infra/schemas/schemas.js +5 -0
  77. package/node_modules/@campfirein/brv-transport-client/dist/infra/schemas/schemas.js.map +1 -1
  78. package/node_modules/@campfirein/brv-transport-client/dist/infra/socket-io-client.d.ts +8 -0
  79. package/node_modules/@campfirein/brv-transport-client/dist/infra/socket-io-client.d.ts.map +1 -1
  80. package/node_modules/@campfirein/brv-transport-client/dist/infra/socket-io-client.js +15 -0
  81. package/node_modules/@campfirein/brv-transport-client/dist/infra/socket-io-client.js.map +1 -1
  82. package/node_modules/@campfirein/brv-transport-client/dist/infra/version-utils.d.ts +35 -0
  83. package/node_modules/@campfirein/brv-transport-client/dist/infra/version-utils.d.ts.map +1 -0
  84. package/node_modules/@campfirein/brv-transport-client/dist/infra/version-utils.js +59 -0
  85. package/node_modules/@campfirein/brv-transport-client/dist/infra/version-utils.js.map +1 -0
  86. package/node_modules/@campfirein/brv-transport-client/package.json +1 -1
  87. package/oclif.manifest.json +206 -206
  88. package/package.json +4 -4
  89. package/dist/webui/assets/index-CvcqpMYn.css +0 -1
  90. 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
- - 🤖 18 LLM providers (Anthropic, OpenAI, Google, Groq, Mistral, xAI, and more)
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 18 LLM providers out of the box. Connect and switch providers from the dashboard, or use `brv providers connect` / `brv providers switch`.
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
- await this.contextManager.addAssistantMessage(content);
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
- temperature: request.config.temperature,
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
- temperature: request.config.temperature,
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
- if (!textContent && !hasToolCalls) {
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
- * - Claude/DeepSeek/MiniMax: `<think>...</think>` XML tags in content
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
- * - Claude/DeepSeek/MiniMax: `<think>...</think>` XML tags in content
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 use think tags
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
- reasoningFormat: 'think-tags',
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
  },
@@ -110,7 +110,7 @@ Bad:
110
110
  client,
111
111
  command: 'query',
112
112
  format,
113
- onCompleted: ({ result, taskId: tid }) => {
113
+ onCompleted: ({ durationMs, matchedDocs, result, taskId: tid, tier, topScore }) => {
114
114
  const previousResult = finalResult;
115
115
  // Always prefer the completed payload — it carries the attribution footer
116
116
  // that may not be present in the earlier llmservice:response event.
@@ -133,11 +133,17 @@ Bad:
133
133
  if (format === 'json') {
134
134
  writeJsonResponse({
135
135
  command: 'query',
136
+ // Recall metadata is only present on query tasks; older daemons omit it. Spread
137
+ // conditionally so JSON consumers do not see undefined keys.
136
138
  data: {
139
+ ...(durationMs === undefined ? {} : { durationMs }),
137
140
  event: 'completed',
141
+ ...(matchedDocs === undefined ? {} : { matchedDocs }),
138
142
  result: finalResult,
139
143
  status: 'completed',
140
144
  taskId: tid,
145
+ ...(tier === undefined ? {} : { tier }),
146
+ ...(topScore === undefined ? {} : { topScore }),
141
147
  },
142
148
  success: true,
143
149
  });