codeep 2.11.2 → 2.13.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/dist/api/index.js CHANGED
@@ -2,7 +2,7 @@ import * as http from 'node:http';
2
2
  import * as https from 'node:https';
3
3
  import { config, getApiKey, resolveBaseUrl } from '../config/index.js';
4
4
  import { withRetry, isNetworkError } from '../utils/retry.js';
5
- import { getProvider, getProviderBaseUrl, getProviderAuthHeader, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams } from '../config/providers.js';
5
+ import { getProvider, getProviderBaseUrl, getProviderAuthHeader, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams, reasoningParamsFor } from '../config/providers.js';
6
6
  import { logApiRequest, logApiResponse } from '../utils/logger.js';
7
7
  import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence.js';
8
8
  import { loadProjectRules } from '../utils/agent.js';
@@ -390,6 +390,7 @@ async function chatOpenAI(message, history, model, apiKey, onChunk, abortSignal)
390
390
  ...(stream ? { stream_options: { include_usage: true } } : {}),
391
391
  ...(omitTemperature ? {} : { temperature }),
392
392
  ...(useCompletionTokens ? { max_completion_tokens: maxTokens } : { max_tokens: maxTokens }),
393
+ ...reasoningParamsFor(providerId, model, config.get('reasoningEffort')),
393
394
  ...(providerId === 'openrouter' ? { usage: { include: true } } : {}),
394
395
  ...(openRouterProvider ? { provider: openRouterProvider } : {}),
395
396
  });
@@ -658,6 +659,7 @@ async function chatAnthropic(message, history, model, apiKey, onChunk, abortSign
658
659
  // Fable 5 / Opus 4.7+ reject temperature with a 400 — omit it there
659
660
  // (omission means API default on every Claude model).
660
661
  ...(modelRejectsSamplingParams(model) ? {} : { temperature }),
662
+ ...reasoningParamsFor(providerId, model, config.get('reasoningEffort')),
661
663
  stream,
662
664
  ...cachedSystem,
663
665
  }),
