@corbat-tech/coco 2.33.1 → 2.34.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/cli/index.js CHANGED
@@ -158,6 +158,19 @@ function getProxyFromSystem(platform = process.platform, run = defaultRunner) {
158
158
  }
159
159
  return null;
160
160
  }
161
+ function detectPacProxy(run = defaultRunner, platform = process.platform) {
162
+ if (platform !== "darwin") return null;
163
+ const out = run("scutil", ["--proxy"]);
164
+ if (!out) return null;
165
+ const getField = (name) => {
166
+ const re = new RegExp(`^\\s*${name}\\s*:\\s*(.+?)\\s*$`, "m");
167
+ return out.match(re)?.[1];
168
+ };
169
+ if (getField("ProxyAutoConfigEnable") === "1") {
170
+ return getField("ProxyAutoConfigURLString") ?? "PAC script";
171
+ }
172
+ return null;
173
+ }
161
174
  function installProxyDispatcher(resolveSystem = () => getProxyFromSystem()) {
162
175
  if (installed) {
163
176
  const existing = getProxyFromEnv();
@@ -1706,6 +1719,44 @@ function isCopilotTokenExpired(creds) {
1706
1719
  if (!creds.copilotToken || !creds.copilotTokenExpiresAt) return true;
1707
1720
  return Date.now() >= creds.copilotTokenExpiresAt - REFRESH_BUFFER_MS;
1708
1721
  }
1722
+ function exchangeForCopilotTokenViaGhCli() {
1723
+ return new Promise((resolve4) => {
1724
+ execFile("gh", ["api", "/copilot_internal/v2/token"], { timeout: 1e4 }, (err, stdout) => {
1725
+ if (err || !stdout) {
1726
+ resolve4(null);
1727
+ return;
1728
+ }
1729
+ try {
1730
+ const parsed = JSON.parse(stdout);
1731
+ resolve4(parsed.token && parsed.expires_at ? parsed : null);
1732
+ } catch {
1733
+ resolve4(null);
1734
+ }
1735
+ });
1736
+ });
1737
+ }
1738
+ function getGitHubCliAuthStatus() {
1739
+ return new Promise((resolve4) => {
1740
+ execFile(
1741
+ "gh",
1742
+ ["auth", "status", "--hostname", "github.com"],
1743
+ { timeout: 5e3 },
1744
+ (_err, stdout, stderr) => {
1745
+ const combined = (stdout ?? "") + (stderr ?? "");
1746
+ const match = combined.match(/Logged in to github\.com account (\S+)/);
1747
+ if (match) {
1748
+ resolve4(match[1]);
1749
+ return;
1750
+ }
1751
+ if (combined.includes("Logged in")) {
1752
+ resolve4("authenticated");
1753
+ return;
1754
+ }
1755
+ resolve4(null);
1756
+ }
1757
+ );
1758
+ });
1759
+ }
1709
1760
  async function getValidCopilotToken() {
1710
1761
  const creds = await loadCopilotCredentials();
1711
1762
  const envToken = process.env["COPILOT_GITHUB_TOKEN"] || process.env["GH_TOKEN"] || process.env["GITHUB_TOKEN"];
@@ -1719,8 +1770,7 @@ async function getValidCopilotToken() {
1719
1770
  isNew: false
1720
1771
  };
1721
1772
  }
1722
- try {
1723
- const copilotToken = await exchangeForCopilotToken(githubToken);
1773
+ const saveAndReturn = async (copilotToken) => {
1724
1774
  const updatedCreds = {
1725
1775
  ...creds ?? { githubToken },
1726
1776
  githubToken: creds?.githubToken ?? githubToken,
@@ -1734,11 +1784,23 @@ async function getValidCopilotToken() {
1734
1784
  baseUrl: getCopilotBaseUrl(updatedCreds.accountType),
1735
1785
  isNew: true
1736
1786
  };
1787
+ };
1788
+ try {
1789
+ const copilotToken = await exchangeForCopilotToken(githubToken);
1790
+ return saveAndReturn(copilotToken);
1737
1791
  } catch (error) {
1738
1792
  if (error instanceof CopilotAuthError && error.permanent) {
1793
+ const ghCliToken2 = await exchangeForCopilotTokenViaGhCli();
1794
+ if (ghCliToken2) {
1795
+ return saveAndReturn(ghCliToken2);
1796
+ }
1739
1797
  await deleteCopilotCredentials();
1740
1798
  return null;
1741
1799
  }
1800
+ const ghCliToken = await exchangeForCopilotTokenViaGhCli();
1801
+ if (ghCliToken) {
1802
+ return saveAndReturn(ghCliToken);
1803
+ }
1742
1804
  throw error;
1743
1805
  }
1744
1806
  }
@@ -2310,6 +2372,51 @@ async function runApiKeyFlow(provider) {
2310
2372
  console.log(chalk.green("\n \u2705 API key saved!\n"));
2311
2373
  return { tokens, accessToken: apiKey };
2312
2374
  }
2375
+ async function runCopilotAuthViaGhCli(ghCliUser) {
2376
+ const spinner18 = p26.spinner();
2377
+ spinner18.start("Exchanging GitHub CLI credentials for Copilot token...");
2378
+ try {
2379
+ const githubToken = await getGitHubCliToken();
2380
+ if (!githubToken) {
2381
+ spinner18.stop(chalk.red("\u2717 Could not read gh auth token"));
2382
+ return null;
2383
+ }
2384
+ let copilotToken;
2385
+ try {
2386
+ copilotToken = await exchangeForCopilotToken(githubToken);
2387
+ } catch {
2388
+ copilotToken = await exchangeForCopilotTokenViaGhCli();
2389
+ }
2390
+ if (!copilotToken) {
2391
+ spinner18.stop(chalk.red("\u2717 Could not obtain Copilot token via gh CLI"));
2392
+ console.log(chalk.dim(" Ensure your GitHub account has an active Copilot subscription:"));
2393
+ console.log(chalk.cyan(" \u2192 https://github.com/settings/copilot"));
2394
+ return null;
2395
+ }
2396
+ const creds = {
2397
+ githubToken,
2398
+ copilotToken: copilotToken.token,
2399
+ copilotTokenExpiresAt: copilotToken.expires_at * 1e3,
2400
+ accountType: copilotToken.annotations?.copilot_plan
2401
+ };
2402
+ await saveCopilotCredentials(creds);
2403
+ spinner18.stop(chalk.green("\u2713 GitHub Copilot authenticated via gh CLI!"));
2404
+ const userLabel = ghCliUser !== "authenticated" ? ` (@${ghCliUser})` : "";
2405
+ console.log(chalk.dim(` Account${userLabel} \xB7 Plan: ${creds.accountType ?? "individual"}`));
2406
+ console.log(chalk.dim(" Credentials stored in ~/.coco/tokens/copilot.json\n"));
2407
+ const tokens = {
2408
+ accessToken: copilotToken.token,
2409
+ tokenType: "Bearer",
2410
+ expiresAt: copilotToken.expires_at * 1e3
2411
+ };
2412
+ return { tokens, accessToken: copilotToken.token };
2413
+ } catch (error) {
2414
+ const { code } = describeFetchError(error);
2415
+ spinner18.stop(chalk.red("\u2717 Failed to authenticate via gh CLI"));
2416
+ printNetworkTroubleshooting(code);
2417
+ return null;
2418
+ }
2419
+ }
2313
2420
  async function runCopilotDeviceFlow() {
2314
2421
  console.log();
2315
2422
  console.log(chalk.magenta(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
@@ -2321,6 +2428,21 @@ async function runCopilotDeviceFlow() {
2321
2428
  console.log(chalk.dim(" Requires an active GitHub Copilot subscription."));
2322
2429
  console.log(chalk.dim(" https://github.com/settings/copilot"));
2323
2430
  console.log();
2431
+ const ghCliUser = await getGitHubCliAuthStatus();
2432
+ if (ghCliUser) {
2433
+ console.log(
2434
+ chalk.dim(` \u2139 GitHub CLI session detected`) + (ghCliUser !== "authenticated" ? chalk.dim(` (@${ghCliUser})`) : "") + chalk.dim(".")
2435
+ );
2436
+ const useGhSession = await p26.confirm({
2437
+ message: "Use your existing `gh` session? (recommended on corporate networks)",
2438
+ initialValue: true
2439
+ });
2440
+ if (p26.isCancel(useGhSession)) return null;
2441
+ if (useGhSession) {
2442
+ return runCopilotAuthViaGhCli(ghCliUser);
2443
+ }
2444
+ console.log();
2445
+ }
2324
2446
  try {
2325
2447
  console.log(chalk.dim(" Requesting device code from GitHub..."));
2326
2448
  const deviceCode = await requestGitHubDeviceCode();
@@ -2423,23 +2545,38 @@ async function runCopilotDeviceFlow() {
2423
2545
  }
2424
2546
  function printNetworkTroubleshooting(code) {
2425
2547
  const proxy = getProxyFromEnv();
2548
+ const pacUrl = detectPacProxy();
2426
2549
  if (proxy) {
2427
2550
  console.log(chalk.dim(` Proxy in use: ${maskProxyUrl(proxy)}`));
2428
2551
  console.log(chalk.dim(" \u2192 Verify the proxy allows github.com and api.github.com."));
2552
+ } else if (pacUrl) {
2553
+ console.log(
2554
+ chalk.dim(" Automatic proxy (PAC script) detected \u2014 Node.js cannot evaluate it.")
2555
+ );
2556
+ console.log(chalk.dim(" You have two options:"));
2557
+ console.log(chalk.dim(" 1. Run `gh auth login` first, then re-run /provider copilot."));
2558
+ console.log(
2559
+ chalk.dim(
2560
+ " Coco will reuse your `gh` session (Go HTTP client handles PAC automatically)."
2561
+ )
2562
+ );
2563
+ console.log(chalk.dim(" 2. Set HTTPS_PROXY=http://<your-proxy>:<port> manually and retry."));
2429
2564
  } else {
2430
2565
  console.log(chalk.dim(" No HTTPS_PROXY / HTTP_PROXY env vars detected."));
2431
2566
  console.log(chalk.dim(" \u2192 If you're behind a corporate proxy, set HTTPS_PROXY and retry."));
2567
+ console.log(chalk.dim(" \u2192 Or run `gh auth login` first \u2014 Coco will reuse the gh session."));
2432
2568
  }
2433
2569
  if (code === "SELF_SIGNED_CERT_IN_CHAIN" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") {
2434
2570
  console.log(
2435
2571
  chalk.dim(
2436
- " \u2192 A TLS interceptor is rewriting certificates. Add your corp root CA to NODE_EXTRA_CA_CERTS."
2572
+ " \u2192 TLS interceptor detected. Add your corporate root CA to NODE_EXTRA_CA_CERTS."
2437
2573
  )
2438
2574
  );
2575
+ console.log(chalk.dim(" Example: NODE_EXTRA_CA_CERTS=/path/to/corp-ca.crt coco"));
2439
2576
  } else if (code === "ENOTFOUND") {
2440
2577
  console.log(chalk.dim(" \u2192 Check DNS: `nslookup github.com`"));
2441
2578
  } else if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT") {
2442
- console.log(chalk.dim(" \u2192 A firewall may be dropping the connection silently."));
2579
+ console.log(chalk.dim(" \u2192 A firewall may be blocking the connection. Try `gh auth login`."));
2443
2580
  }
2444
2581
  }
2445
2582
  async function getOrRefreshOAuthToken(provider) {
@@ -2690,10 +2827,12 @@ __export(env_exports, {
2690
2827
  getBaseUrl: () => getBaseUrl,
2691
2828
  getDefaultModel: () => getDefaultModel,
2692
2829
  getDefaultProvider: () => getDefaultProvider,
2830
+ getEditorModel: () => getEditorModel,
2693
2831
  getInternalProviderId: () => getInternalProviderId,
2694
2832
  getLastUsedModel: () => getLastUsedModel,
2695
2833
  getLastUsedProvider: () => getLastUsedProvider,
2696
2834
  getLastUsedThinking: () => getLastUsedThinking,
2835
+ getWeakModel: () => getWeakModel,
2697
2836
  isOAuthProvider: () => isOAuthProvider,
2698
2837
  migrateOldPreferences: () => migrateOldPreferences,
2699
2838
  removeEnvProvider: () => removeEnvProvider,
@@ -3137,6 +3276,18 @@ async function migrateOldPreferences() {
3137
3276
  } catch {
3138
3277
  }
3139
3278
  }
3279
+ function getEditorModel() {
3280
+ const raw = process.env["COCO_EDITOR_MODEL"]?.trim();
3281
+ if (!raw || raw.length === 0) return void 0;
3282
+ if (["none", "default", "null", "undefined"].includes(raw.toLowerCase())) return void 0;
3283
+ return raw;
3284
+ }
3285
+ function getWeakModel() {
3286
+ const raw = process.env["COCO_WEAK_MODEL"]?.trim();
3287
+ if (!raw || raw.length === 0) return void 0;
3288
+ if (["none", "default", "null", "undefined"].includes(raw.toLowerCase())) return void 0;
3289
+ return raw;
3290
+ }
3140
3291
  var VALID_PROVIDERS, env;
3141
3292
  var init_env = __esm({
3142
3293
  "src/config/env.ts"() {
@@ -3166,7 +3317,9 @@ var init_env = __esm({
3166
3317
  provider: getDefaultProvider(),
3167
3318
  getApiKey,
3168
3319
  getBaseUrl,
3169
- getDefaultModel
3320
+ getDefaultModel,
3321
+ getWeakModel,
3322
+ getEditorModel
3170
3323
  };
3171
3324
  }
3172
3325
  });
@@ -4208,6 +4361,134 @@ var init_tool_call_normalizer = __esm({
4208
4361
  };
4209
4362
  }
4210
4363
  });
4364
+
4365
+ // src/providers/model-tier.ts
4366
+ function matchTier(model, table) {
4367
+ const lower = model.toLowerCase();
4368
+ const sorted = [...table].sort((a, b) => b.prefix.length - a.prefix.length);
4369
+ for (const { prefix, tier } of sorted) {
4370
+ if (lower.startsWith(prefix.toLowerCase())) return tier;
4371
+ }
4372
+ return null;
4373
+ }
4374
+ function getModelTier(provider, model) {
4375
+ if (!model) return "standard";
4376
+ const p47 = provider.toLowerCase();
4377
+ if (p47 === "anthropic") {
4378
+ return matchTier(model, ANTHROPIC_TIERS) ?? "standard";
4379
+ }
4380
+ if (p47 === "kimi-code") {
4381
+ return matchTier(model, KIMI_TIERS) ?? matchTier(model, ANTHROPIC_TIERS) ?? "standard";
4382
+ }
4383
+ if (p47 === "openai" || p47 === "copilot" || p47 === "codex") {
4384
+ if (model.startsWith("claude-")) {
4385
+ return matchTier(model, ANTHROPIC_TIERS) ?? "standard";
4386
+ }
4387
+ const evalMatch = matchTier(model, EVAL_TIERS);
4388
+ if (evalMatch) return evalMatch;
4389
+ return matchTier(model, OPENAI_TIERS) ?? "standard";
4390
+ }
4391
+ if (p47 === "gemini" || p47 === "vertex") {
4392
+ return matchTier(model, GEMINI_TIERS) ?? "standard";
4393
+ }
4394
+ if (p47 === "kimi" || p47 === "moonshot") {
4395
+ return matchTier(model, KIMI_TIERS) ?? "standard";
4396
+ }
4397
+ return "standard";
4398
+ }
4399
+ function getTierConfig(provider, model) {
4400
+ return TIER_CONFIGS[getModelTier(provider, model)];
4401
+ }
4402
+ var TIER_CONFIGS, ANTHROPIC_TIERS, OPENAI_TIERS, GEMINI_TIERS, KIMI_TIERS, EVAL_TIERS;
4403
+ var init_model_tier = __esm({
4404
+ "src/providers/model-tier.ts"() {
4405
+ TIER_CONFIGS = {
4406
+ mini: {
4407
+ maxTools: 12,
4408
+ parallelToolCalls: false,
4409
+ compactionThreshold: 0.5,
4410
+ supportsCoT: false
4411
+ },
4412
+ standard: {
4413
+ maxTools: 40,
4414
+ parallelToolCalls: true,
4415
+ compactionThreshold: 0.75,
4416
+ supportsCoT: true
4417
+ },
4418
+ advanced: {
4419
+ maxTools: 128,
4420
+ parallelToolCalls: true,
4421
+ compactionThreshold: 0.8,
4422
+ supportsCoT: true
4423
+ }
4424
+ };
4425
+ ANTHROPIC_TIERS = [
4426
+ // Haiku — mini tier
4427
+ { prefix: "claude-haiku", tier: "mini" },
4428
+ { prefix: "claude-3-haiku", tier: "mini" },
4429
+ // Sonnet / Claude 3.5 — standard tier
4430
+ { prefix: "claude-3-5-sonnet", tier: "standard" },
4431
+ { prefix: "claude-3-7-sonnet", tier: "standard" },
4432
+ { prefix: "claude-sonnet", tier: "standard" },
4433
+ // Opus — advanced tier
4434
+ { prefix: "claude-opus", tier: "advanced" },
4435
+ { prefix: "claude-3-opus", tier: "advanced" }
4436
+ // claude-4+ (future) — default to standard unless matched above
4437
+ ];
4438
+ OPENAI_TIERS = [
4439
+ // Mini models
4440
+ { prefix: "gpt-4o-mini", tier: "mini" },
4441
+ { prefix: "gpt-5-mini", tier: "mini" },
4442
+ { prefix: "gpt-5.4-mini", tier: "mini" },
4443
+ { prefix: "gpt-5.3-mini", tier: "mini" },
4444
+ { prefix: "o1-mini", tier: "mini" },
4445
+ { prefix: "o3-mini", tier: "mini" },
4446
+ // Advanced / reasoning models
4447
+ { prefix: "o1", tier: "advanced" },
4448
+ { prefix: "o3", tier: "advanced" },
4449
+ { prefix: "o4", tier: "advanced" },
4450
+ { prefix: "gpt-4.1", tier: "advanced" },
4451
+ { prefix: "gpt-5.4-codex", tier: "advanced" },
4452
+ { prefix: "gpt-5.3-codex", tier: "advanced" },
4453
+ { prefix: "gpt-5.2-codex", tier: "advanced" },
4454
+ { prefix: "gpt-5.1-codex", tier: "advanced" },
4455
+ { prefix: "gpt-5.4", tier: "advanced" },
4456
+ { prefix: "gpt-5.3", tier: "advanced" },
4457
+ { prefix: "gpt-5.2", tier: "advanced" },
4458
+ { prefix: "gpt-5.1", tier: "advanced" },
4459
+ // GPT-5 catch-all (non-mini/codex) — advanced
4460
+ { prefix: "gpt-5", tier: "advanced" },
4461
+ // GPT-4o — standard
4462
+ { prefix: "gpt-4o", tier: "standard" },
4463
+ // GPT-4 — standard
4464
+ { prefix: "gpt-4", tier: "standard" }
4465
+ ];
4466
+ GEMINI_TIERS = [
4467
+ // Flash — mini tier
4468
+ { prefix: "gemini-3-flash", tier: "mini" },
4469
+ { prefix: "gemini-2.5-flash", tier: "mini" },
4470
+ { prefix: "gemini-2.0-flash", tier: "mini" },
4471
+ { prefix: "gemini-1.5-flash", tier: "mini" },
4472
+ // Pro — standard/advanced
4473
+ { prefix: "gemini-3.1-pro", tier: "advanced" },
4474
+ { prefix: "gemini-3-pro", tier: "advanced" },
4475
+ { prefix: "gemini-2.5-pro", tier: "standard" },
4476
+ { prefix: "gemini-2.0-pro", tier: "standard" },
4477
+ { prefix: "gemini-1.5-pro", tier: "standard" }
4478
+ ];
4479
+ KIMI_TIERS = [
4480
+ { prefix: "kimi-for-coding", tier: "advanced" },
4481
+ { prefix: "kimi-k2", tier: "advanced" },
4482
+ { prefix: "kimi-latest", tier: "standard" },
4483
+ { prefix: "kimi", tier: "standard" }
4484
+ ];
4485
+ EVAL_TIERS = [
4486
+ { prefix: "grok-code", tier: "standard" },
4487
+ { prefix: "raptor", tier: "mini" },
4488
+ { prefix: "goldeneye", tier: "standard" }
4489
+ ];
4490
+ }
4491
+ });
4211
4492
  function needsResponsesApi(model) {
4212
4493
  return model.includes("codex") || model.startsWith("gpt-5") || model.startsWith("o4-") || model === "o3";
4213
4494
  }
@@ -4220,6 +4501,10 @@ function buildMaxTokensParam(model, maxTokens) {
4220
4501
  }
4221
4502
  return { max_tokens: maxTokens };
4222
4503
  }
4504
+ function truncateToolDescription(description) {
4505
+ if (description.length <= MAX_TOOL_DESCRIPTION_LENGTH) return description;
4506
+ return description.slice(0, MAX_TOOL_DESCRIPTION_LENGTH - 1) + "\u2026";
4507
+ }
4223
4508
  function createOpenAIProvider(config) {
4224
4509
  const provider = new OpenAIProvider();
4225
4510
  if (config) {
@@ -4242,13 +4527,14 @@ function createKimiProvider(config) {
4242
4527
  }
4243
4528
  return provider;
4244
4529
  }
4245
- var DEFAULT_MODEL2, CONTEXT_WINDOWS2, MODELS_WITHOUT_TEMPERATURE, LOCAL_MODEL_PATTERNS, MODELS_WITH_THINKING_MODE, OpenAIProvider;
4530
+ var DEFAULT_MODEL2, CONTEXT_WINDOWS2, MODELS_WITHOUT_TEMPERATURE, LOCAL_MODEL_PATTERNS, MODELS_WITH_THINKING_MODE, OpenAIProvider, MAX_TOOL_DESCRIPTION_LENGTH;
4246
4531
  var init_openai = __esm({
4247
4532
  "src/providers/openai.ts"() {
4248
4533
  init_errors();
4249
4534
  init_retry();
4250
4535
  init_tool_call_normalizer();
4251
4536
  init_thinking();
4537
+ init_model_tier();
4252
4538
  DEFAULT_MODEL2 = "gpt-5.3-codex";
4253
4539
  CONTEXT_WINDOWS2 = {
4254
4540
  // OpenAI models
@@ -4400,6 +4686,14 @@ var init_openai = __esm({
4400
4686
  supportsTemperature(model) {
4401
4687
  return !MODELS_WITHOUT_TEMPERATURE.some((m) => model.toLowerCase().includes(m.toLowerCase()));
4402
4688
  }
4689
+ /**
4690
+ * Whether this provider instance supports the Responses API for the given model.
4691
+ * Subclasses (e.g. CopilotProvider) can override to force Chat Completions
4692
+ * when their endpoint does not expose /v1/responses.
4693
+ */
4694
+ modelNeedsResponsesApi(model) {
4695
+ return needsResponsesApi(model);
4696
+ }
4403
4697
  /**
4404
4698
  * Get extra body parameters for API calls.
4405
4699
  * Honors the user's ThinkingMode for Kimi models; defaults to disabled
@@ -4419,7 +4713,7 @@ var init_openai = __esm({
4419
4713
  async chat(messages, options) {
4420
4714
  this.ensureInitialized();
4421
4715
  const model = options?.model ?? this.config.model ?? DEFAULT_MODEL2;
4422
- if (needsResponsesApi(model)) {
4716
+ if (this.modelNeedsResponsesApi(model)) {
4423
4717
  return this.chatViaResponses(messages, options);
4424
4718
  }
4425
4719
  return withRetry(async () => {
@@ -4459,21 +4753,24 @@ var init_openai = __esm({
4459
4753
  async chatWithTools(messages, options) {
4460
4754
  this.ensureInitialized();
4461
4755
  const model = options?.model ?? this.config.model ?? DEFAULT_MODEL2;
4462
- if (needsResponsesApi(model)) {
4756
+ if (this.modelNeedsResponsesApi(model)) {
4463
4757
  return this.chatWithToolsViaResponses(messages, options);
4464
4758
  }
4759
+ const tierCfg = getTierConfig(this.id, model);
4465
4760
  return withRetry(async () => {
4466
4761
  try {
4467
4762
  const supportsTemp = this.supportsTemperature(model);
4468
4763
  const extraBody = this.getExtraBody(model, options?.thinking);
4469
4764
  const reasoningEffort = mapToOpenAIEffort(options?.thinking, model);
4470
4765
  const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
4766
+ const tools = this.limitTools(options.tools, tierCfg.maxTools);
4471
4767
  const requestParams = {
4472
4768
  model,
4473
4769
  ...buildMaxTokensParam(model, maxTokens),
4474
4770
  messages: this.convertMessages(messages, options?.system),
4475
- tools: this.convertTools(options.tools),
4476
- tool_choice: this.convertToolChoice(options.toolChoice)
4771
+ tools: this.convertTools(tools),
4772
+ tool_choice: this.convertToolChoice(options.toolChoice),
4773
+ parallel_tool_calls: tierCfg.parallelToolCalls
4477
4774
  };
4478
4775
  if (supportsTemp) {
4479
4776
  requestParams.temperature = options?.temperature ?? this.config.temperature ?? 0;
@@ -4511,7 +4808,7 @@ var init_openai = __esm({
4511
4808
  async *stream(messages, options) {
4512
4809
  this.ensureInitialized();
4513
4810
  const model = options?.model ?? this.config.model ?? DEFAULT_MODEL2;
4514
- if (needsResponsesApi(model)) {
4811
+ if (this.modelNeedsResponsesApi(model)) {
4515
4812
  yield* this.streamViaResponses(messages, options);
4516
4813
  return;
4517
4814
  }
@@ -4549,22 +4846,25 @@ var init_openai = __esm({
4549
4846
  async *streamWithTools(messages, options) {
4550
4847
  this.ensureInitialized();
4551
4848
  const model = options?.model ?? this.config.model ?? DEFAULT_MODEL2;
4552
- if (needsResponsesApi(model)) {
4849
+ if (this.modelNeedsResponsesApi(model)) {
4553
4850
  yield* this.streamWithToolsViaResponses(messages, options);
4554
4851
  return;
4555
4852
  }
4853
+ const tierCfg = getTierConfig(this.id, model);
4556
4854
  let timeoutTriggered = false;
4557
4855
  try {
4558
4856
  const supportsTemp = this.supportsTemperature(model);
4559
4857
  const extraBody = this.getExtraBody(model, options?.thinking);
4560
4858
  const reasoningEffort = mapToOpenAIEffort(options?.thinking, model);
4561
4859
  const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
4860
+ const tools = this.limitTools(options.tools, tierCfg.maxTools);
4562
4861
  const requestParams = {
4563
4862
  model,
4564
4863
  ...buildMaxTokensParam(model, maxTokens),
4565
4864
  messages: this.convertMessages(messages, options?.system),
4566
- tools: this.convertTools(options.tools),
4865
+ tools: this.convertTools(tools),
4567
4866
  tool_choice: this.convertToolChoice(options.toolChoice),
4867
+ parallel_tool_calls: tierCfg.parallelToolCalls,
4568
4868
  stream: true
4569
4869
  };
4570
4870
  if (supportsTemp) {
@@ -4801,7 +5101,7 @@ var init_openai = __esm({
4801
5101
  } catch {
4802
5102
  try {
4803
5103
  const model = this.config.model || DEFAULT_MODEL2;
4804
- if (needsResponsesApi(model)) {
5104
+ if (this.modelNeedsResponsesApi(model)) {
4805
5105
  await this.client.responses.create({
4806
5106
  model,
4807
5107
  input: [{ role: "user", content: [{ type: "input_text", text: "Hi" }] }],
@@ -4934,6 +5234,17 @@ var init_openai = __esm({
4934
5234
  }
4935
5235
  return content.filter((block) => block.type === "text").map((block) => block.text).join("");
4936
5236
  }
5237
+ /**
5238
+ * Limit the tool list to at most `max` entries.
5239
+ * Built-in tools (those without an `mcp_server` tag) are prioritised over
5240
+ * MCP tools so core capabilities are never dropped.
5241
+ */
5242
+ limitTools(tools, max) {
5243
+ if (tools.length <= max) return tools;
5244
+ const builtin = tools.filter((t) => !("serverName" in t && t.serverName));
5245
+ const mcp = tools.filter((t) => "serverName" in t && t.serverName);
5246
+ return [...builtin, ...mcp].slice(0, max);
5247
+ }
4937
5248
  /**
4938
5249
  * Convert tools to OpenAI format
4939
5250
  */
@@ -4942,8 +5253,9 @@ var init_openai = __esm({
4942
5253
  type: "function",
4943
5254
  function: {
4944
5255
  name: tool.name,
4945
- description: tool.description,
4946
- parameters: tool.input_schema
5256
+ description: truncateToolDescription(tool.description),
5257
+ parameters: tool.input_schema,
5258
+ strict: true
4947
5259
  }
4948
5260
  }));
4949
5261
  }
@@ -5076,8 +5388,11 @@ var init_openai = __esm({
5076
5388
  return withRetry(async () => {
5077
5389
  try {
5078
5390
  const model = options?.model ?? this.config.model ?? DEFAULT_MODEL2;
5391
+ const tierCfg = getTierConfig(this.id, model);
5079
5392
  const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
5080
- const tools = this.convertToolsForResponses(options.tools);
5393
+ const tools = this.convertToolsForResponses(
5394
+ this.limitTools(options.tools, tierCfg.maxTools)
5395
+ );
5081
5396
  const supportsTemp = this.supportsTemperature(model);
5082
5397
  const reasoningEffort = mapToOpenAIEffort(options?.thinking, model);
5083
5398
  const response = await this.client.responses.create({
@@ -5197,8 +5512,10 @@ var init_openai = __esm({
5197
5512
  let timeoutTriggered = false;
5198
5513
  try {
5199
5514
  const model = options?.model ?? this.config.model ?? DEFAULT_MODEL2;
5515
+ const tierCfg = getTierConfig(this.id, model);
5200
5516
  const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
5201
- const tools = options.tools.length > 0 ? this.convertToolsForResponses(options.tools) : void 0;
5517
+ const limitedTools = this.limitTools(options.tools, tierCfg.maxTools);
5518
+ const tools = limitedTools.length > 0 ? this.convertToolsForResponses(limitedTools) : void 0;
5202
5519
  const supportsTemp = this.supportsTemperature(model);
5203
5520
  const reasoningEffort = mapToOpenAIEffort(options?.thinking, model);
5204
5521
  const requestParams = {
@@ -5428,12 +5745,13 @@ var init_openai = __esm({
5428
5745
  return tools.map((tool) => ({
5429
5746
  type: "function",
5430
5747
  name: tool.name,
5431
- description: tool.description ?? void 0,
5748
+ description: tool.description ? truncateToolDescription(tool.description) : void 0,
5432
5749
  parameters: tool.input_schema ?? null,
5433
- strict: false
5750
+ strict: true
5434
5751
  }));
5435
5752
  }
5436
5753
  };
5754
+ MAX_TOOL_DESCRIPTION_LENGTH = 1024;
5437
5755
  }
5438
5756
  });
5439
5757
 
@@ -6210,6 +6528,14 @@ var init_copilot2 = __esm({
6210
6528
  /**
6211
6529
  * Count tokens (approximate — Copilot models vary in tokenizer)
6212
6530
  */
6531
+ /**
6532
+ * The GitHub Copilot endpoint (api.githubcopilot.com) does not expose the
6533
+ * OpenAI Responses API (/v1/responses). Always use Chat Completions so that
6534
+ * gpt-5-mini and similar models work correctly with MCP tools.
6535
+ */
6536
+ modelNeedsResponsesApi(_model) {
6537
+ return false;
6538
+ }
6213
6539
  countTokens(text15) {
6214
6540
  if (!text15) return 0;
6215
6541
  return Math.ceil(text15.length / 3.5);
@@ -10001,7 +10327,8 @@ var init_compactor = __esm({
10001
10327
  conversationText,
10002
10328
  provider,
10003
10329
  signal,
10004
- options.focusTopic
10330
+ options.focusTopic,
10331
+ options.summaryModel
10005
10332
  );
10006
10333
  const systemMessages = messages.filter((m) => m.role === "system");
10007
10334
  const summaryMessage = {
@@ -10056,14 +10383,15 @@ ${summary}
10056
10383
  /**
10057
10384
  * Generate a summary of the conversation using the LLM
10058
10385
  */
10059
- async generateSummary(conversationText, provider, signal, focusTopic) {
10386
+ async generateSummary(conversationText, provider, signal, focusTopic, summaryModel) {
10060
10387
  if (signal?.aborted) return "[Compaction cancelled]";
10061
10388
  const prompt = buildCompactionPrompt(focusTopic) + conversationText;
10062
10389
  try {
10063
10390
  const chatPromise = provider.chat([{ role: "user", content: prompt }], {
10064
10391
  maxTokens: this.config.summaryMaxTokens,
10065
- temperature: 0.3
10392
+ temperature: 0.3,
10066
10393
  // Lower temperature for more consistent summaries
10394
+ ...summaryModel ? { model: summaryModel } : {}
10067
10395
  });
10068
10396
  if (signal) {
10069
10397
  const abortPromise = new Promise((_, reject) => {
@@ -10786,7 +11114,7 @@ async function createDefaultReplConfig() {
10786
11114
  const model = await getLastUsedModel(providerType) || getDefaultModel(providerType);
10787
11115
  const persistedThinking = await getLastUsedThinking(providerType);
10788
11116
  const thinking = persistedThinking ?? resolveDefaultThinking(providerType, model);
10789
- const thinkingToStore = thinking === "off" ? void 0 : thinking;
11117
+ const thinkingToStore = persistedThinking !== void 0 ? persistedThinking : thinking === "off" ? void 0 : thinking;
10790
11118
  return {
10791
11119
  provider: {
10792
11120
  type: providerType,
@@ -10959,6 +11287,14 @@ ${finalInstructions}`;
10959
11287
  systemPrompt = `${systemPrompt}
10960
11288
 
10961
11289
  ${PLAN_MODE_SYSTEM_PROMPT}`;
11290
+ }
11291
+ const providerType = session.config.provider.type;
11292
+ const currentModel = session.config.provider.model ?? "";
11293
+ const tierCfg = getTierConfig(providerType, currentModel);
11294
+ if (!tierCfg.supportsCoT) {
11295
+ systemPrompt = `${systemPrompt}
11296
+
11297
+ ${MINI_MODEL_ADDENDUM}`;
10962
11298
  }
10963
11299
  return [{ role: "system", content: systemPrompt }, ...session.messages];
10964
11300
  }
@@ -11143,8 +11479,11 @@ async function initializeSessionTrust(session) {
11143
11479
  }
11144
11480
  function initializeContextManager(session, provider) {
11145
11481
  const contextWindow = provider.getContextWindow();
11482
+ const providerType = session.config.provider.type;
11483
+ const model = session.config.provider.model ?? "";
11484
+ const tierCfg = getTierConfig(providerType, model);
11146
11485
  session.contextManager = createContextManager(contextWindow, {
11147
- compactionThreshold: 0.8,
11486
+ compactionThreshold: tierCfg.compactionThreshold,
11148
11487
  reservedTokens: 4096
11149
11488
  });
11150
11489
  }
@@ -11179,7 +11518,11 @@ async function checkAndCompactContext(session, provider, signal, toolRegistry) {
11179
11518
  summaryMaxTokens: 1e3
11180
11519
  });
11181
11520
  try {
11182
- const result = await compactor.compact(session.messages, provider, signal);
11521
+ const summaryModel = session.config.provider.weakModel ?? session.config.provider.editorModel ?? getWeakModel() ?? getEditorModel();
11522
+ const result = await compactor.compact(session.messages, provider, {
11523
+ signal,
11524
+ ...summaryModel ? { summaryModel } : {}
11525
+ });
11183
11526
  if (result.wasCompacted) {
11184
11527
  const compactedNonSystem = result.messages.filter((m) => m.role !== "system");
11185
11528
  session.messages = compactedNonSystem;
@@ -11223,11 +11566,12 @@ function getSessionMemory(session) {
11223
11566
  async function reloadSessionMemory(session) {
11224
11567
  await initializeSessionMemory(session);
11225
11568
  }
11226
- var MAX_SKILL_INSTRUCTIONS_CHARS, TRUST_SETTINGS_DIR, TRUST_SETTINGS_FILE, PROJECT_TRUST_FILE_RELATIVE_PATH, CATEGORY_LABELS, COCO_SYSTEM_PROMPT, SHELL_METACHARACTERS, SAFE_COMMAND_VALIDATORS;
11569
+ var MAX_SKILL_INSTRUCTIONS_CHARS, TRUST_SETTINGS_DIR, TRUST_SETTINGS_FILE, PROJECT_TRUST_FILE_RELATIVE_PATH, CATEGORY_LABELS, COCO_SYSTEM_PROMPT, MINI_MODEL_ADDENDUM, SHELL_METACHARACTERS, SAFE_COMMAND_VALIDATORS;
11227
11570
  var init_session = __esm({
11228
11571
  "src/cli/repl/session.ts"() {
11229
11572
  init_env();
11230
11573
  init_thinking();
11574
+ init_model_tier();
11231
11575
  init_manager();
11232
11576
  init_compactor();
11233
11577
  init_memory();
@@ -11425,6 +11769,17 @@ Responses are short and direct by default. Lead with the answer or action, not r
11425
11769
  **During tool-calling iterations, keep text minimal.** A single short orienting line before tool calls is acceptable. Do NOT explain every step, narrate what you are about to do, or produce paragraphs between tool calls. Reserve explanatory text for your final response after all tools have completed.
11426
11770
 
11427
11771
  **Code blocks in responses are expensive.** Only include a code block when the user explicitly asks to see code, or when the code IS the deliverable (e.g., a script to paste in a terminal). Never include a code block to "show your work" when you can write the file directly instead.`;
11772
+ MINI_MODEL_ADDENDUM = `
11773
+ ## Mini-Model Mode
11774
+
11775
+ You are running on a fast, compact model. Keep outputs minimal and actions direct.
11776
+
11777
+ - Call ONE tool at a time. Do NOT combine multiple tool calls per turn.
11778
+ - Skip multi-step planning \u2014 just do the next concrete action.
11779
+ - Debugging: read the error, fix it, verify. No analysis phases.
11780
+ - Verification: run the command, report the result. No elaborate protocols.
11781
+ - Skip "Parallel Execution" \u2014 serialize all tool calls for reliability.
11782
+ - NEVER narrate your plan. NEVER explain what you are about to do. Just do it.`;
11428
11783
  SHELL_METACHARACTERS = /[;|&`$(){}<>!\n\\'"]/;
11429
11784
  SAFE_COMMAND_VALIDATORS = {
11430
11785
  git: (args) => {
@@ -12568,13 +12923,21 @@ var init_registry4 = __esm({
12568
12923
  const field = issue.path.join(".") || "input";
12569
12924
  return `${field} (${issue.message.toLowerCase()})`;
12570
12925
  });
12571
- errorMessage = `Invalid tool input \u2014 ${fields.join(", ")}`;
12926
+ errorMessage = `Invalid tool input for '${name}' \u2014 ${fields.join(", ")}`;
12572
12927
  const allUndefined = error.issues.every(
12573
12928
  (i) => i.message.toLowerCase().includes("received undefined")
12574
12929
  );
12575
12930
  if (allUndefined && error.issues.length > 1) {
12576
12931
  errorMessage += ". All parameters are missing \u2014 this is likely a JSON serialization error on our side. Please retry with the same arguments.";
12577
12932
  }
12933
+ try {
12934
+ const schema = zodToJsonSchema(tool.parameters);
12935
+ errorMessage += `
12936
+
12937
+ Expected schema for '${name}':
12938
+ ${JSON.stringify(schema, null, 2)}`;
12939
+ } catch {
12940
+ }
12578
12941
  } else if (isCocoError(error)) {
12579
12942
  const causeMsg = error.cause instanceof Error ? error.cause.message : "";
12580
12943
  const isRawEnoent = causeMsg.startsWith("ENOENT:");
@@ -18680,7 +19043,7 @@ var init_git = __esm({
18680
19043
  init_errors();
18681
19044
  gitStatusTool = defineTool({
18682
19045
  name: "git_status",
18683
- description: `Get the current git repository status including branch, staged, modified, and untracked files.
19046
+ description: `Return the current git repository state: branch name, staged files, modified files, and untracked files. Use this before committing to see what has changed, or to check which branch is active. Do NOT use this to see the content of changes (use git_diff), or to view history (use git_log). Returns isClean: true when the working tree is clean with nothing to commit.
18684
19047
 
18685
19048
  Examples:
18686
19049
  - Current dir: {} \u2192 { "branch": "main", "isClean": false, "modified": ["src/app.ts"] }
@@ -18714,7 +19077,7 @@ Examples:
18714
19077
  });
18715
19078
  gitDiffTool = defineTool({
18716
19079
  name: "git_diff",
18717
- description: `Get git diff showing file changes.
19080
+ description: `Show the unified diff of changes in the working tree or staging area. Use this to see exactly what lines changed in files before committing, or to review changes the user just made. Use staged: true to see only what has been staged (git add), or omit it to see all unstaged changes. Do NOT use this to see commit history (use git_log) or file status summary (use git_status).
18718
19081
 
18719
19082
  Examples:
18720
19083
  - All changes: {} \u2192 { "diff": "...", "filesChanged": 3, "insertions": 42, "deletions": 10 }
@@ -18751,7 +19114,7 @@ Examples:
18751
19114
  });
18752
19115
  gitAddTool = defineTool({
18753
19116
  name: "git_add",
18754
- description: `Stage files for commit.
19117
+ description: `Stage one or more files (or patterns) so they are included in the next git commit. Use this after making file edits to mark them ready for commit. Pass ["."] to stage all changes, or list specific file paths to stage selectively. Do NOT use this to commit (use git_commit after staging) or to view what is staged (use git_status or git_diff with staged: true).
18755
19118
 
18756
19119
  Examples:
18757
19120
  - Stage all: { "files": ["."] }
@@ -18777,7 +19140,7 @@ Examples:
18777
19140
  });
18778
19141
  gitCommitTool = defineTool({
18779
19142
  name: "git_commit",
18780
- description: `Create a git commit with the staged changes.
19143
+ description: `Create a git commit from whatever is currently staged (git add must run first). Use conventional commit format (feat/fix/docs/chore). Do NOT use this when nothing is staged \u2014 check git_status first; do NOT use this to stage files (use git_add) or to see what will be committed (use git_diff with staged: true).
18781
19144
 
18782
19145
  Examples:
18783
19146
  - Simple commit: { "message": "fix: resolve auth bug" }
@@ -18810,7 +19173,7 @@ Examples:
18810
19173
  });
18811
19174
  gitLogTool = defineTool({
18812
19175
  name: "git_log",
18813
- description: `Get git commit history.
19176
+ description: `Show git commit history with hashes, messages, authors, and dates. Use this to understand what changed recently, find the commit that introduced a bug, or check whether a feature was already merged. Do NOT use this to see the content of changes \u2014 use git_diff; do NOT use this to check current file state \u2014 use git_status.
18814
19177
 
18815
19178
  Examples:
18816
19179
  - Last 10 commits: {} (default)
@@ -18850,7 +19213,7 @@ Examples:
18850
19213
  });
18851
19214
  gitBranchTool = defineTool({
18852
19215
  name: "git_branch",
18853
- description: `Manage git branches (list, create, delete).
19216
+ description: `List all local branches, create a new branch from the current HEAD, or delete a branch. Use this to see available branches before checking out, or to create a feature branch. Do NOT use this to switch the active branch \u2014 use git_checkout; do NOT delete branches that have unmerged work.
18854
19217
 
18855
19218
  Examples:
18856
19219
  - List branches: {} \u2192 { "branches": ["main", "feature/x"], "current": "main" }
@@ -18894,7 +19257,7 @@ Examples:
18894
19257
  });
18895
19258
  gitCheckoutTool = defineTool({
18896
19259
  name: "git_checkout",
18897
- description: `Switch branches or create and switch to a new branch.
19260
+ description: `Switch the working directory to an existing branch, or create a new branch and switch to it in one step. Use this to move between branches or start a new feature branch. Do NOT use this with unsaved file edits (stage or stash first); do NOT use this to just list branches \u2014 use git_branch.
18898
19261
 
18899
19262
  Examples:
18900
19263
  - Switch branch: { "branch": "main" }
@@ -18924,7 +19287,7 @@ Examples:
18924
19287
  });
18925
19288
  gitPushTool = defineTool({
18926
19289
  name: "git_push",
18927
- description: `Push commits to remote repository.
19290
+ description: `Upload local commits to a remote repository. Use setUpstream: true the first time you push a new branch to create the tracking relationship. Do NOT use this on main/master without explicit user confirmation \u2014 it modifies shared history; do NOT push without first checking git_status to confirm all commits are clean.
18928
19291
 
18929
19292
  Examples:
18930
19293
  - Push current: {} \u2192 pushes to origin
@@ -18962,7 +19325,7 @@ Examples:
18962
19325
  });
18963
19326
  gitPullTool = defineTool({
18964
19327
  name: "git_pull",
18965
- description: `Pull changes from remote repository.
19328
+ description: `Fetch and integrate remote commits into the current branch. Use rebase: true to keep a linear history (preferred for feature branches). Do NOT use this when you have uncommitted local changes \u2014 stage or stash them first; if a merge conflict is reported, use read_file / edit_file to resolve then git_add and git_commit.
18966
19329
 
18967
19330
  Examples:
18968
19331
  - Pull current: {} \u2192 pulls from origin
@@ -19220,16 +19583,11 @@ var init_bash = __esm({
19220
19583
  ];
19221
19584
  bashExecTool = defineTool({
19222
19585
  name: "bash_exec",
19223
- description: `Execute a bash/shell command in the user's shell environment.
19586
+ description: `Execute a shell command and return its stdout, stderr, and exit code. Use this for running build scripts, test runners, linters, package managers, CLI tools, git commands, or any other shell operation. Runs in the user's shell environment with their full PATH and locally-configured credentials (kubeconfig, gcloud auth, AWS profiles, SSH keys) so never claim you cannot run a command due to missing credentials \u2014 always attempt and report the actual exit code. Do NOT use this to read or write files (use read_file / write_file / edit_file which are safer); prefer specific file tools for file operations.
19224
19587
 
19225
19588
  Runs with the user's full PATH and inherited environment \u2014 any tool installed
19226
19589
  on the user's machine is available: kubectl, gcloud, aws, docker, git, node,
19227
- pnpm, and others. Credentials configured locally are inherited automatically:
19228
- kubeconfig contexts, gcloud auth, AWS profiles, SSH keys, etc.
19229
-
19230
- IMPORTANT: never claim you cannot run a command because you lack credentials
19231
- or access \u2014 the environment is the user's own shell. Always attempt; report
19232
- failure only if the command actually returns a non-zero exit code.
19590
+ pnpm, and others.
19233
19591
 
19234
19592
  Examples:
19235
19593
  - List files: { "command": "ls -la" }
@@ -19474,7 +19832,7 @@ var init_github = __esm({
19474
19832
  init_errors();
19475
19833
  ghCheckAuthTool = defineTool({
19476
19834
  name: "gh_check_auth",
19477
- description: "Check if the GitHub CLI is installed and authenticated.",
19835
+ description: "Verify the gh CLI is installed and the user is logged into GitHub. Call this before any other gh_ tool to confirm authentication is working. Returns the logged-in username if authenticated.",
19478
19836
  category: "git",
19479
19837
  parameters: z.object({
19480
19838
  cwd: z.string().optional()
@@ -19494,7 +19852,7 @@ var init_github = __esm({
19494
19852
  });
19495
19853
  ghRepoInfoTool = defineTool({
19496
19854
  name: "gh_repo_info",
19497
- description: "Get GitHub repository information (name, default branch, URL).",
19855
+ description: "Get the remote GitHub repository's name, owner, default branch, and URL from within the current git working directory. Use before creating PRs to confirm the target repo. Requires gh_check_auth to pass first.",
19498
19856
  category: "git",
19499
19857
  parameters: z.object({
19500
19858
  cwd: z.string().optional()
@@ -19516,7 +19874,7 @@ var init_github = __esm({
19516
19874
  });
19517
19875
  ghPrCreateTool = defineTool({
19518
19876
  name: "gh_pr_create",
19519
- description: "Create a GitHub pull request.",
19877
+ description: "Open a pull request on GitHub from the current branch. Requires commits to be pushed first (use git_push with setUpstream: true). Do NOT use this if a PR for this branch already exists \u2014 use gh_pr_list to check first. Use draft: true for work in progress.",
19520
19878
  category: "git",
19521
19879
  parameters: z.object({
19522
19880
  title: z.string().describe("PR title"),
@@ -19540,7 +19898,7 @@ var init_github = __esm({
19540
19898
  });
19541
19899
  ghPrMergeTool = defineTool({
19542
19900
  name: "gh_pr_merge",
19543
- description: "Merge a GitHub pull request.",
19901
+ description: "Merge a pull request into its base branch. Use squash (default) for feature branches to keep history clean. Always confirm with gh_pr_checks first to ensure CI passed. Do NOT merge if anyFailed is true.",
19544
19902
  category: "git",
19545
19903
  parameters: z.object({
19546
19904
  number: z.number().describe("PR number"),
@@ -19561,7 +19919,7 @@ var init_github = __esm({
19561
19919
  });
19562
19920
  ghPrChecksTool = defineTool({
19563
19921
  name: "gh_pr_checks",
19564
- description: "Get CI check statuses for a pull request.",
19922
+ description: "Poll the CI check results for a pull request \u2014 returns pass/fail/pending per check and convenience flags (allPassed, anyFailed). Call this after pushing to confirm CI is green before merging. Check anyPending to know if results are still arriving.",
19565
19923
  category: "git",
19566
19924
  parameters: z.object({
19567
19925
  number: z.number().describe("PR number"),
@@ -19595,7 +19953,7 @@ var init_github = __esm({
19595
19953
  });
19596
19954
  ghPrListTool = defineTool({
19597
19955
  name: "gh_pr_list",
19598
- description: "List pull requests, optionally filtered by head branch.",
19956
+ description: "List open (or all) pull requests for this repository, optionally filtered to a specific branch. Use before gh_pr_create to ensure a PR for the current branch doesn't already exist. Returns PR number, title, URL, and state.",
19599
19957
  category: "git",
19600
19958
  parameters: z.object({
19601
19959
  head: z.string().optional().describe("Filter by head branch name"),
@@ -19612,7 +19970,7 @@ var init_github = __esm({
19612
19970
  });
19613
19971
  ghReleaseCreateTool = defineTool({
19614
19972
  name: "gh_release_create",
19615
- description: "Create a GitHub release with notes.",
19973
+ description: "Publish a versioned GitHub release attached to a tag. The tag must already exist in the repo (push it with git_push first). Provide markdown release notes. Use prerelease: true for release candidates or beta builds.",
19616
19974
  category: "git",
19617
19975
  parameters: z.object({
19618
19976
  tag: z.string().describe("Tag name (e.g., v1.2.3)"),
@@ -34319,6 +34677,12 @@ async function renderStartupPanel(session, gitCtx, mcpServers = []) {
34319
34677
  }
34320
34678
  const cocoStatus = isQualityLoop() ? chalk.magenta(" \u{1F504} quality mode: ") + chalk.green.bold("on") + chalk.dim(" \u2014 iterates until quality \u2265 85. /quality to disable") : chalk.dim(" \u{1F4A1} quality mode is Coco's edge for robust code. Enable with /quality on");
34321
34679
  console.log(cocoStatus);
34680
+ if (thinkingCapability.supported) {
34681
+ const modeLabel = formatThinkingMode(session.config.provider.thinking ?? "off");
34682
+ console.log(
34683
+ chalk.dim(" \u{1F9E0} reasoning: ") + chalk.magenta(modeLabel) + chalk.dim(" \xB7 /thinking to change")
34684
+ );
34685
+ }
34322
34686
  const skillTotal = session.skillRegistry?.size ?? 0;
34323
34687
  const hasSomething = skillTotal > 0 || mcpServers.length > 0;
34324
34688
  if (hasSomething) {
@@ -35928,6 +36292,7 @@ async function ensureConfiguredV2(config) {
35928
36292
  const preferredHasOpenAIOAuth = preferredProviderDef?.id === "openai" && hasOpenAIOAuthTokens;
35929
36293
  const preferredHasCopilotCreds = preferredProviderDef?.id === "copilot" && isProviderConfigured();
35930
36294
  const preferredIsConfigured = preferredIsLocal || preferredHasApiKey || preferredHasOpenAIOAuth || preferredHasCopilotCreds;
36295
+ const preferredWasConfigured = Boolean(preferredProviderDef && preferredIsConfigured);
35931
36296
  let preferredWasConfiguredButUnavailable = false;
35932
36297
  let preferredUnavailableWasLocal = false;
35933
36298
  if (preferredProviderDef && preferredIsConfigured) {
@@ -35973,7 +36338,9 @@ async function ensureConfiguredV2(config) {
35973
36338
  }
35974
36339
  const provider = await createProvider(providerId, { model });
35975
36340
  if (await provider.isAvailable()) {
35976
- await saveProviderPreference(prov.id, model);
36341
+ if (!preferredWasConfigured) {
36342
+ await saveProviderPreference(prov.id, model);
36343
+ }
35977
36344
  return {
35978
36345
  ...config,
35979
36346
  provider: {
@@ -35998,7 +36365,9 @@ async function ensureConfiguredV2(config) {
35998
36365
  const model = recommended?.id || openaiDef.models[0]?.id || "";
35999
36366
  const provider = await createProvider("codex", { model });
36000
36367
  if (await provider.isAvailable()) {
36001
- await saveProviderPreference("openai", model);
36368
+ if (!preferredWasConfigured) {
36369
+ await saveProviderPreference("openai", model);
36370
+ }
36002
36371
  return {
36003
36372
  ...config,
36004
36373
  provider: {
@@ -42581,7 +42950,7 @@ Use list_dir or glob to find the correct path.`;
42581
42950
  }
42582
42951
  var readFileTool = defineTool({
42583
42952
  name: "read_file",
42584
- description: `Read the contents of a file.
42953
+ description: `Read the full text content of a file at the given path and return it as a string. Use this when you need the actual source code, configuration values, or text content of a specific file you already know the path to. Do NOT use this to list files in a directory (use list_directory), to check if a file exists (use file_exists), or to search for files by name pattern (use find_files). Returns an error if the path does not exist or is not a readable text file.
42585
42954
 
42586
42955
  Examples:
42587
42956
  - Read config: { "path": "package.json" }
@@ -42639,7 +43008,7 @@ Examples:
42639
43008
  });
42640
43009
  var writeFileTool = defineTool({
42641
43010
  name: "write_file",
42642
- description: `Write content to a file, creating it if it doesn't exist.
43011
+ description: `Write text content to a file, replacing it entirely if it already exists or creating it if it does not. Use this when you want to create a new file or fully replace an existing file's content. Do NOT use this to make a small change to an existing file (use edit_file instead, which performs a targeted find-and-replace without rewriting the whole file). Set createDirs: true to automatically create missing parent directories; otherwise the parent directory must already exist.
42643
43012
 
42644
43013
  Examples:
42645
43014
  - Create file: { "path": "src/utils.ts", "content": "export const foo = 1;" }
@@ -42700,7 +43069,7 @@ Examples:
42700
43069
  });
42701
43070
  var editFileTool = defineTool({
42702
43071
  name: "edit_file",
42703
- description: `Edit a file by replacing text (find and replace).
43072
+ description: `Make a targeted text replacement inside an existing file by finding oldText and replacing it with newText. Use this for surgical edits to source code, configuration files, or documentation \u2014 it is much safer than rewriting the whole file with write_file because it only touches the exact bytes you specify. The oldText must match exactly (including whitespace and indentation); if it appears more than once in the file, use all: true to replace every occurrence or make oldText longer to be unique. Do NOT use this to create new files (use write_file) or to rename/move files (use move_path).
42704
43073
 
42705
43074
  Examples:
42706
43075
  - Single replace: { "path": "src/app.ts", "oldText": "TODO:", "newText": "DONE:" }
@@ -42784,7 +43153,7 @@ Hint: Use read_file first to verify the exact content.`
42784
43153
  });
42785
43154
  var globTool = defineTool({
42786
43155
  name: "glob",
42787
- description: `Find files matching a glob pattern.
43156
+ description: `Find files whose paths match a glob pattern and return their relative paths as a list. Use this when you know the file extension or naming convention but not the exact path (e.g. find all TypeScript test files, all JSON configs). Do NOT use this to search inside file contents \u2014 use grep or search for that. Returns an empty list when nothing matches; does not throw an error for zero results. node_modules, .git, and dist directories are excluded by default.
42788
43157
 
42789
43158
  Examples:
42790
43159
  - All TypeScript: { "pattern": "**/*.ts" }
@@ -42826,7 +43195,7 @@ Examples:
42826
43195
  });
42827
43196
  var fileExistsTool = defineTool({
42828
43197
  name: "file_exists",
42829
- description: `Check if a file or directory exists.
43198
+ description: `Check whether a path exists on disk and whether it is a file or directory. Use this before attempting to read or write a path when you are unsure it exists \u2014 it never throws, always returning { exists: false } for missing paths. Do NOT use this to read file contents (use read_file) or to list directory contents (use list_directory). Returns isFile and isDirectory flags so you can distinguish files from directories in a single call.
42830
43199
 
42831
43200
  Examples:
42832
43201
  - Check file: { "path": "package.json" } \u2192 { "exists": true, "isFile": true, "isDirectory": false }
@@ -42856,7 +43225,7 @@ Examples:
42856
43225
  });
42857
43226
  var listDirTool = defineTool({
42858
43227
  name: "list_dir",
42859
- description: `List contents of a directory.
43228
+ description: `List the immediate entries (files and subdirectories) inside a directory and return their names, types, and sizes. Use this to understand what's in a folder before deciding which files to read. Do NOT use this to find files matching a pattern across the whole project (use glob) or to read file contents (use read_file). Returns an error if the path does not exist or is not a directory.
42860
43229
 
42861
43230
  Examples:
42862
43231
  - List src: { "path": "src" }
@@ -44394,7 +44763,7 @@ init_registry4();
44394
44763
  init_errors();
44395
44764
  var grepTool = defineTool({
44396
44765
  name: "grep",
44397
- description: `Search for text patterns in files using regex.
44766
+ description: `Search for a regex pattern across all files in a directory and return matching lines with file paths and line numbers. Use this when you know a symbol name, string literal, or pattern and want to find where it appears in the codebase (function definitions, imports, error messages, config keys). Do NOT use this to find files by name \u2014 use glob for that. Do NOT use this when you already know the file path \u2014 use read_file directly. Searches are recursive by default; narrow with the include glob to restrict to specific file types.
44398
44767
 
44399
44768
  Examples:
44400
44769
  - Simple search: { "pattern": "TODO" }
@@ -44527,7 +44896,7 @@ Examples:
44527
44896
  });
44528
44897
  var findInFileTool = defineTool({
44529
44898
  name: "find_in_file",
44530
- description: `Search for a pattern in a single file.
44899
+ description: `Search for a text or regex pattern inside a single known file and return matching line numbers and content. Use this when you already have the file path and want to quickly find which lines match (e.g. locate a function signature, find an import, or confirm a value exists). Do NOT use this to search across the whole project \u2014 use grep for that. Returns line numbers so you can navigate directly to the match.
44531
44900
 
44532
44901
  Examples:
44533
44902
  - Find text: { "file": "src/app.ts", "pattern": "export" }
@@ -45668,7 +46037,7 @@ async function searchSerpApi(query, maxResults, timeout) {
45668
46037
  }
45669
46038
  var webSearchTool = defineTool({
45670
46039
  name: "web_search",
45671
- description: `Search the web for information, documentation, error solutions, and API references.
46040
+ description: `Search the web using a natural-language query and return a list of result titles, URLs, and short excerpts. Use this when you need to find documentation, research an error message, discover API references, or learn about a library before you have a specific URL. Do NOT use this when you already have the URL \u2014 use web_fetch to read a known page directly. Returns result titles and URLs you can then fetch with web_fetch for full content.
45672
46041
 
45673
46042
  Examples:
45674
46043
  - Basic search: { "query": "typescript zod validation examples" }
@@ -45953,7 +46322,7 @@ ${rows.join("\n")}
45953
46322
  }
45954
46323
  var webFetchTool = defineTool({
45955
46324
  name: "web_fetch",
45956
- description: `Fetch a URL and convert its content to clean markdown. Extracts main content, strips navigation/ads, and returns readable text.
46325
+ description: `Fetch the content of a specific URL and return it as clean, readable markdown text. Use this when you already have the exact URL \u2014 for example a documentation page, a GitHub file, or an API response \u2014 and want to read its content. Do NOT use this to find information without a URL; use web_search instead to discover relevant URLs first. By default strips navigation menus, headers, and ads to return only the main content; set extractContent: false to get the raw HTML/text.
45957
46326
 
45958
46327
  Examples:
45959
46328
  - Fetch documentation: { "url": "https://docs.example.com/api" }
@@ -46641,8 +47010,7 @@ async function saveMemory(scope, memory) {
46641
47010
  }
46642
47011
  var createMemoryTool = defineTool({
46643
47012
  name: "create_memory",
46644
- description: `Save a memory (key-value pair) that persists between sessions.
46645
- Use for storing project conventions, patterns, preferences, and learnings.
47013
+ description: `Persist a named key-value fact that survives across sessions \u2014 use this for project conventions, patterns, user preferences, or discovered constraints you'll need later. If the key already exists the value is overwritten. Do NOT use this for temporary scratch data within a single session; do NOT use this to store file contents \u2014 use write_file for that.
46646
47014
 
46647
47015
  Examples:
46648
47016
  - Save convention: { "key": "naming-convention", "value": "Use camelCase for variables", "tags": ["style"] }
@@ -46699,7 +47067,7 @@ Examples:
46699
47067
  });
46700
47068
  var recallMemoryTool = defineTool({
46701
47069
  name: "recall_memory",
46702
- description: `Search and recall stored memories by key, tags, or free text query.
47070
+ description: `Search previously saved memories by key substring, tags, or free-text value match. Use this at the start of a task to check if relevant conventions or patterns were already learned. Returns full memory content. Do NOT use this to list all memories (use list_memories) or to search file content (use grep).
46703
47071
 
46704
47072
  Examples:
46705
47073
  - By key substring: { "query": "naming" }
@@ -46754,7 +47122,7 @@ Examples:
46754
47122
  });
46755
47123
  var listMemoriesTool = defineTool({
46756
47124
  name: "list_memories",
46757
- description: `List all stored memories with optional filtering. Returns lightweight index entries.
47125
+ description: `List stored memory keys and tags without loading full values \u2014 useful for browsing what has been saved before recalling specific entries. Do NOT use this to get memory content (use recall_memory for that).
46758
47126
 
46759
47127
  Examples:
46760
47128
  - List all: { "scope": "all" }
@@ -56973,7 +57341,7 @@ program.command("setup").description("Configure AI provider and API key").action
56973
57341
  console.log("\n\u274C Setup cancelled.");
56974
57342
  }
56975
57343
  });
56976
- program.command("chat", { isDefault: true }).description("Start interactive chat session with the agent").option("-m, --model <model>", "LLM model to use").option("--provider <provider>", "LLM provider (anthropic, openai, codex, gemini, kimi)").option("-p, --path <path>", "Project path", process.cwd()).option("-P, --print [task]", "Headless mode: run task and print output (no interactive UI)").option("--output <format>", "Output format for headless mode (text or json)", "text").option("--setup", "Run setup wizard before starting").action(
57344
+ program.command("chat", { isDefault: true }).description("Start interactive chat session with the agent").option("-m, --model <model>", "LLM model to use").option("--provider <provider>", "LLM provider (anthropic, openai, codex, gemini, kimi)").option("--editor-model <model>", "Cheap model for file edits (architect/editor split)").option("--weak-model <model>", "Cheap model for background tasks (compaction, summaries)").option("-p, --path <path>", "Project path", process.cwd()).option("-P, --print [task]", "Headless mode: run task and print output (no interactive UI)").option("--output <format>", "Output format for headless mode (text or json)", "text").option("--setup", "Run setup wizard before starting").action(
56977
57345
  async (options) => {
56978
57346
  if (options.setup) {
56979
57347
  const result = await runOnboardingV2();
@@ -57008,7 +57376,9 @@ program.command("chat", { isDefault: true }).description("Start interactive chat
57008
57376
  provider: {
57009
57377
  type: providerType,
57010
57378
  model: options.model ?? "",
57011
- maxTokens: 8192
57379
+ maxTokens: 8192,
57380
+ editorModel: options.editorModel,
57381
+ weakModel: options.weakModel
57012
57382
  }
57013
57383
  }
57014
57384
  });