@doist/doistbot-cli 1.0.9 → 1.0.10

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.
@@ -138,7 +138,7 @@ export async function runAuthSetup(dependencies = {}) {
138
138
  deps.writeLine('OpenRouter not configured. Skipping optional OpenRouter provider.');
139
139
  return;
140
140
  }
141
- deps.writeLine(`Doistbot can use OpenRouter for DeepSeek, Z.ai, and other open models. Paste an OpenRouter API key, or ${formatSkipInstruction()}.`);
141
+ deps.writeLine(`Doistbot can use OpenRouter for DeepSeek, Grok, and other open models. Paste an OpenRouter API key, or ${formatSkipInstruction()}.`);
142
142
  const openRouterApiKey = (await deps.promptHiddenInput(API_KEY_PROMPT)).trim();
143
143
  if (!openRouterApiKey) {
144
144
  deps.writeLine();
@@ -6,6 +6,7 @@ const JSON_SCHEMA_VERSION = 1;
6
6
  const PROVIDER_DISPLAY_NAMES = {
7
7
  deepseek: 'DeepSeek',
8
8
  gemini: 'Gemini',
9
+ grok: 'Grok',
9
10
  zai: 'Z.ai',
10
11
  openai: 'OpenAI',
11
12
  };
package/dist/config.js CHANGED
@@ -10,7 +10,7 @@ const ALL_REVIEW_FOCUSES = [
10
10
  'tests',
11
11
  'standards',
12
12
  ];
13
- const OPENROUTER_REVIEW_MODELS = ['deepseek', 'zai'];
13
+ const OPENROUTER_REVIEW_MODELS = ['deepseek', 'grok'];
14
14
  const CLI_GEMINI_REVIEW_MODEL = 'gemini-3.7-flash';
15
15
  function createCliGeminiCredential(apiKey) {
16
16
  return {
@@ -49,6 +49,7 @@ export async function getApiKeys(env = process.env, auth = {
49
49
  }
50
50
  if (openRouterApiKey) {
51
51
  apiKeys.deepseek = { apiKey: openRouterApiKey, piProviderId: 'openrouter' };
52
+ apiKeys.grok = { apiKey: openRouterApiKey, piProviderId: 'openrouter' };
52
53
  apiKeys.zai = { apiKey: openRouterApiKey, piProviderId: 'openrouter' };
53
54
  }
54
55
  if (openAICodexAuthState === 'ready') {
@@ -76,7 +77,7 @@ export function formatMissingRequiredCliAuthProviders(missingProviders) {
76
77
  }
77
78
  export function getCliReviewRouting(apiKeys) {
78
79
  const hasGemini = Boolean(apiKeys.gemini);
79
- const hasOpenRouter = Boolean(apiKeys.deepseek && apiKeys.zai);
80
+ const hasOpenRouter = Boolean(apiKeys.deepseek && apiKeys.grok);
80
81
  if (hasGemini && hasOpenRouter) {
81
82
  return {
82
83
  reviewModelsEnabled: ['openai', ...OPENROUTER_REVIEW_MODELS],
package/dist/terminal.js CHANGED
@@ -116,6 +116,7 @@ export async function withSpinner(message, task, terminalConfig = getDefaultTerm
116
116
  const PROVIDER_DISPLAY_NAMES = {
117
117
  deepseek: 'DeepSeek',
118
118
  gemini: 'Gemini',
119
+ grok: 'Grok',
119
120
  zai: 'Z.ai',
120
121
  openai: 'OpenAI',
121
122
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/doistbot-cli",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Local self-review CLI for Doistbot",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -50,7 +50,7 @@
50
50
  "commander": "14.0.3",
51
51
  "js-yaml": "4.3.1",
52
52
  "octokit": "5.0.5",
53
- "undici": "8.9.0",
53
+ "undici": "8.10.0",
54
54
  "yocto-spinner": "1.2.0"
55
55
  },
56
56
  "devDependencies": {
@@ -1,7 +1,7 @@
1
1
  import type { Logger } from 'doistbot-logging';
2
2
  import type { Octokit } from 'octokit';
3
3
  export declare const CONFIG_FILE_PATH = ".github/doistbot.yml";
4
- export declare const DEFAULT_REVIEW_MODELS: readonly ["openai", "deepseek", "zai"];
4
+ export declare const DEFAULT_REVIEW_MODELS: readonly ["openai", "deepseek", "grok"];
5
5
  export type DefaultReviewModel = (typeof DEFAULT_REVIEW_MODELS)[number];
6
6
  export type RepoConfig = {
7
7
  review?: {
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { load as yamlLoad } from 'js-yaml';
4
4
  export const CONFIG_FILE_PATH = '.github/doistbot.yml';
5
- export const DEFAULT_REVIEW_MODELS = ['openai', 'deepseek', 'zai'];
5
+ export const DEFAULT_REVIEW_MODELS = ['openai', 'deepseek', 'grok'];
6
6
  function parseRepoConfigContent(content) {
7
7
  return yamlLoad(content);
8
8
  }
@@ -11,6 +11,8 @@ function toTaskMetricBaseModel(model) {
11
11
  case 'gemini':
12
12
  case 'google':
13
13
  return 'google';
14
+ case 'grok':
15
+ return 'grok';
14
16
  case 'zai':
15
17
  return 'zai';
16
18
  case 'openai':
@@ -6,18 +6,21 @@ import { fileURLToPath } from 'url';
6
6
  export const PI_PROVIDER_BY_DOISTBOT_PROVIDER = {
7
7
  deepseek: 'openrouter',
8
8
  gemini: 'google',
9
+ grok: 'openrouter',
9
10
  zai: 'openrouter',
10
11
  openai: 'openai',
11
12
  };
12
13
  export const DEFAULT_PI_MODELS = {
13
14
  deepseek: 'deepseek-v4-pro',
14
15
  gemini: 'gemini-3.1-pro-preview',
16
+ grok: 'grok-4.6',
15
17
  zai: 'glm5.2',
16
18
  openai: 'gpt-5.6-terra',
17
19
  };
18
20
  export const DEFAULT_OPENROUTER_MODELS = {
19
21
  deepseek: 'deepseek/deepseek-v4-pro-0813',
20
22
  gemini: 'google/gemini-3.1-pro-preview',
23
+ grok: 'x-ai/grok-4.6',
21
24
  zai: 'z-ai/glm-5.2',
22
25
  };
23
26
  // Gemini flash fallback model.
@@ -25,6 +28,7 @@ export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-3.7-flash';
25
28
  export const MAX_PI_THINKING_BY_PROVIDER = {
26
29
  deepseek: 'high',
27
30
  gemini: 'high',
31
+ grok: 'high',
28
32
  zai: 'high',
29
33
  openai: 'xhigh',
30
34
  };
@@ -72,7 +72,7 @@ export function recordEvent(name, meta) {
72
72
  /**
73
73
  * Maps a doistbot provider key to the OTel `gen_ai.provider.name` value
74
74
  * (docs/sandbox-instrumentation.md). Only `gemini` has a standard name
75
- * (`gcp.gemini`); the OpenRouter-routed keys (`zai`, `deepseek`) have none, so
75
+ * (`gcp.gemini`); the OpenRouter-routed keys (`grok`, `zai`, `deepseek`) have none, so
76
76
  * they pass through. Single source so every span site stays consistent.
77
77
  */
78
78
  export function genAiProviderName(provider) {
@@ -11,6 +11,7 @@ export * from './types.js';
11
11
  const providers = {
12
12
  deepseek: new OpenRouterPiProvider('deepseek'),
13
13
  gemini: new GeminiProvider(),
14
+ grok: new OpenRouterPiProvider('grok'),
14
15
  zai: new OpenRouterPiProvider('zai'),
15
16
  openai: new OpenAIProvider(),
16
17
  };
@@ -4,11 +4,11 @@ import { buildPiImageContent, createPiInvoker, DEFAULT_OPENROUTER_MODELS } from
4
4
  import { withPhase } from '../../core/tracing.js';
5
5
  const ISSUE_SUMMARIZE_TIMEOUT_MS = 5 * 60 * 1000;
6
6
  const ISSUE_SUMMARIZE_MULTIMODAL_MODEL = 'google/gemini-2.5-pro';
7
- const SUMMARIZE_TEXT_PROVIDER = 'zai';
8
- // Single source of truth: inherit the repo-wide ZAI default so a default-model
7
+ const SUMMARIZE_TEXT_PROVIDER = 'grok';
8
+ // Single source of truth: inherit the repo-wide Grok default so a default-model
9
9
  // bump in core/pi.ts moves summarize too, and the executed model matches the
10
10
  // recorded gen_ai.request.model.
11
- const SUMMARIZE_TEXT_MODEL = DEFAULT_OPENROUTER_MODELS.zai;
11
+ const SUMMARIZE_TEXT_MODEL = DEFAULT_OPENROUTER_MODELS.grok;
12
12
  const SUMMARIZE_MULTIMODAL_PROVIDER = 'gcp.gemini';
13
13
  // One model span per actual model.invoke. The final summary uses
14
14
  // `issue-summarize.model`; each chunk summary uses `issue-summarize.chunk`.
@@ -16,7 +16,7 @@ const SUMMARIZE_MODEL_PHASE = 'issue-summarize.model';
16
16
  const SUMMARIZE_CHUNK_PHASE = 'issue-summarize.chunk';
17
17
  export function createIssueSummarizeModel(apiKey) {
18
18
  return createPiInvoker({
19
- provider: 'zai',
19
+ provider: 'grok',
20
20
  apiKey,
21
21
  piProviderId: 'openrouter',
22
22
  cwd: process.cwd(),
@@ -10,13 +10,13 @@ import { buildGeminiFlashReviewSummaryModel } from '../summary-model.js';
10
10
  import { dedupeMultiFocusFindings } from './dedupe.js';
11
11
  import { buildGeminiFlashCredential, getApiKeys, getAvailableModels, getEnabledReviewModels, getOpenRouterReviewCredential, getProviderFailures, requestModelReview, } from './shared.js';
12
12
  const DEFAULT_REVIEW_MODELS_BY_FOCUS = {
13
- general: ['deepseek', 'zai', 'openai'],
14
- security: ['deepseek', 'zai', 'openai'],
15
- reuse: ['deepseek', 'zai', 'openai'],
16
- quality: ['deepseek', 'zai', 'openai'],
17
- efficiency: ['deepseek', 'zai', 'openai'],
18
- tests: ['deepseek', 'zai'],
19
- standards: ['deepseek', 'zai'],
13
+ general: ['deepseek', 'grok', 'openai'],
14
+ security: ['deepseek', 'grok', 'openai'],
15
+ reuse: ['deepseek', 'grok', 'openai'],
16
+ quality: ['deepseek', 'grok', 'openai'],
17
+ efficiency: ['deepseek', 'grok', 'openai'],
18
+ tests: ['deepseek', 'grok'],
19
+ standards: ['deepseek', 'grok'],
20
20
  };
21
21
  // Default Gemini-flash pass on failure. tests/standards keep no fallback unless
22
22
  // a caller opts into a wider fallback set.
@@ -28,7 +28,7 @@ const DEFAULT_GEMINI_FLASH_FALLBACK_FOCUSES = [
28
28
  'efficiency',
29
29
  ];
30
30
  // Open models whose failed pass triggers that fallback.
31
- const GEMINI_FLASH_FALLBACK_MODELS = ['deepseek', 'zai'];
31
+ const GEMINI_FLASH_FALLBACK_MODELS = ['deepseek', 'grok'];
32
32
  function lowPrioritySummaryNotice(count) {
33
33
  return count === 1
34
34
  ? 'I also left one optional follow-up note in the details below.'
@@ -347,7 +347,7 @@ export async function runMultiFocusReviewEngine({ ctx, prompt, lineLookup, prTit
347
347
  const routedFindings = routeLowPriorityFindings(dedupedFindings, lowPriorityFindingPlacement);
348
348
  const geminiSummaryFallback = buildGeminiFlashReviewSummaryModel(apiKeys.gemini);
349
349
  const summaryModel = ctx.reviewSummaryModel ??
350
- (apiKeys.zai ? { provider: 'zai', credential: apiKeys.zai } : undefined);
350
+ (apiKeys.grok ? { provider: 'grok', credential: apiKeys.grok } : undefined);
351
351
  const review = await buildMultiFocusReviewOutput({
352
352
  inlineFindings: routedFindings.inlineFindings,
353
353
  summaryFindings: routedFindings.summaryFindings,
@@ -22,6 +22,8 @@ export function getAvailableModels(apiKeys) {
22
22
  models.push('openai');
23
23
  if (hasReviewProviderCredential(apiKeys.deepseek))
24
24
  models.push('deepseek');
25
+ if (hasReviewProviderCredential(apiKeys.grok))
26
+ models.push('grok');
25
27
  if (hasReviewProviderCredential(apiKeys.zai))
26
28
  models.push('zai');
27
29
  return models;
@@ -51,7 +51,7 @@ Keep it human and compact: the opener should be 1 sentence, or 2 only if the PR
51
51
  Respond with only valid JSON in this exact shape:
52
52
  {"summary":"<your summary>","comments":[]}`;
53
53
  }
54
- export async function summarizeReviewFindings({ credential, provider = 'zai', modelName, fallbackModels = [], prTitle, prBody, findings, reviewObservations, workspaceDir, requestReview = requestReviewSummary, }) {
54
+ export async function summarizeReviewFindings({ credential, provider = 'grok', modelName, fallbackModels = [], prTitle, prBody, findings, reviewObservations, workspaceDir, requestReview = requestReviewSummary, }) {
55
55
  const prompt = buildReviewSummaryPrompt({
56
56
  prTitle,
57
57
  prBody,
@@ -15,7 +15,8 @@ import { REVIEW_FOCUS_DEFINITIONS } from './multi-focus-prompt.js';
15
15
  import { appendReviewFeedbackLink } from './review-feedback.js';
16
16
  import { runSharedReview } from './runner.js';
17
17
  const REVIEW_EVENT = 'COMMENT';
18
- const REQUIRED_PRODUCTION_REVIEW_MODELS = getDefaultReviewModels();
18
+ const DEFAULT_PRODUCTION_REVIEW_MODELS = getDefaultReviewModels();
19
+ const SUPPORTED_PRODUCTION_REVIEW_MODELS = ['openai', 'deepseek', 'grok', 'zai'];
19
20
  const REVIEW_FOCUSES = REVIEW_FOCUS_DEFINITIONS.map((focus) => focus.name);
20
21
  function optionalEnv(name) {
21
22
  const value = process.env[name]?.trim();
@@ -43,8 +44,8 @@ function parseContext() {
43
44
  export function parseReviewModelsEnv(value) {
44
45
  return parseCsvAllowList({
45
46
  value,
46
- allowedValues: REQUIRED_PRODUCTION_REVIEW_MODELS,
47
- fallback: REQUIRED_PRODUCTION_REVIEW_MODELS,
47
+ allowedValues: SUPPORTED_PRODUCTION_REVIEW_MODELS,
48
+ fallback: DEFAULT_PRODUCTION_REVIEW_MODELS,
48
49
  envName: 'REVIEW_MODELS_ENABLED',
49
50
  });
50
51
  }
@@ -253,6 +254,7 @@ async function main() {
253
254
  }
254
255
  if (ctx.openRouterApiKey) {
255
256
  reviewApiKeys.deepseek = createOpenRouterCredential(ctx.openRouterApiKey);
257
+ reviewApiKeys.grok = createOpenRouterCredential(ctx.openRouterApiKey);
256
258
  reviewApiKeys.zai = createOpenRouterCredential(ctx.openRouterApiKey);
257
259
  }
258
260
  if (ctx.openaiApiKey) {
@@ -6,7 +6,7 @@ import { genAiProviderName, withPhase } from '../../core/tracing.js';
6
6
  import { resolveReviewProviderCredential } from '../../providers/credentials.js';
7
7
  import { extractJsonCandidatesFromResponse } from '../../providers/helpers.js';
8
8
  const REVIEW_SUMMARY_TIMEOUT_MS = 5 * 60 * 1000;
9
- const REVIEW_SUMMARY_THINKING_LEVEL = 'medium';
9
+ const DEFAULT_REVIEW_SUMMARY_THINKING_LEVEL = 'medium';
10
10
  // One model span per summary attempt; a fallback chain emits one span per model.
11
11
  const SUMMARY_PHASE = 'review.summary';
12
12
  const DIFF_SIDE = {
@@ -56,7 +56,7 @@ function getRequestFailureReason(result) {
56
56
  ? 'no_assistant_response'
57
57
  : 'request_failed';
58
58
  }
59
- export async function requestReviewSummary({ credential, provider = 'zai', modelName, prompt, workspaceDir, }) {
59
+ export async function requestReviewSummary({ credential, provider = 'grok', modelName, prompt, workspaceDir, }) {
60
60
  const resolvedCredential = resolveReviewProviderCredential(credential);
61
61
  if (!resolvedCredential) {
62
62
  return failedReviewSummaryRequest({ reason: 'missing_credential' });
@@ -93,12 +93,13 @@ export async function requestReviewSummary({ credential, provider = 'zai', model
93
93
  }
94
94
  async function runReviewSummaryRequest({ provider, resolvedCredential, resolvedModelName, logModelName, prompt, workspaceDir, }) {
95
95
  try {
96
+ const thinkingLevel = provider === 'grok' ? 'high' : DEFAULT_REVIEW_SUMMARY_THINKING_LEVEL;
96
97
  logger.info('Review summary model request', {
97
98
  provider,
98
99
  modelName: logModelName,
99
100
  promptLength: prompt.length,
100
101
  timeoutMs: REVIEW_SUMMARY_TIMEOUT_MS,
101
- thinkingLevel: REVIEW_SUMMARY_THINKING_LEVEL,
102
+ thinkingLevel,
102
103
  });
103
104
  const result = await invokePiPrompt({
104
105
  provider,
@@ -111,7 +112,7 @@ async function runReviewSummaryRequest({ provider, resolvedCredential, resolvedM
111
112
  cwd: workspaceDir ?? getWorkspaceDir(),
112
113
  timeoutMs: REVIEW_SUMMARY_TIMEOUT_MS,
113
114
  capabilityPreset: 'read-only',
114
- thinkingLevel: REVIEW_SUMMARY_THINKING_LEVEL,
115
+ thinkingLevel,
115
116
  });
116
117
  if (!result.ok) {
117
118
  const reason = getRequestFailureReason(result);
@@ -4,6 +4,8 @@ export function getReviewThinkingLevel(model) {
4
4
  return 'high';
5
5
  case 'openai':
6
6
  return 'high';
7
+ case 'grok':
8
+ return 'high';
7
9
  case 'zai':
8
10
  return 'high';
9
11
  case 'gemini':
@@ -1,7 +1,7 @@
1
1
  import type { Logger } from 'doistbot-logging';
2
2
  import type { Octokit } from 'octokit';
3
3
  export declare const CONFIG_FILE_PATH = ".github/doistbot.yml";
4
- export declare const DEFAULT_REVIEW_MODELS: readonly ["openai", "deepseek", "zai"];
4
+ export declare const DEFAULT_REVIEW_MODELS: readonly ["openai", "deepseek", "grok"];
5
5
  export type DefaultReviewModel = (typeof DEFAULT_REVIEW_MODELS)[number];
6
6
  export type RepoConfig = {
7
7
  review?: {
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { load as yamlLoad } from 'js-yaml';
4
4
  export const CONFIG_FILE_PATH = '.github/doistbot.yml';
5
- export const DEFAULT_REVIEW_MODELS = ['openai', 'deepseek', 'zai'];
5
+ export const DEFAULT_REVIEW_MODELS = ['openai', 'deepseek', 'grok'];
6
6
  function parseRepoConfigContent(content) {
7
7
  return yamlLoad(content);
8
8
  }