@@ -52,6 +52,11 @@ interface ConfigSchema {
52
52
  migrationVersion: number;
53
53
  temperature: number;
54
54
  maxTokens: number;
55
+ /** Thinking / reasoning-effort tier sent with each request. 'auto' (default)
56
+ * omits the param so each model uses its own default; low/medium/high/max are
57
+ * clamped per provider+model by reasoningParamsFor() (config/providers.ts).
58
+ * Only applied for models that expose a graded knob — set via `/thinking`. */
59
+ reasoningEffort: 'auto' | 'low' | 'medium' | 'high' | 'max';
55
60
  apiTimeout: number;
56
61
  rateLimitApi: number;
57
62
  rateLimitCommands: number;
@@ -180,6 +180,7 @@ function createConfig() {
180
180
  currentSessionId: '',
181
181
  temperature: 0.7,
182
182
  maxTokens: 32768,
183
+ reasoningEffort: 'auto',
183
184
  apiTimeout: 60000,
184
185
  rateLimitApi: 10000,
185
186
  rateLimitCommands: 10000,
@@ -26,6 +26,10 @@ export interface ProviderConfig {
26
26
  maxOutputTokens?: number;
27
27
  useMaxCompletionTokens?: boolean;
28
28
  requiresDefaultTemperature?: boolean;
29
+ /** Provider's OpenAI-compatible endpoint rejects `tools` together with
30
+ * `stream: true` (Alibaba/Qwen DashScope). When true, agent turns that send
31
+ * tools are issued non-streamed (we buffer the full response). */
32
+ noStreamWithTools?: boolean;
29
33
  envKey?: string;
30
34
  subscribeUrl?: string;
31
35
  noApiKey?: boolean;
@@ -68,9 +72,69 @@ export declare function usesMaxCompletionTokens(providerId: string): boolean;
68
72
  * (e.g. OpenAI GPT-5+ only accepts the default of 1).
69
73
  */
70
74
  export declare function requiresDefaultTemperature(providerId: string): boolean;
75
+ /**
76
+ * Returns true if the provider's OpenAI-compatible endpoint rejects `tools`
77
+ * together with `stream: true` (Alibaba/Qwen) — callers must issue tool-bearing
78
+ * agent turns non-streamed.
79
+ */
80
+ export declare function providerNoStreamWithTools(providerId: string): boolean;
71
81
  export declare function modelRejectsSamplingParams(model: string): boolean;
72
82
  /**
73
83
  * Returns the effective max output tokens for a provider, capped by the provider's limit.
74
84
  * Falls back to the requested value if no provider limit is set.
75
85
  */
76
86
  export declare function getEffectiveMaxTokens(providerId: string, requested: number): number;
87
+ /**
88
+ * Unified, user-facing thinking-effort tiers (the `/thinking` setting).
89
+ *
90
+ * 'auto' — omit the param entirely → each provider's own default.
91
+ * low / medium / high / max — four explicit depth tiers.
92
+ *
93
+ * The four tiers are CONCEPTUAL. `reasoningParamsFor()` clamps each one to the
94
+ * nearest level the active provider+model actually accepts, so we never send a
95
+ * value that would 400 (e.g. Gemini rejects "medium"; OpenAI has no "max").
96
+ * The control is a pure DEPTH knob on models that already think — it never
97
+ * toggles thinking on/off, which keeps us clear of the reasoning_content-replay
98
+ * contract that DeepSeek/GLM impose when thinking mode is flipped.
99
+ */
100
+ export type ReasoningTier = 'auto' | 'low' | 'medium' | 'high' | 'max';
101
+ export declare const REASONING_TIERS: ReasoningTier[];
102
+ /**
103
+ * Canonicalize a model id for capability matching: lowercase, drop any
104
+ * `vendor/` namespace (OpenRouter sends `anthropic/claude-opus-4.8`), and
105
+ * normalize `.` version separators to `-` (`glm-5.2` → `glm-5-2`,
106
+ * `claude-opus-4.8` → `claude-opus-4-8`). Mirrors macOS `ModelTuning.canonicalModelID`.
107
+ */
108
+ export declare function canonicalModelId(model: string): string;
109
+ /**
110
+ * Does this provider+model expose a GRADED thinking-effort control we can drive?
111
+ * Used to gate the `/thinking` UI — hidden entirely for models without one.
112
+ * Keep in lockstep with macOS `ModelTuning.reasoningEffortSupported`.
113
+ */
114
+ export declare function modelSupportsReasoningEffort(providerId: string, model: string): boolean;
115
+ /**
116
+ * Build the request-body fields that carry the chosen effort tier for the
117
+ * active provider+model+protocol. Returns `{}` for 'auto', unsupported
118
+ * models, or providers without a graded knob — so callers can spread it
119
+ * unconditionally. Keep in lockstep with macOS `ModelTuning.reasoningParams`.
120
+ */
121
+ export declare function reasoningParamsFor(providerId: string, model: string, tier: ReasoningTier): Record<string, unknown>;
122
+ /**
123
+ * The DISTINCT tiers a given provider+model actually exposes — used to build a
124
+ * per-model picker that only offers levels the model can tell apart (e.g.
125
+ * GLM-5.2/DeepSeek grade only high|max; Gemini via the OpenAI-compat layer only
126
+ * low|high). Always leads with 'auto'. `[]` for models with no graded knob.
127
+ *
128
+ * Kept in lockstep with `reasoningParamsFor` (the providers-test asserts every
129
+ * listed tier yields a DISTINCT param, so this can't silently drift). Mirrors
130
+ * macOS `ModelTuning.availableReasoningTiers`.
131
+ */
132
+ export declare function availableReasoningTiers(providerId: string, model: string): ReasoningTier[];
133
+ /**
134
+ * Map a (possibly out-of-range) tier to the tier this model actually distinguishes,
135
+ * for display — the chip + the checked menu row. The effort setting is global, so
136
+ * a tier picked on Opus ('low') may not exist on GLM-5.2; we show the level GLM
137
+ * will really run (its 'low' clamps to 'high'). Picks the available tier whose
138
+ * effective param equals the requested one. 'auto' (or unsupported) → 'auto'.
139
+ */
140
+ export declare function resolveReasoningTier(providerId: string, model: string, tier: ReasoningTier): ReasoningTier;
@@ -203,6 +203,196 @@ export const PROVIDERS = {
203
203
  groupLabel: 'DeepSeek',
204
204
  hint: 'Pay-per-use via DeepSeek API key (platform.deepseek.com).',
205
205
  },
206
+ // ── Kimi (Moonshot AI) ────────────────────────────────────────────
207
+ // Subscription (Kimi Code) mirrors the Z.AI GLM-Coding-Plan shape: a
208
+ // dedicated coding base URL + a separate key, model id ALWAYS
209
+ // `kimi-for-coding` (a backend alias). OpenAI-compatible is the
210
+ // battle-tested path so we don't expose the Anthropic surface here.
211
+ 'kimi': {
212
+ name: 'Kimi (Moonshot) — Coding Plan',
213
+ description: 'Kimi Code subscription',
214
+ protocols: {
215
+ openai: { baseUrl: 'https://api.kimi.com/coding/v1', authHeader: 'Bearer', supportsNativeTools: true },
216
+ },
217
+ models: [
218
+ { id: 'kimi-for-coding', name: 'Kimi Code', description: 'Subscription alias — auto-maps to the latest Kimi coding model (K2.7 Code)' },
219
+ ],
220
+ defaultModel: 'kimi-for-coding',
221
+ defaultProtocol: 'openai',
222
+ maxOutputTokens: 32_768,
223
+ envKey: 'KIMI_CODE_API_KEY',
224
+ subscribeUrl: 'https://www.kimi.com/code',
225
+ groupLabel: 'Kimi — Subscription (Kimi Code)',
226
+ hint: 'Uses your Kimi Code subscription — no per-token charges. Key from kimi.com/code/console.',
227
+ },
228
+ 'kimi-api': {
229
+ name: 'Kimi (Moonshot) API (pay-per-use)',
230
+ description: 'Moonshot AI Kimi models via API key',
231
+ protocols: {
232
+ openai: { baseUrl: 'https://api.moonshot.ai/v1', authHeader: 'Bearer', supportsNativeTools: true },
233
+ },
234
+ models: [
235
+ { id: 'kimi-k2.7-code', name: 'Kimi K2.7 Code', description: 'Flagship agentic coding model (256K context)' },
236
+ { id: 'kimi-k2.7-code-highspeed', name: 'Kimi K2.7 Code (High-Speed)', description: 'Throughput-tuned K2.7 Code for latency-sensitive loops' },
237
+ { id: 'kimi-k2.6', name: 'Kimi K2.6', description: 'Previous-gen multimodal reasoning model' },
238
+ { id: 'kimi-k2.5', name: 'Kimi K2.5', description: 'Older general-purpose model (cheaper)' },
239
+ ],
240
+ defaultModel: 'kimi-k2.7-code',
241
+ defaultProtocol: 'openai',
242
+ maxOutputTokens: 32_768,
243
+ envKey: 'MOONSHOT_API_KEY',
244
+ subscribeUrl: 'https://platform.kimi.ai/console/api-keys',
245
+ groupLabel: 'Kimi — API (pay-per-use)',
246
+ hint: 'Pay-per-use via Moonshot API key (platform.kimi.ai).',
247
+ },
248
+ 'kimi-cn': {
249
+ name: 'Kimi China (Moonshot)',
250
+ description: 'Moonshot AI Kimi models (China)',
251
+ protocols: {
252
+ openai: { baseUrl: 'https://api.moonshot.cn/v1', authHeader: 'Bearer', supportsNativeTools: true },
253
+ },
254
+ models: [
255
+ { id: 'kimi-k2.7-code', name: 'Kimi K2.7 Code', description: 'Flagship agentic coding model (256K context)' },
256
+ { id: 'kimi-k2.7-code-highspeed', name: 'Kimi K2.7 Code (High-Speed)', description: 'Throughput-tuned K2.7 Code' },
257
+ { id: 'kimi-k2.6', name: 'Kimi K2.6', description: 'Previous-gen multimodal reasoning model' },
258
+ { id: 'kimi-k2.5', name: 'Kimi K2.5', description: 'Older general-purpose model' },
259
+ ],
260
+ defaultModel: 'kimi-k2.7-code',
261
+ defaultProtocol: 'openai',
262
+ maxOutputTokens: 32_768,
263
+ envKey: 'MOONSHOT_CN_API_KEY',
264
+ subscribeUrl: 'https://platform.moonshot.cn/console/api-keys',
265
+ groupLabel: 'Kimi China — API (pay-per-use)',
266
+ hint: 'Pay-per-use via Moonshot China API key (platform.moonshot.cn).',
267
+ },
268
+ // ── Grok (xAI) ────────────────────────────────────────────────────
269
+ // Pay-per-use today (console.x.ai key). The SuperGrok / X Premium+
270
+ // subscription is OAuth-based — added separately. Reasoning models
271
+ // require max_completion_tokens (like GPT-5), so useMaxCompletionTokens.
272
+ 'grok': {
273
+ name: 'Grok (xAI)',
274
+ description: 'xAI Grok models',
275
+ protocols: {
276
+ openai: { baseUrl: 'https://api.x.ai/v1', authHeader: 'Bearer', supportsNativeTools: true },
277
+ },
278
+ models: [
279
+ { id: 'grok-build-0.1', name: 'Grok Build 0.1', description: 'Agentic coding model — fast, 256K context' },
280
+ { id: 'grok-4.3', name: 'Grok 4.3', description: 'Flagship — highest quality, 1M context' },
281
+ { id: 'grok-code-fast-1', name: 'Grok Code Fast 1', description: 'Low-cost speed-first coder (alias of Build 0.1)' },
282
+ { id: 'grok-4-fast-reasoning', name: 'Grok 4 Fast (reasoning)', description: 'Cheap reasoning model, very large context' },
283
+ ],
284
+ defaultModel: 'grok-build-0.1',
285
+ defaultProtocol: 'openai',
286
+ useMaxCompletionTokens: true, // reasoning models reject max_tokens
287
+ envKey: 'XAI_API_KEY',
288
+ subscribeUrl: 'https://console.x.ai',
289
+ groupLabel: 'xAI Grok',
290
+ hint: 'Pay-per-use via xAI API key (console.x.ai).',
291
+ },
292
+ // ── Qwen (Alibaba Model Studio / DashScope) ───────────────────────
293
+ // Coding Plan subscription = dedicated base URL + sk-sp- key (mirrors
294
+ // Z.AI). Qwen's OpenAI-compatible surface CANNOT combine tools with
295
+ // streaming, so all Qwen entries set noStreamWithTools.
296
+ 'qwen': {
297
+ name: 'Qwen (Alibaba) — Coding Plan',
298
+ description: 'Qwen Coding Plan subscription',
299
+ protocols: {
300
+ openai: { baseUrl: 'https://coding-intl.dashscope.aliyuncs.com/v1', authHeader: 'Bearer', supportsNativeTools: true },
301
+ },
302
+ models: [
303
+ { id: 'qwen3-coder-plus', name: 'Qwen3-Coder Plus', description: 'Flagship coding model — best quality' },
304
+ { id: 'qwen3-coder-next', name: 'Qwen3-Coder Next', description: 'Balanced quality/speed/cost' },
305
+ { id: 'qwen3-max', name: 'Qwen3-Max', description: 'Flagship general model (code + reasoning)' },
306
+ ],
307
+ defaultModel: 'qwen3-coder-plus',
308
+ defaultProtocol: 'openai',
309
+ maxOutputTokens: 65_536,
310
+ noStreamWithTools: true,
311
+ envKey: 'BAILIAN_CODING_PLAN_API_KEY',
312
+ subscribeUrl: 'https://www.alibabacloud.com/help/en/model-studio/qwen-code-coding-plan',
313
+ groupLabel: 'Qwen — Subscription (Coding Plan)',
314
+ hint: 'Uses your Qwen Coding Plan — no per-token charges. sk-sp-… key from Model Studio. Interactive coding use only.',
315
+ },
316
+ 'qwen-api': {
317
+ name: 'Qwen (Alibaba) API (pay-per-use)',
318
+ description: 'Alibaba Model Studio Qwen models via API key',
319
+ protocols: {
320
+ openai: { baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', authHeader: 'Bearer', supportsNativeTools: true },
321
+ },
322
+ models: [
323
+ { id: 'qwen3-coder-plus', name: 'Qwen3-Coder Plus', description: 'Flagship coding model (256K, up to 1M)' },
324
+ { id: 'qwen3-coder-next', name: 'Qwen3-Coder Next', description: 'Balanced quality/speed/cost' },
325
+ { id: 'qwen3-coder-flash', name: 'Qwen3-Coder Flash', description: 'Fast/cheap coder' },
326
+ { id: 'qwen3-max', name: 'Qwen3-Max', description: 'Flagship general model' },
327
+ ],
328
+ defaultModel: 'qwen3-coder-plus',
329
+ defaultProtocol: 'openai',
330
+ maxOutputTokens: 65_536,
331
+ noStreamWithTools: true,
332
+ envKey: 'DASHSCOPE_API_KEY',
333
+ subscribeUrl: 'https://modelstudio.console.alibabacloud.com/',
334
+ groupLabel: 'Qwen — API (pay-per-use)',
335
+ hint: 'Pay-per-use via Alibaba Model Studio key (DASHSCOPE_API_KEY).',
336
+ },
337
+ 'qwen-cn': {
338
+ name: 'Qwen China — Coding Plan',
339
+ description: 'Qwen Coding Plan subscription (China)',
340
+ protocols: {
341
+ openai: { baseUrl: 'https://coding.dashscope.aliyuncs.com/v1', authHeader: 'Bearer', supportsNativeTools: true },
342
+ },
343
+ models: [
344
+ { id: 'qwen3-coder-plus', name: 'Qwen3-Coder Plus', description: 'Flagship coding model — best quality' },
345
+ { id: 'qwen3-coder-next', name: 'Qwen3-Coder Next', description: 'Balanced quality/speed/cost' },
346
+ { id: 'qwen3-max', name: 'Qwen3-Max', description: 'Flagship general model' },
347
+ ],
348
+ defaultModel: 'qwen3-coder-plus',
349
+ defaultProtocol: 'openai',
350
+ maxOutputTokens: 65_536,
351
+ noStreamWithTools: true,
352
+ envKey: 'BAILIAN_CODING_PLAN_CN_API_KEY',
353
+ subscribeUrl: 'https://bailian.console.aliyun.com/',
354
+ groupLabel: 'Qwen China — Subscription (Coding Plan)',
355
+ hint: 'Uses your Qwen Coding Plan (China). sk-sp-… key from Bailian.',
356
+ },
357
+ 'qwen-cn-api': {
358
+ name: 'Qwen China API (pay-per-use)',
359
+ description: 'Alibaba Model Studio Qwen models via API key (China)',
360
+ protocols: {
361
+ openai: { baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', authHeader: 'Bearer', supportsNativeTools: true },
362
+ },
363
+ models: [
364
+ { id: 'qwen3-coder-plus', name: 'Qwen3-Coder Plus', description: 'Flagship coding model' },
365
+ { id: 'qwen3-coder-next', name: 'Qwen3-Coder Next', description: 'Balanced quality/speed/cost' },
366
+ { id: 'qwen3-coder-flash', name: 'Qwen3-Coder Flash', description: 'Fast/cheap coder' },
367
+ { id: 'qwen3-max', name: 'Qwen3-Max', description: 'Flagship general model' },
368
+ ],
369
+ defaultModel: 'qwen3-coder-plus',
370
+ defaultProtocol: 'openai',
371
+ maxOutputTokens: 65_536,
372
+ noStreamWithTools: true,
373
+ envKey: 'DASHSCOPE_CN_API_KEY',
374
+ subscribeUrl: 'https://bailian.console.aliyun.com/',
375
+ groupLabel: 'Qwen China — API (pay-per-use)',
376
+ hint: 'Pay-per-use via Alibaba Model Studio China key.',
377
+ },
378
+ 'modelscope': {
379
+ name: 'ModelScope (free Qwen)',
380
+ description: 'Free Qwen3-Coder inference via ModelScope',
381
+ protocols: {
382
+ openai: { baseUrl: 'https://api-inference.modelscope.cn/v1', authHeader: 'Bearer', supportsNativeTools: true },
383
+ },
384
+ models: [
385
+ { id: 'Qwen/Qwen3-Coder-480B-A35B-Instruct', name: 'Qwen3-Coder 480B', description: 'Open MoE coder — free tier (~2000 req/day)' },
386
+ ],
387
+ defaultModel: 'Qwen/Qwen3-Coder-480B-A35B-Instruct',
388
+ defaultProtocol: 'openai',
389
+ maxOutputTokens: 65_536,
390
+ noStreamWithTools: true,
391
+ envKey: 'MODELSCOPE_API_KEY',
392
+ subscribeUrl: 'https://modelscope.cn/my/myaccesstoken',
393
+ groupLabel: 'ModelScope — Free (Qwen)',
394
+ hint: 'Free tier (~2000 req/day) via ModelScope token (modelscope.cn). Needs a bound Aliyun account.',
395
+ },
206
396
  'openai': {
207
397
  name: 'OpenAI',
208
398
  description: 'GPT and o-series models',
@@ -361,14 +551,24 @@ const DISPLAY_ORDER = [
361
551
  'openrouter', // 100+ models, one key — surfaced high on purpose for 2.0.0.
362
552
  'z.ai',
363
553
  'z.ai-api',
554
+ 'kimi',
555
+ 'kimi-api',
556
+ 'qwen',
557
+ 'qwen-api',
558
+ 'grok',
364
559
  'deepseek',
365
560
  'google',
366
561
  'minimax',
367
562
  'minimax-api',
563
+ 'modelscope',
368
564
  'ollama',
369
565
  'custom',
566
+ // Regional + parameter-variant entries trail.
370
567
  'z.ai-cn',
371
568
  'z.ai-cn-api',
569
+ 'kimi-cn',
570
+ 'qwen-cn',
571
+ 'qwen-cn-api',
372
572
  'minimax-cn',
373
573
  ];
374
574
  export function getProviderList() {
@@ -441,14 +641,26 @@ export function usesMaxCompletionTokens(providerId) {
441
641
  export function requiresDefaultTemperature(providerId) {
442
642
  return PROVIDERS[providerId]?.requiresDefaultTemperature ?? false;
443
643
  }
644
+ /**
645
+ * Returns true if the provider's OpenAI-compatible endpoint rejects `tools`
646
+ * together with `stream: true` (Alibaba/Qwen) — callers must issue tool-bearing
647
+ * agent turns non-streamed.
648
+ */
649
+ export function providerNoStreamWithTools(providerId) {
650
+ return PROVIDERS[providerId]?.noStreamWithTools ?? false;
651
+ }
444
652
  /**
445
653
  * Models that reject sampling parameters (temperature/top_p/top_k) with a 400.
446
654
  * Anthropic removed them on Fable 5 and Opus 4.7+; older Claude models still
447
655
  * accept them, so this must be a MODEL-level check, not a provider-level one
448
656
  * (requiresDefaultTemperature can't express it). Omitting the field is always
449
- * safe — the API treats omission as default.
657
+ * safe — the API treats omission as default. Kimi K2.x code/thinking models
658
+ * fix temperature internally and 400 on any custom value, so they're here too.
450
659
  */
451
- const SAMPLING_PARAMS_REJECTED = ['claude-fable-5', 'claude-opus-4-8', 'claude-opus-4-7'];
660
+ const SAMPLING_PARAMS_REJECTED = [
661
+ 'claude-fable-5', 'claude-opus-4-8', 'claude-opus-4-7',
662
+ 'kimi-k2.7-code', 'kimi-for-coding',
663
+ ];
452
664
  export function modelRejectsSamplingParams(model) {
453
665
  return SAMPLING_PARAMS_REJECTED.some(id => model === id || model.startsWith(`${id}-`));
454
666
  }
@@ -462,3 +674,162 @@ export function getEffectiveMaxTokens(providerId, requested) {
462
674
  return requested;
463
675
  return Math.min(requested, provider.maxOutputTokens);
464
676
  }
677
+ export const REASONING_TIERS = ['auto', 'low', 'medium', 'high', 'max'];
678
+ /**
679
+ * Canonicalize a model id for capability matching: lowercase, drop any
680
+ * `vendor/` namespace (OpenRouter sends `anthropic/claude-opus-4.8`), and
681
+ * normalize `.` version separators to `-` (`glm-5.2` → `glm-5-2`,
682
+ * `claude-opus-4.8` → `claude-opus-4-8`). Mirrors macOS `ModelTuning.canonicalModelID`.
683
+ */
684
+ export function canonicalModelId(model) {
685
+ let id = model.toLowerCase();
686
+ const slash = id.lastIndexOf('/');
687
+ if (slash !== -1)
688
+ id = id.slice(slash + 1);
689
+ return id.replace(/\./g, '-');
690
+ }
691
+ /** True when `id` equals `prefix` or starts with `prefix-` (catches dated variants). */
692
+ function idMatches(id, prefix) {
693
+ return id === prefix || id.startsWith(`${prefix}-`);
694
+ }
695
+ /**
696
+ * Does this provider+model expose a GRADED thinking-effort control we can drive?
697
+ * Used to gate the `/thinking` UI — hidden entirely for models without one.
698
+ * Keep in lockstep with macOS `ModelTuning.reasoningEffortSupported`.
699
+ */
700
+ export function modelSupportsReasoningEffort(providerId, model) {
701
+ const id = canonicalModelId(model);
702
+ switch (providerId) {
703
+ case 'anthropic':
704
+ // Effort is GA on Opus 4.5+, Sonnet 4.6, Fable 5 — NOT Haiku or Sonnet 4.5.
705
+ if (idMatches(id, 'claude-haiku-4-5') || idMatches(id, 'claude-sonnet-4-5'))
706
+ return false;
707
+ return /^claude-(opus-4-([5-9]|\d\d)|sonnet-4-6|fable-5)/.test(id);
708
+ case 'openai':
709
+ // GPT-5.x are reasoning models — reasoning_effort across the family (incl. mini).
710
+ return id.startsWith('gpt-5');
711
+ case 'google':
712
+ // Gemini 3.x thinking_level via the OpenAI-compat reasoning_effort mapping.
713
+ return id.startsWith('gemini-3');
714
+ case 'deepseek':
715
+ return id.startsWith('deepseek-v4');
716
+ case 'z.ai':
717
+ case 'z.ai-api':
718
+ case 'z.ai-cn':
719
+ case 'z.ai-cn-api':
720
+ // GLM-5.2 added graded High/Max effort. glm-5-turbo is a plain thinking
721
+ // toggle (no graded levels) so it stays out.
722
+ return idMatches(id, 'glm-5-2');
723
+ case 'grok':
724
+ // Grok reasoning models accept reasoning_effort (none/low/medium/high).
725
+ // Explicit *-non-reasoning variants don't think → excluded.
726
+ return id.startsWith('grok') && !id.includes('non-reasoning');
727
+ // Kimi (thinking on/off, not graded) and Qwen coders (non-thinking) have
728
+ // no graded knob → fall through to default false.
729
+ case 'openrouter':
730
+ // OpenRouter normalizes a unified `reasoning` field and silently ignores
731
+ // it for non-reasoning models, so the control is always safe to expose.
732
+ return true;
733
+ default:
734
+ // minimax (toggle only), ollama, custom — no graded depth knob.
735
+ return false;
736
+ }
737
+ }
738
+ /**
739
+ * Build the request-body fields that carry the chosen effort tier for the
740
+ * active provider+model+protocol. Returns `{}` for 'auto', unsupported
741
+ * models, or providers without a graded knob — so callers can spread it
742
+ * unconditionally. Keep in lockstep with macOS `ModelTuning.reasoningParams`.
743
+ */
744
+ export function reasoningParamsFor(providerId, model, tier) {
745
+ // 'auto', or any unexpected value from an older/garbled config, → no param.
746
+ // (Guards against ever emitting e.g. `effort: undefined`, which could 400.)
747
+ if (tier === 'auto' || !REASONING_TIERS.includes(tier))
748
+ return {};
749
+ if (!modelSupportsReasoningEffort(providerId, model))
750
+ return {};
751
+ switch (providerId) {
752
+ case 'anthropic':
753
+ // low / medium / high / max — all valid on the capable Claude models.
754
+ return { output_config: { effort: tier } };
755
+ case 'openai':
756
+ // none/low/medium/high/xhigh — no "max"; map our Max → xhigh (the ceiling).
757
+ return { reasoning_effort: tier === 'max' ? 'xhigh' : tier };
758
+ case 'google':
759
+ // Gemini 3 (OpenAI-compat) accepts ONLY low/high — "medium" 400s.
760
+ return { reasoning_effort: tier === 'low' ? 'low' : 'high' };
761
+ case 'deepseek':
762
+ case 'z.ai':
763
+ case 'z.ai-api':
764
+ case 'z.ai-cn':
765
+ case 'z.ai-cn-api':
766
+ // Graded thinking depth: high (default) or max. Lower tiers collapse to high.
767
+ return { reasoning_effort: tier === 'max' ? 'max' : 'high' };
768
+ case 'grok':
769
+ // none/low/medium/high — no "max"; map our Max → high (the ceiling).
770
+ return { reasoning_effort: tier === 'max' ? 'high' : tier };
771
+ case 'openrouter':
772
+ // Unified reasoning object; no "max" effort → cap at high.
773
+ return { reasoning: { effort: tier === 'max' ? 'high' : tier } };
774
+ default:
775
+ return {};
776
+ }
777
+ }
778
+ /**
779
+ * The DISTINCT tiers a given provider+model actually exposes — used to build a
780
+ * per-model picker that only offers levels the model can tell apart (e.g.
781
+ * GLM-5.2/DeepSeek grade only high|max; Gemini via the OpenAI-compat layer only
782
+ * low|high). Always leads with 'auto'. `[]` for models with no graded knob.
783
+ *
784
+ * Kept in lockstep with `reasoningParamsFor` (the providers-test asserts every
785
+ * listed tier yields a DISTINCT param, so this can't silently drift). Mirrors
786
+ * macOS `ModelTuning.availableReasoningTiers`.
787
+ */
788
+ export function availableReasoningTiers(providerId, model) {
789
+ if (!modelSupportsReasoningEffort(providerId, model))
790
+ return [];
791
+ switch (providerId) {
792
+ case 'anthropic':
793
+ case 'openai':
794
+ return ['auto', 'low', 'medium', 'high', 'max'];
795
+ case 'google':
796
+ // OpenAI-compat layer accepts only low/high — "medium" 400s.
797
+ return ['auto', 'low', 'high'];
798
+ case 'deepseek':
799
+ case 'z.ai':
800
+ case 'z.ai-api':
801
+ case 'z.ai-cn':
802
+ case 'z.ai-cn-api':
803
+ return ['auto', 'high', 'max'];
804
+ case 'grok':
805
+ return ['auto', 'low', 'medium', 'high'];
806
+ case 'openrouter':
807
+ return ['auto', 'low', 'medium', 'high'];
808
+ default:
809
+ return [];
810
+ }
811
+ }
812
+ /**
813
+ * Map a (possibly out-of-range) tier to the tier this model actually distinguishes,
814
+ * for display — the chip + the checked menu row. The effort setting is global, so
815
+ * a tier picked on Opus ('low') may not exist on GLM-5.2; we show the level GLM
816
+ * will really run (its 'low' clamps to 'high'). Picks the available tier whose
817
+ * effective param equals the requested one. 'auto' (or unsupported) → 'auto'.
818
+ */
819
+ export function resolveReasoningTier(providerId, model, tier) {
820
+ if (tier === 'auto')
821
+ return 'auto';
822
+ const avail = availableReasoningTiers(providerId, model);
823
+ if (avail.length === 0)
824
+ return 'auto';
825
+ if (avail.includes(tier))
826
+ return tier;
827
+ const target = JSON.stringify(reasoningParamsFor(providerId, model, tier));
828
+ for (const t of avail) {
829
+ if (t === 'auto')
830
+ continue;
831
+ if (JSON.stringify(reasoningParamsFor(providerId, model, t)) === target)
832
+ return t;
833
+ }
834
+ return 'auto';
835
+ }
@@ -66,7 +66,7 @@ const COMMAND_DESCRIPTIONS = {
66
66
  'drop': 'Remove file from context',
67
67
  'multiline': 'Toggle multi-line input',
68
68
  'test': 'Generate/run tests',
69
- 'docs': 'Add documentation',
69
+ 'docs': 'Open web docs for a command (e.g. /docs personality)',
70
70
  'refactor': 'Improve code quality',
71
71
  'fix': 'Debug and fix issues',
72
72
  'explain': 'Explain code',
@@ -123,6 +123,8 @@ const COMMAND_DESCRIPTIONS = {
123
123
  'sync': 'Sync learning preferences and profiles to codeep.dev',
124
124
  'telemetry': 'Show or toggle automatic cloud telemetry (on/off)',
125
125
  'keysync': 'Show or toggle syncing API keys to codeep.dev (on/off)',
126
+ 'thinking': 'Set the thinking/reasoning-effort tier (auto/low/medium/high/max) for models that support it',
127
+ 'effort': 'Alias for /thinking — set the reasoning-effort tier',
126
128
  // 2.0 — surfaced for `/` autocomplete; documented in /help too.
127
129
  'compact': 'Summarize older messages to free up context',
128
130
  'commands': 'List custom slash commands in .codeep/commands/*.md',
@@ -2312,6 +2314,14 @@ export class App {
2312
2314
  this.screen.write(leftX - 1, y, '·', fg.gray);
2313
2315
  leftX += 1;
2314
2316
  }
2317
+ // Thinking-effort tier, right beside the model (the CLI twin of the Mac
2318
+ // app's effort chip). Only present when set + supported — see getStatus.
2319
+ if (status.reasoningEffort) {
2320
+ this.screen.write(leftX, y, fg.yellow + status.reasoningEffort + style.reset);
2321
+ leftX += status.reasoningEffort.length + 2;
2322
+ this.screen.write(leftX - 1, y, '·', fg.gray);
2323
+ leftX += 1;
2324
+ }
2315
2325
  this.screen.write(leftX, y, msgCount, fg.gray);
2316
2326
  leftX += msgCount.length;
2317
2327
  if (tokenStr) {
@@ -8,7 +8,7 @@
8
8
  import { config, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, isTelemetryEnabled, telemetryForcedOffByEnv, isKeySyncEnabled, keySyncForcedOffByEnv, saveSession, startNewSession, loadSession, listSessionsWithInfo, deleteSession, renameSession, setProjectPermission, saveProfile, loadProfile, applyProfile, listProfiles, deleteProfile, initializeAsProject, isManuallyInitializedProject, } from '../config/index.js';
9
9
  import { getProjectContext } from '../utils/project.js';
10
10
  import { getCurrentVersion } from '../utils/update.js';
11
- import { getProviderList, getProvider } from '../config/providers.js';
11
+ import { getProviderList, getProvider, modelSupportsReasoningEffort, reasoningParamsFor, availableReasoningTiers, resolveReasoningTier, REASONING_TIERS } from '../config/providers.js';
12
12
  import { setProjectContext } from '../api/index.js';
13
13
  import { runSkill, runCommandChain } from './agentExecution.js';
14
14
  import { loadProjectIntelligence, saveProjectIntelligence } from '../utils/projectIntelligence.js';
@@ -303,6 +303,57 @@ export async function handleCommand(command, args, ctx) {
303
303
  ctx.app.addMessage({ role: 'system', content: kLines.join('\n') });
304
304
  break;
305
305
  }
306
+ case 'effort':
307
+ case 'thinking': {
308
+ const providerId = config.get('provider');
309
+ const model = config.get('model');
310
+ const supported = modelSupportsReasoningEffort(providerId, model);
311
+ // Tiers THIS model actually distinguishes (e.g. GLM-5.2 → auto/high/max).
312
+ const available = availableReasoningTiers(providerId, model);
313
+ const sub = args[0]?.toLowerCase();
314
+ if (sub && REASONING_TIERS.includes(sub)) {
315
+ config.set('reasoningEffort', sub);
316
+ if (sub === 'auto') {
317
+ ctx.app.notify('Thinking effort: auto — each model uses its own default.');
318
+ }
319
+ else if (!supported) {
320
+ ctx.app.notify(`Thinking effort set to "${sub}", but ${model} has no graded thinking control — it will be ignored until you switch to a model that does (e.g. Opus 4.8, GPT-5.x, Gemini 3, DeepSeek V4, GLM-5.2).`);
321
+ }
322
+ else {
323
+ // Tell the user what THIS model will actually run (the tier may
324
+ // collapse onto a level the model distinguishes, e.g. low→high on GLM).
325
+ const resolved = resolveReasoningTier(providerId, model, sub);
326
+ const note = resolved === sub ? '' : ` (${model} runs this as "${resolved}")`;
327
+ ctx.app.notify(`Thinking effort: ${sub}${note} — sending ${JSON.stringify(reasoningParamsFor(providerId, model, sub))}.`);
328
+ }
329
+ break;
330
+ }
331
+ if (sub && sub !== 'status') {
332
+ const offer = available.length > 0 ? available : REASONING_TIERS;
333
+ ctx.app.notify(`Usage: /thinking ${offer.join(' · /thinking ')}`);
334
+ break;
335
+ }
336
+ const tier = (config.get('reasoningEffort') ?? 'auto');
337
+ const resolved = resolveReasoningTier(providerId, model, tier);
338
+ const tLines = ['## Thinking effort', ''];
339
+ tLines.push(`**Tier** ${tier}${resolved !== tier && tier !== 'auto' ? ` → ${resolved} on this model` : ''}`);
340
+ tLines.push(`**Model** ${model} (${providerId})`);
341
+ if (!supported) {
342
+ tLines.push('**Effective** not sent — this model has no graded thinking control');
343
+ }
344
+ else if (tier === 'auto') {
345
+ tLines.push('**Effective** model default (no param sent)');
346
+ }
347
+ else {
348
+ tLines.push(`**Effective** ${JSON.stringify(reasoningParamsFor(providerId, model, tier))}`);
349
+ }
350
+ if (supported)
351
+ tLines.push(`**Available** ${available.join(' · ')}`);
352
+ tLines.push('');
353
+ tLines.push('Sets how hard the model reasons. Each model offers only the levels it distinguishes (GLM-5.2 / DeepSeek → high · max; Gemini → low · high; Opus/Sonnet & GPT-5.x → the full set). The setting is global and clamps to the active model, so it never sends a value the API rejects. `/effort` is an alias.');
354
+ ctx.app.addMessage({ role: 'system', content: tLines.join('\n') });
355
+ break;
356
+ }
306
357
  case 'grant': {
307
358
  setProjectPermission(ctx.projectPath, true, true);
308
359
  ctx.setHasWriteAccess(true);
@@ -341,10 +392,12 @@ export async function handleCommand(command, args, ctx) {
341
392
  runAgentTask(args.join(' '), true, ctx, () => null, () => { });
342
393
  break;
343
394
  }
395
+ case 'd':
344
396
  case 'docs': {
345
397
  // Open per-command web docs in the system browser. Lets the inline
346
398
  // /help stay terse (single-line entries) while users who want the
347
- // long story get one keystroke away from a real page.
399
+ // long story get one keystroke away from a real page. `/d` is the
400
+ // short alias.
348
401
  const cmd = (args[0] ?? '').toLowerCase().replace(/^\//, '');
349
402
  const KNOWN = {
350
403
  personality: 'https://codeep.dev/docs/agent#personalities',
@@ -1504,8 +1557,6 @@ Format: use headers per category, only include categories where you found issues
1504
1557
  case 'commit':
1505
1558
  case 't':
1506
1559
  case 'test':
1507
- case 'd':
1508
- case 'docs':
1509
1560
  case 'r':
1510
1561
  case 'refactor':
1511
1562
  case 'f':
@@ -26,6 +26,19 @@ export const SETTINGS = [
26
26
  max: 32768,
27
27
  step: 256,
28
28
  },
29
+ {
30
+ key: 'reasoningEffort',
31
+ label: 'Thinking Effort',
32
+ getValue: () => config.get('reasoningEffort') ?? 'auto',
33
+ type: 'select',
34
+ options: [
35
+ { value: 'auto', label: 'Auto (model default)' },
36
+ { value: 'low', label: 'Low' },
37
+ { value: 'medium', label: 'Medium' },
38
+ { value: 'high', label: 'High' },
39
+ { value: 'max', label: 'Max' },
40
+ ],
41
+ },
29
42
  {
30
43
  key: 'apiTimeout',
31
44
  label: 'API Timeout (ms)',
@@ -7,6 +7,9 @@ export interface StatusInfo {
7
7
  provider: string;
8
8
  model: string;
9
9
  agentMode: string;
10
+ /** Thinking-effort tier to show beside the model (e.g. "max") — only set
11
+ * when non-auto AND the active model supports a graded knob; undefined hides it. */
12
+ reasoningEffort?: string;
10
13
  projectPath: string;
11
14
  hasWriteAccess: boolean;
12
15
  sessionId: string;
@@ -15,7 +15,7 @@ import { getZaiVisionConfig, getMinimaxMcpConfig, callZaiVisionApi, callMinimaxA
15
15
  import { config, loadApiKey, loadAllApiKeys, getCurrentProvider, autoSaveSession, startNewSession, getCurrentSessionId, loadSession, listSessionsWithInfo, deleteSession, hasReadPermission, hasWritePermission, setProjectPermission, initializeAsProject, isManuallyInitializedProject, setApiKey, setProvider, getGithubId, } from '../config/index.js';
16
16
  import { isProjectDirectory, getProjectContext, } from '../utils/project.js';
17
17
  import { getCurrentVersion, checkForUpdates, getUpdateInstructions } from '../utils/update.js';
18
- import { getProviderList, isNoApiKeyProvider } from '../config/providers.js';
18
+ import { getProviderList, isNoApiKeyProvider, resolveReasoningTier } from '../config/providers.js';
19
19
  import { getSessionStats, getCostBreakdown } from '../utils/tokenTracker.js';
20
20
  import { isGitRepository } from '../utils/git.js';
21
21
  import { reportStats, syncSession, generateProjectId } from '../utils/codeepCloud.js';
@@ -73,11 +73,17 @@ function getStatus() {
73
73
  const providers = getProviderList();
74
74
  const providerInfo = providers.find(p => p.id === provider.id);
75
75
  const stats = getSessionStats();
76
+ // Show the thinking-effort tier beside the model. Resolve the (global) tier
77
+ // to what THIS model actually runs — e.g. a global 'low' shows as 'high' on
78
+ // GLM-5.2, which only grades high|max. 'auto'/unsupported → hidden.
79
+ const resolved = resolveReasoningTier(provider.id, config.get('model'), config.get('reasoningEffort'));
80
+ const reasoningEffort = resolved !== 'auto' ? resolved : undefined;
76
81
  return {
77
82
  version: getCurrentVersion(),
78
83
  provider: providerInfo?.name || 'Unknown',
79
84
  model: config.get('model'),
80
85
  agentMode: config.get('agentMode') || 'off',
86
+ reasoningEffort,
81
87
  projectPath,
82
88
  hasWriteAccess,
83
89
  sessionId,
@@ -16,8 +16,9 @@ import { join } from 'path';
16
16
  import { createHash } from 'crypto';
17
17
  import { config, getApiKey, resolveBaseUrl } from '../config/index.js';
18
18
  import { loadProjectIntelligence, generateContextFromIntelligence } from './projectIntelligence.js';
19
+ import { formatCommandIndex } from './commandIndex.js';
19
20
  import { syncProgress, generateProjectId } from './codeepCloud.js';
20
- import { getProviderAuthHeader, supportsNativeTools, getEffectiveMaxTokens, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams, isNoApiKeyProvider } from '../config/providers.js';
21
+ import { getProviderAuthHeader, supportsNativeTools, getEffectiveMaxTokens, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams, isNoApiKeyProvider, reasoningParamsFor, providerNoStreamWithTools } from '../config/providers.js';
21
22
  import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './tokenTracker.js';
22
23
  import { parseOpenAIToolCalls, parseAnthropicToolCalls, parseToolCalls } from './toolParsing.js';
23
24
  import { formatToolDefinitions, getOpenAITools, getAnthropicTools } from './tools.js';
@@ -281,6 +282,16 @@ export function getAgentSystemPrompt(projectContext) {
281
282
  - Keep working until the task is actually finished. If you still have work to do, CALL A TOOL — don't just narrate. If you're done, reply with a short summary and no tool calls.
282
283
  - Don't ask permission for routine work; the user already launched the agent.
283
284
 
285
+ ## About Codeep
286
+ Codeep is an open-source, terminal-native AI coding agent — also a native macOS app and a VS Code / Zed extension (over ACP). Beyond the file/command tools above, the user can extend you through Codeep features:
287
+ - Skills — reusable workflow bundles (browse/add/run with \`/skills\`)
288
+ - MCP — connect external tools & data, e.g. Postgres, GitHub, a browser, the iOS simulator (\`/mcp\`)
289
+ - Sub-agents — delegate focused subtasks (\`/agents\`)
290
+ - Personalities — change your working style (\`/personality\`)
291
+ - A dashboard at codeep.dev for synced sessions, usage & cost
292
+ When the user asks what you can do, or a task maps to one of these, point them at the right slash-command:
293
+ ${formatCommandIndex()}
294
+
284
295
  ## Codeep App Storage (your own metadata)
285
296
  This project uses Codeep. The following paths are Codeep's internal state — **read** them if you need context about prior sessions, but do not edit them manually during a task:
286
297
  - \`${root}/.codeep/intelligence.json\` — cached project analysis (structure, frameworks, CI/CD, conventions). Refresh via \`/scan\`.
@@ -356,10 +367,15 @@ additionalTools) {
356
367
  try {
357
368
  let endpoint;
358
369
  let body;
359
- const useStreaming = Boolean(onChunk);
370
+ // Qwen/DashScope reject `tools` + `stream:true` together; this path always
371
+ // sends tools, so force a non-streamed request there (the non-streaming
372
+ // branch below still emits the content via onChunk). Other providers stream.
373
+ const useStreaming = Boolean(onChunk) && !providerNoStreamWithTools(providerId);
360
374
  // Provider-level guard (OpenAI GPT-5+) OR model-level guard — Anthropic's
361
375
  // Fable 5 / Opus 4.7+ reject temperature with a 400; omission is safe.
362
376
  const tempParam = (requiresDefaultTemperature(providerId) || modelRejectsSamplingParams(model)) ? {} : { temperature: config.get('temperature') };
377
+ // Thinking-effort tier → provider-shaped param ({} for 'auto'/unsupported).
378
+ const reasoningParam = reasoningParamsFor(providerId, model, config.get('reasoningEffort'));
363
379
  if (protocol === 'openai') {
364
380
  const maxTok = getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384));
365
381
  const tokParam = usesMaxCompletionTokens(providerId) ? { max_completion_tokens: maxTok } : { max_tokens: maxTok };
@@ -410,7 +426,7 @@ additionalTools) {
410
426
  body = {
411
427
  model, messages: [{ role: 'system', content: systemPrompt }, ...messages],
412
428
  tools: getOpenAITools(additionalTools), tool_choice: 'auto', stream: useStreaming,
413
- ...tempParam, ...tokParam,
429
+ ...tempParam, ...tokParam, ...reasoningParam,
414
430
  ...(useStreaming && providerId === 'openai' ? { stream_options: { include_usage: true } } : {}),
415
431
  ...openRouterExtras,
416
432
  };
@@ -436,7 +452,7 @@ additionalTools) {
436
452
  system: [{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } }],
437
453
  messages,
438
454
  tools: cachedTools, stream: useStreaming,
439
- ...tempParam, max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
455
+ ...tempParam, ...reasoningParam, max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
440
456
  };
441
457
  }
442
458
  const response = await fetch(endpoint, {
@@ -554,13 +570,15 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
554
570
  // Provider-level guard (OpenAI GPT-5+) OR model-level guard — Anthropic's
555
571
  // Fable 5 / Opus 4.7+ reject temperature with a 400; omission is safe.
556
572
  const tempParam = (requiresDefaultTemperature(providerId) || modelRejectsSamplingParams(model)) ? {} : { temperature: config.get('temperature') };
573
+ // Thinking-effort tier → provider-shaped param ({} for 'auto'/unsupported).
574
+ const reasoningParam = reasoningParamsFor(providerId, model, config.get('reasoningEffort'));
557
575
  if (protocol === 'openai') {
558
576
  const maxTok = getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384));
559
577
  const tokParam = usesMaxCompletionTokens(providerId) ? { max_completion_tokens: maxTok } : { max_tokens: maxTok };
560
578
  endpoint = `${baseUrl}/chat/completions`;
561
579
  body = {
562
580
  model, messages: [{ role: 'system', content: fallbackPrompt }, ...messages],
563
- stream: Boolean(onChunk), ...tempParam, ...tokParam,
581
+ stream: Boolean(onChunk), ...tempParam, ...tokParam, ...reasoningParam,
564
582
  };
565
583
  }
566
584
  else {
@@ -577,7 +595,7 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
577
595
  { role: 'assistant', content: 'Understood. I will use the tools as specified.' },
578
596
  ...messages,
579
597
  ],
580
- stream: Boolean(onChunk), ...tempParam,
598
+ stream: Boolean(onChunk), ...tempParam, ...reasoningParam,
581
599
  max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
582
600
  };
583
601
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Curated, agent-facing index of the most useful Codeep slash-commands.
3
+ *
4
+ * Injected into the agent's system prompt (see `getAgentSystemPrompt`) so the
5
+ * model knows these exist and can point the user at the right one. Deliberately
6
+ * a CURATED subset — not the full ~60-command registry in
7
+ * `renderer/commands.ts` — to keep the prompt small; every entry here must be
8
+ * a real command. Single source for this list so it doesn't drift across the
9
+ * places that describe Codeep to the model.
10
+ */
11
+ export declare const COMMAND_INDEX: {
12
+ cmd: string;
13
+ desc: string;
14
+ }[];
15
+ /** Render the index as Markdown bullets for the system prompt. */
16
+ export declare function formatCommandIndex(): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Curated, agent-facing index of the most useful Codeep slash-commands.
3
+ *
4
+ * Injected into the agent's system prompt (see `getAgentSystemPrompt`) so the
5
+ * model knows these exist and can point the user at the right one. Deliberately
6
+ * a CURATED subset — not the full ~60-command registry in
7
+ * `renderer/commands.ts` — to keep the prompt small; every entry here must be
8
+ * a real command. Single source for this list so it doesn't drift across the
9
+ * places that describe Codeep to the model.
10
+ */
11
+ export const COMMAND_INDEX = [
12
+ { cmd: '/scan', desc: 'analyze the project (structure, frameworks, conventions) into .codeep/intelligence.json' },
13
+ { cmd: '/plan', desc: 'plan a task before executing it (plan mode)' },
14
+ { cmd: '/skills', desc: 'browse, add, or run reusable skill bundles' },
15
+ { cmd: '/mcp', desc: 'connect external tools & data via MCP servers (Postgres, GitHub, a browser, the iOS simulator…)' },
16
+ { cmd: '/agents', desc: 'manage sub-agents you can delegate focused subtasks to' },
17
+ { cmd: '/personality', desc: 'switch the agent persona / working style' },
18
+ { cmd: '/review', desc: 'code-review the current changes' },
19
+ { cmd: '/commit', desc: 'write a commit message from the current diff' },
20
+ { cmd: '/pr', desc: 'open a pull request' },
21
+ { cmd: '/checkpoint', desc: 'save a restore point (/rewind rolls back to it)' },
22
+ { cmd: '/memory', desc: 'add or list project memory notes the agent remembers' },
23
+ { cmd: '/cost', desc: 'show token usage and estimated cost for the session' },
24
+ { cmd: '/settings', desc: 'change models, providers, confirmation policy, and limits' },
25
+ ];
26
+ /** Render the index as Markdown bullets for the system prompt. */
27
+ export function formatCommandIndex() {
28
+ return COMMAND_INDEX.map(c => `- \`${c.cmd}\` — ${c.desc}`).join('\n');
29
+ }
@@ -9,7 +9,9 @@
9
9
  * - Each entry's `args` should be the **invocation-without-runtime-args**
10
10
  * so `/mcp install` can prompt for the things that vary per user
11
11
  * (paths, tokens, etc.) via `argHints`.
12
- * - Prefer official `@modelcontextprotocol/*` packages over third-party.
12
+ * - Prefer official `@modelcontextprotocol/*` packages; well-maintained
13
+ * third-party servers are fine when they're the de-facto standard for
14
+ * their niche (e.g. Playwright for browsers, the iOS-simulator servers).
13
15
  */
14
16
  import type { McpServer } from '../acp/protocol.js';
15
17
  export interface MarketplaceEntry {
@@ -9,7 +9,9 @@
9
9
  * - Each entry's `args` should be the **invocation-without-runtime-args**
10
10
  * so `/mcp install` can prompt for the things that vary per user
11
11
  * (paths, tokens, etc.) via `argHints`.
12
- * - Prefer official `@modelcontextprotocol/*` packages over third-party.
12
+ * - Prefer official `@modelcontextprotocol/*` packages; well-maintained
13
+ * third-party servers are fine when they're the de-facto standard for
14
+ * their niche (e.g. Playwright for browsers, the iOS-simulator servers).
13
15
  */
14
16
  export const MCP_MARKETPLACE = [
15
17
  {
@@ -116,11 +118,25 @@ export const MCP_MARKETPLACE = [
116
118
  url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/time',
117
119
  },
118
120
  {
119
- id: 'puppeteer',
120
- name: 'Puppeteer (browser)',
121
- description: 'Headless Chromium for navigating, screenshotting, and scraping pages.',
122
- server: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-puppeteer'] },
123
- url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer',
121
+ id: 'playwright',
122
+ name: 'Playwright (browser)',
123
+ description: 'Drive a real browser — navigate, click, fill, screenshot, and scrape — via Microsoft Playwright. The de-facto browser-automation MCP (supersedes Puppeteer).',
124
+ server: { command: 'npx', args: ['-y', '@playwright/mcp@latest'] },
125
+ url: 'https://github.com/microsoft/playwright-mcp',
126
+ },
127
+ {
128
+ id: 'ios-simulator',
129
+ name: 'iOS Simulator',
130
+ description: 'Drive the iOS Simulator — tap, type, swipe, screenshot, record, install & launch apps. macOS only; needs Xcode + idb (`brew install facebook/fb/idb-companion`).',
131
+ server: { command: 'npx', args: ['-y', 'ios-simulator-mcp'] },
132
+ url: 'https://github.com/joshuayoes/ios-simulator-mcp',
133
+ },
134
+ {
135
+ id: 'mobile',
136
+ name: 'Mobile (iOS + Android)',
137
+ description: 'UI automation for end-to-end mobile testing across iOS simulators/devices and Android. macOS needs Xcode command-line tools.',
138
+ server: { command: 'npx', args: ['-y', '@mobilenext/mobile-mcp@latest'] },
139
+ url: 'https://github.com/mobile-next/mobile-mcp',
124
140
  },
125
141
  ];
126
142
  export function findMarketplaceEntry(id) {
@@ -26,6 +26,23 @@ const MODEL_CONTEXT_WINDOWS = {
26
26
  'gemini-3-flash-preview': 1_000_000,
27
27
  // MiniMax
28
28
  'MiniMax-M3': 524_288,
29
+ // Kimi (Moonshot) — 256K across the K2.x line
30
+ 'kimi-k2.7-code': 262_144,
31
+ 'kimi-k2.7-code-highspeed': 262_144,
32
+ 'kimi-k2.6': 262_144,
33
+ 'kimi-k2.5': 262_144,
34
+ 'kimi-for-coding': 262_144,
35
+ // Grok (xAI)
36
+ 'grok-build-0.1': 256_000,
37
+ 'grok-4.3': 1_000_000,
38
+ 'grok-code-fast-1': 256_000,
39
+ 'grok-4-fast-reasoning': 2_000_000,
40
+ // Qwen (Alibaba) — 256K native (1M with extrapolation)
41
+ 'qwen3-coder-plus': 262_144,
42
+ 'qwen3-coder-next': 262_144,
43
+ 'qwen3-coder-flash': 262_144,
44
+ 'qwen3-max': 262_144,
45
+ 'Qwen/Qwen3-Coder-480B-A35B-Instruct': 262_144,
29
46
  };
30
47
  const DEFAULT_CONTEXT_WINDOW = 128_000;
31
48
  /**
@@ -62,6 +79,26 @@ const MODEL_PRICING = {
62
79
  'gemini-3-flash-preview': { inputPer1M: 0.50, outputPer1M: 3.00 },
63
80
  // MiniMax
64
81
  'MiniMax-M3': { inputPer1M: 0.60, outputPer1M: 2.40 },
82
+ // Kimi (Moonshot) — pay-per-use cache-miss rates; `kimi-for-coding` is the
83
+ // subscription alias (flat-fee in reality, priced notionally like K2.7 Code).
84
+ 'kimi-k2.7-code': { inputPer1M: 0.60, outputPer1M: 2.50 },
85
+ 'kimi-k2.7-code-highspeed': { inputPer1M: 0.60, outputPer1M: 2.50 },
86
+ 'kimi-k2.6': { inputPer1M: 0.55, outputPer1M: 2.20 },
87
+ 'kimi-k2.5': { inputPer1M: 0.40, outputPer1M: 1.90 },
88
+ 'kimi-for-coding': { inputPer1M: 0.60, outputPer1M: 2.50 },
89
+ // Grok (xAI)
90
+ 'grok-build-0.1': { inputPer1M: 1.00, outputPer1M: 2.00 },
91
+ 'grok-4.3': { inputPer1M: 1.25, outputPer1M: 2.50 },
92
+ 'grok-code-fast-1': { inputPer1M: 0.20, outputPer1M: 1.50 },
93
+ 'grok-4-fast-reasoning': { inputPer1M: 0.20, outputPer1M: 0.50 },
94
+ // Qwen (Alibaba) — qwen3-coder-* 0–256K tier; the Coding Plan is flat-fee so
95
+ // this only affects the pay-per-use estimate.
96
+ 'qwen3-coder-plus': { inputPer1M: 0.28, outputPer1M: 1.65 },
97
+ 'qwen3-coder-next': { inputPer1M: 0.28, outputPer1M: 1.65 },
98
+ 'qwen3-coder-flash': { inputPer1M: 0.10, outputPer1M: 0.50 },
99
+ 'qwen3-max': { inputPer1M: 1.20, outputPer1M: 6.00 },
100
+ // ModelScope free tier — no per-token charge.
101
+ 'Qwen/Qwen3-Coder-480B-A35B-Instruct': { inputPer1M: 0, outputPer1M: 0 },
65
102
  };
66
103
  export function getPricingTable() {
67
104
  return Object.entries(MODEL_PRICING).map(([model, p]) => ({ model, ...p }));
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "2.11.2";
1
+ export declare const VERSION = "2.13.0";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
2
2
  // Baked from package.json at build time so the bun-compiled binary reports
3
3
  // the right version (it has no package.json on disk to read at runtime).
4
- export const VERSION = '2.11.2';
4
+ export const VERSION = '2.13.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "2.11.2",
3
+ "version": "2.13.0",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",