codeep 2.11.1 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -90,8 +90,15 @@ export async function runAgentSession(opts) {
90
90
  const result = await runAgent(opts.prompt, projectContext, {
91
91
  abortSignal: opts.abortSignal,
92
92
  onChunk: (text) => { chunksEmitted++; opts.onChunk(text); },
93
- onIteration: (_iteration, _message) => {
94
- // Intentionally not forwarded — iteration count is internal detail
93
+ onIteration: (_iteration, message) => {
94
+ // The bare iteration counter is internal noise, but transient notices —
95
+ // API retry/backoff ("retrying in Ns") and context warnings (⚠) — are
96
+ // exactly what the user needs to see when a request stalls, otherwise
97
+ // the editor just shows an endless "Thinking…" spinner. Surface those
98
+ // (and only those) as a thought.
99
+ if (opts.onThought && /retry|⚠/i.test(message)) {
100
+ opts.onThought(message);
101
+ }
95
102
  },
96
103
  onThinking: (text) => {
97
104
  if (opts.onThought) {
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,
@@ -74,3 +74,57 @@ export declare function modelRejectsSamplingParams(model: string): boolean;
74
74
  * Falls back to the requested value if no provider limit is set.
75
75
  */
76
76
  export declare function getEffectiveMaxTokens(providerId: string, requested: number): number;
77
+ /**
78
+ * Unified, user-facing thinking-effort tiers (the `/thinking` setting).
79
+ *
80
+ * 'auto' — omit the param entirely → each provider's own default.
81
+ * low / medium / high / max — four explicit depth tiers.
82
+ *
83
+ * The four tiers are CONCEPTUAL. `reasoningParamsFor()` clamps each one to the
84
+ * nearest level the active provider+model actually accepts, so we never send a
85
+ * value that would 400 (e.g. Gemini rejects "medium"; OpenAI has no "max").
86
+ * The control is a pure DEPTH knob on models that already think — it never
87
+ * toggles thinking on/off, which keeps us clear of the reasoning_content-replay
88
+ * contract that DeepSeek/GLM impose when thinking mode is flipped.
89
+ */
90
+ export type ReasoningTier = 'auto' | 'low' | 'medium' | 'high' | 'max';
91
+ export declare const REASONING_TIERS: ReasoningTier[];
92
+ /**
93
+ * Canonicalize a model id for capability matching: lowercase, drop any
94
+ * `vendor/` namespace (OpenRouter sends `anthropic/claude-opus-4.8`), and
95
+ * normalize `.` version separators to `-` (`glm-5.2` → `glm-5-2`,
96
+ * `claude-opus-4.8` → `claude-opus-4-8`). Mirrors macOS `ModelTuning.canonicalModelID`.
97
+ */
98
+ export declare function canonicalModelId(model: string): string;
99
+ /**
100
+ * Does this provider+model expose a GRADED thinking-effort control we can drive?
101
+ * Used to gate the `/thinking` UI — hidden entirely for models without one.
102
+ * Keep in lockstep with macOS `ModelTuning.reasoningEffortSupported`.
103
+ */
104
+ export declare function modelSupportsReasoningEffort(providerId: string, model: string): boolean;
105
+ /**
106
+ * Build the request-body fields that carry the chosen effort tier for the
107
+ * active provider+model+protocol. Returns `{}` for 'auto', unsupported
108
+ * models, or providers without a graded knob — so callers can spread it
109
+ * unconditionally. Keep in lockstep with macOS `ModelTuning.reasoningParams`.
110
+ */
111
+ export declare function reasoningParamsFor(providerId: string, model: string, tier: ReasoningTier): Record<string, unknown>;
112
+ /**
113
+ * The DISTINCT tiers a given provider+model actually exposes — used to build a
114
+ * per-model picker that only offers levels the model can tell apart (e.g.
115
+ * GLM-5.2/DeepSeek grade only high|max; Gemini via the OpenAI-compat layer only
116
+ * low|high). Always leads with 'auto'. `[]` for models with no graded knob.
117
+ *
118
+ * Kept in lockstep with `reasoningParamsFor` (the providers-test asserts every
119
+ * listed tier yields a DISTINCT param, so this can't silently drift). Mirrors
120
+ * macOS `ModelTuning.availableReasoningTiers`.
121
+ */
122
+ export declare function availableReasoningTiers(providerId: string, model: string): ReasoningTier[];
123
+ /**
124
+ * Map a (possibly out-of-range) tier to the tier this model actually distinguishes,
125
+ * for display — the chip + the checked menu row. The effort setting is global, so
126
+ * a tier picked on Opus ('low') may not exist on GLM-5.2; we show the level GLM
127
+ * will really run (its 'low' clamps to 'high'). Picks the available tier whose
128
+ * effective param equals the requested one. 'auto' (or unsupported) → 'auto'.
129
+ */
130
+ export declare function resolveReasoningTier(providerId: string, model: string, tier: ReasoningTier): ReasoningTier;
@@ -19,9 +19,7 @@ export const PROVIDERS = {
19
19
  },
20
20
  models: [
21
21
  { id: 'glm-5.2', name: 'GLM-5.2', description: 'Latest GLM model, available to all users' },
22
- { id: 'glm-5.1', name: 'GLM-5.1', description: 'Previous GLM model, available to all users' },
23
22
  { id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant, available to all users' },
24
- { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM-5 model (Pro/Max plan only)' },
25
23
  ],
26
24
  defaultModel: 'glm-5.2',
27
25
  defaultProtocol: 'openai',
@@ -47,9 +45,7 @@ export const PROVIDERS = {
47
45
  },
48
46
  models: [
49
47
  { id: 'glm-5.2', name: 'GLM-5.2', description: 'Latest GLM model' },
50
- { id: 'glm-5.1', name: 'GLM-5.1', description: 'Previous GLM model' },
51
48
  { id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant' },
52
- { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM-5 model' },
53
49
  ],
54
50
  defaultModel: 'glm-5.2',
55
51
  defaultProtocol: 'openai',
@@ -75,9 +71,7 @@ export const PROVIDERS = {
75
71
  },
76
72
  models: [
77
73
  { id: 'glm-5.2', name: 'GLM-5.2', description: 'Latest GLM model, available to all users' },
78
- { id: 'glm-5.1', name: 'GLM-5.1', description: 'Previous GLM model, available to all users' },
79
74
  { id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant, available to all users' },
80
- { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM-5 model (Pro/Max plan only)' },
81
75
  ],
82
76
  defaultModel: 'glm-5.2',
83
77
  defaultProtocol: 'openai',
@@ -103,9 +97,7 @@ export const PROVIDERS = {
103
97
  },
104
98
  models: [
105
99
  { id: 'glm-5.2', name: 'GLM-5.2', description: 'Latest GLM model' },
106
- { id: 'glm-5.1', name: 'GLM-5.1', description: 'Previous GLM model' },
107
100
  { id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant' },
108
- { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM-5 model' },
109
101
  ],
110
102
  defaultModel: 'glm-5.2',
111
103
  defaultProtocol: 'openai',
@@ -225,7 +217,6 @@ export const PROVIDERS = {
225
217
  { id: 'gpt-5.5', name: 'GPT-5.5', description: 'Latest GPT model — best for coding' },
226
218
  { id: 'gpt-5.4', name: 'GPT-5.4', description: 'Previous generation GPT' },
227
219
  { id: 'gpt-5.4-mini', name: 'GPT-5.4 Mini', description: 'Faster and cheaper GPT-5.4' },
228
- { id: 'gpt-5.4-nano', name: 'GPT-5.4 Nano', description: 'Most affordable, great for simple tasks' },
229
220
  ],
230
221
  defaultModel: 'gpt-5.5',
231
222
  defaultProtocol: 'openai',
@@ -247,7 +238,6 @@ export const PROVIDERS = {
247
238
  },
248
239
  },
249
240
  models: [
250
- { id: 'claude-fable-5', name: 'Claude Fable 5', description: 'Most powerful — new tier above Opus' },
251
241
  { id: 'claude-opus-4-8', name: 'Claude Opus 4.8', description: 'Most capable Opus model' },
252
242
  { id: 'claude-sonnet-4-6', name: 'Claude Sonnet', description: 'Best balance of speed and intelligence' },
253
243
  { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku', description: 'Fastest and most affordable' },
@@ -472,3 +462,151 @@ export function getEffectiveMaxTokens(providerId, requested) {
472
462
  return requested;
473
463
  return Math.min(requested, provider.maxOutputTokens);
474
464
  }
465
+ export const REASONING_TIERS = ['auto', 'low', 'medium', 'high', 'max'];
466
+ /**
467
+ * Canonicalize a model id for capability matching: lowercase, drop any
468
+ * `vendor/` namespace (OpenRouter sends `anthropic/claude-opus-4.8`), and
469
+ * normalize `.` version separators to `-` (`glm-5.2` → `glm-5-2`,
470
+ * `claude-opus-4.8` → `claude-opus-4-8`). Mirrors macOS `ModelTuning.canonicalModelID`.
471
+ */
472
+ export function canonicalModelId(model) {
473
+ let id = model.toLowerCase();
474
+ const slash = id.lastIndexOf('/');
475
+ if (slash !== -1)
476
+ id = id.slice(slash + 1);
477
+ return id.replace(/\./g, '-');
478
+ }
479
+ /** True when `id` equals `prefix` or starts with `prefix-` (catches dated variants). */
480
+ function idMatches(id, prefix) {
481
+ return id === prefix || id.startsWith(`${prefix}-`);
482
+ }
483
+ /**
484
+ * Does this provider+model expose a GRADED thinking-effort control we can drive?
485
+ * Used to gate the `/thinking` UI — hidden entirely for models without one.
486
+ * Keep in lockstep with macOS `ModelTuning.reasoningEffortSupported`.
487
+ */
488
+ export function modelSupportsReasoningEffort(providerId, model) {
489
+ const id = canonicalModelId(model);
490
+ switch (providerId) {
491
+ case 'anthropic':
492
+ // Effort is GA on Opus 4.5+, Sonnet 4.6, Fable 5 — NOT Haiku or Sonnet 4.5.
493
+ if (idMatches(id, 'claude-haiku-4-5') || idMatches(id, 'claude-sonnet-4-5'))
494
+ return false;
495
+ return /^claude-(opus-4-([5-9]|\d\d)|sonnet-4-6|fable-5)/.test(id);
496
+ case 'openai':
497
+ // GPT-5.x are reasoning models — reasoning_effort across the family (incl. mini).
498
+ return id.startsWith('gpt-5');
499
+ case 'google':
500
+ // Gemini 3.x thinking_level via the OpenAI-compat reasoning_effort mapping.
501
+ return id.startsWith('gemini-3');
502
+ case 'deepseek':
503
+ return id.startsWith('deepseek-v4');
504
+ case 'z.ai':
505
+ case 'z.ai-api':
506
+ case 'z.ai-cn':
507
+ case 'z.ai-cn-api':
508
+ // GLM-5.2 added graded High/Max effort. glm-5-turbo is a plain thinking
509
+ // toggle (no graded levels) so it stays out.
510
+ return idMatches(id, 'glm-5-2');
511
+ case 'openrouter':
512
+ // OpenRouter normalizes a unified `reasoning` field and silently ignores
513
+ // it for non-reasoning models, so the control is always safe to expose.
514
+ return true;
515
+ default:
516
+ // minimax (toggle only), ollama, custom — no graded depth knob.
517
+ return false;
518
+ }
519
+ }
520
+ /**
521
+ * Build the request-body fields that carry the chosen effort tier for the
522
+ * active provider+model+protocol. Returns `{}` for 'auto', unsupported
523
+ * models, or providers without a graded knob — so callers can spread it
524
+ * unconditionally. Keep in lockstep with macOS `ModelTuning.reasoningParams`.
525
+ */
526
+ export function reasoningParamsFor(providerId, model, tier) {
527
+ // 'auto', or any unexpected value from an older/garbled config, → no param.
528
+ // (Guards against ever emitting e.g. `effort: undefined`, which could 400.)
529
+ if (tier === 'auto' || !REASONING_TIERS.includes(tier))
530
+ return {};
531
+ if (!modelSupportsReasoningEffort(providerId, model))
532
+ return {};
533
+ switch (providerId) {
534
+ case 'anthropic':
535
+ // low / medium / high / max — all valid on the capable Claude models.
536
+ return { output_config: { effort: tier } };
537
+ case 'openai':
538
+ // none/low/medium/high/xhigh — no "max"; map our Max → xhigh (the ceiling).
539
+ return { reasoning_effort: tier === 'max' ? 'xhigh' : tier };
540
+ case 'google':
541
+ // Gemini 3 (OpenAI-compat) accepts ONLY low/high — "medium" 400s.
542
+ return { reasoning_effort: tier === 'low' ? 'low' : 'high' };
543
+ case 'deepseek':
544
+ case 'z.ai':
545
+ case 'z.ai-api':
546
+ case 'z.ai-cn':
547
+ case 'z.ai-cn-api':
548
+ // Graded thinking depth: high (default) or max. Lower tiers collapse to high.
549
+ return { reasoning_effort: tier === 'max' ? 'max' : 'high' };
550
+ case 'openrouter':
551
+ // Unified reasoning object; no "max" effort → cap at high.
552
+ return { reasoning: { effort: tier === 'max' ? 'high' : tier } };
553
+ default:
554
+ return {};
555
+ }
556
+ }
557
+ /**
558
+ * The DISTINCT tiers a given provider+model actually exposes — used to build a
559
+ * per-model picker that only offers levels the model can tell apart (e.g.
560
+ * GLM-5.2/DeepSeek grade only high|max; Gemini via the OpenAI-compat layer only
561
+ * low|high). Always leads with 'auto'. `[]` for models with no graded knob.
562
+ *
563
+ * Kept in lockstep with `reasoningParamsFor` (the providers-test asserts every
564
+ * listed tier yields a DISTINCT param, so this can't silently drift). Mirrors
565
+ * macOS `ModelTuning.availableReasoningTiers`.
566
+ */
567
+ export function availableReasoningTiers(providerId, model) {
568
+ if (!modelSupportsReasoningEffort(providerId, model))
569
+ return [];
570
+ switch (providerId) {
571
+ case 'anthropic':
572
+ case 'openai':
573
+ return ['auto', 'low', 'medium', 'high', 'max'];
574
+ case 'google':
575
+ // OpenAI-compat layer accepts only low/high — "medium" 400s.
576
+ return ['auto', 'low', 'high'];
577
+ case 'deepseek':
578
+ case 'z.ai':
579
+ case 'z.ai-api':
580
+ case 'z.ai-cn':
581
+ case 'z.ai-cn-api':
582
+ return ['auto', 'high', 'max'];
583
+ case 'openrouter':
584
+ return ['auto', 'low', 'medium', 'high'];
585
+ default:
586
+ return [];
587
+ }
588
+ }
589
+ /**
590
+ * Map a (possibly out-of-range) tier to the tier this model actually distinguishes,
591
+ * for display — the chip + the checked menu row. The effort setting is global, so
592
+ * a tier picked on Opus ('low') may not exist on GLM-5.2; we show the level GLM
593
+ * will really run (its 'low' clamps to 'high'). Picks the available tier whose
594
+ * effective param equals the requested one. 'auto' (or unsupported) → 'auto'.
595
+ */
596
+ export function resolveReasoningTier(providerId, model, tier) {
597
+ if (tier === 'auto')
598
+ return 'auto';
599
+ const avail = availableReasoningTiers(providerId, model);
600
+ if (avail.length === 0)
601
+ return 'auto';
602
+ if (avail.includes(tier))
603
+ return tier;
604
+ const target = JSON.stringify(reasoningParamsFor(providerId, model, tier));
605
+ for (const t of avail) {
606
+ if (t === 'auto')
607
+ continue;
608
+ if (JSON.stringify(reasoningParamsFor(providerId, model, t)) === target)
609
+ return t;
610
+ }
611
+ return 'auto';
612
+ }
@@ -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 } 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\`.
@@ -360,6 +371,8 @@ additionalTools) {
360
371
  // Provider-level guard (OpenAI GPT-5+) OR model-level guard — Anthropic's
361
372
  // Fable 5 / Opus 4.7+ reject temperature with a 400; omission is safe.
362
373
  const tempParam = (requiresDefaultTemperature(providerId) || modelRejectsSamplingParams(model)) ? {} : { temperature: config.get('temperature') };
374
+ // Thinking-effort tier → provider-shaped param ({} for 'auto'/unsupported).
375
+ const reasoningParam = reasoningParamsFor(providerId, model, config.get('reasoningEffort'));
363
376
  if (protocol === 'openai') {
364
377
  const maxTok = getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384));
365
378
  const tokParam = usesMaxCompletionTokens(providerId) ? { max_completion_tokens: maxTok } : { max_tokens: maxTok };
@@ -410,7 +423,7 @@ additionalTools) {
410
423
  body = {
411
424
  model, messages: [{ role: 'system', content: systemPrompt }, ...messages],
412
425
  tools: getOpenAITools(additionalTools), tool_choice: 'auto', stream: useStreaming,
413
- ...tempParam, ...tokParam,
426
+ ...tempParam, ...tokParam, ...reasoningParam,
414
427
  ...(useStreaming && providerId === 'openai' ? { stream_options: { include_usage: true } } : {}),
415
428
  ...openRouterExtras,
416
429
  };
@@ -436,7 +449,7 @@ additionalTools) {
436
449
  system: [{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } }],
437
450
  messages,
438
451
  tools: cachedTools, stream: useStreaming,
439
- ...tempParam, max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
452
+ ...tempParam, ...reasoningParam, max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
440
453
  };
441
454
  }
442
455
  const response = await fetch(endpoint, {
@@ -554,13 +567,15 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
554
567
  // Provider-level guard (OpenAI GPT-5+) OR model-level guard — Anthropic's
555
568
  // Fable 5 / Opus 4.7+ reject temperature with a 400; omission is safe.
556
569
  const tempParam = (requiresDefaultTemperature(providerId) || modelRejectsSamplingParams(model)) ? {} : { temperature: config.get('temperature') };
570
+ // Thinking-effort tier → provider-shaped param ({} for 'auto'/unsupported).
571
+ const reasoningParam = reasoningParamsFor(providerId, model, config.get('reasoningEffort'));
557
572
  if (protocol === 'openai') {
558
573
  const maxTok = getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384));
559
574
  const tokParam = usesMaxCompletionTokens(providerId) ? { max_completion_tokens: maxTok } : { max_tokens: maxTok };
560
575
  endpoint = `${baseUrl}/chat/completions`;
561
576
  body = {
562
577
  model, messages: [{ role: 'system', content: fallbackPrompt }, ...messages],
563
- stream: Boolean(onChunk), ...tempParam, ...tokParam,
578
+ stream: Boolean(onChunk), ...tempParam, ...tokParam, ...reasoningParam,
564
579
  };
565
580
  }
566
581
  else {
@@ -577,7 +592,7 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
577
592
  { role: 'assistant', content: 'Understood. I will use the tools as specified.' },
578
593
  ...messages,
579
594
  ],
580
- stream: Boolean(onChunk), ...tempParam,
595
+ stream: Boolean(onChunk), ...tempParam, ...reasoningParam,
581
596
  max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
582
597
  };
583
598
  }
@@ -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) {
@@ -8,16 +8,12 @@
8
8
  const MODEL_CONTEXT_WINDOWS = {
9
9
  // Z.AI / ZhipuAI
10
10
  'glm-5.2': 200_000,
11
- 'glm-5.1': 131_072,
12
- 'glm-5': 80_000,
13
11
  'glm-5-turbo': 202_752,
14
12
  // OpenAI
15
13
  'gpt-5.5': 1_200_000,
16
14
  'gpt-5.4': 1_050_000,
17
15
  'gpt-5.4-mini': 400_000,
18
- 'gpt-5.4-nano': 400_000,
19
16
  // Anthropic
20
- 'claude-fable-5': 1_000_000,
21
17
  'claude-opus-4-8': 1_000_000,
22
18
  'claude-sonnet-4-6': 1_000_000,
23
19
  'claude-haiku-4-5-20251001': 200_000,
@@ -48,16 +44,12 @@ const MODEL_PRICING = {
48
44
  // Note: on the GLM Coding Plan (the default `z.ai` provider) billing is a flat
49
45
  // subscription, so this only affects the pay-per-use estimate.
50
46
  'glm-5.2': { inputPer1M: 1.00, outputPer1M: 3.20 },
51
- 'glm-5.1': { inputPer1M: 1.00, outputPer1M: 3.20 },
52
- 'glm-5': { inputPer1M: 0.72, outputPer1M: 2.30 },
53
47
  'glm-5-turbo': { inputPer1M: 1.20, outputPer1M: 4.00 },
54
48
  // OpenAI
55
49
  'gpt-5.5': { inputPer1M: 5.00, outputPer1M: 30.00 },
56
50
  'gpt-5.4': { inputPer1M: 2.50, outputPer1M: 15.00 },
57
51
  'gpt-5.4-mini': { inputPer1M: 0.75, outputPer1M: 4.50 },
58
- 'gpt-5.4-nano': { inputPer1M: 0.20, outputPer1M: 1.25 },
59
52
  // Anthropic
60
- 'claude-fable-5': { inputPer1M: 10.00, outputPer1M: 50.00 },
61
53
  'claude-opus-4-8': { inputPer1M: 5.00, outputPer1M: 25.00 },
62
54
  'claude-sonnet-4-6': { inputPer1M: 3.00, outputPer1M: 15.00 },
63
55
  'claude-haiku-4-5-20251001': { inputPer1M: 1.00, outputPer1M: 5.00 },
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "2.11.1";
1
+ export declare const VERSION = "2.12.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.1';
4
+ export const VERSION = '2.12.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "2.11.1",
3
+ "version": "2.12.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",