@alanwchat/coder 0.1.106 → 0.1.108

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/index.cjs CHANGED
@@ -23,18 +23,12 @@ __export(config_exports, {
23
23
  loadConfig: () => loadConfig,
24
24
  resolveApiKey: () => resolveApiKey,
25
25
  resolveProviderConfig: () => resolveProviderConfig,
26
+ resolveTavilyApiKey: () => resolveTavilyApiKey,
26
27
  saveConfig: () => saveConfig
27
28
  });
28
29
  function getConfigDir() {
29
30
  const home = (0, import_node_os.homedir)();
30
- const p = (0, import_node_os.platform)();
31
- if (p === "win32") {
32
- return process.env.APPDATA ? (0, import_node_path10.join)(process.env.APPDATA, "Coder", "cli") : (0, import_node_path10.join)(home, ".config", "coder", "cli");
33
- }
34
- if (process.env.XDG_CONFIG_HOME) {
35
- return (0, import_node_path10.join)(process.env.XDG_CONFIG_HOME, "coder", "cli");
36
- }
37
- return (0, import_node_path10.join)(home, ".config", "coder", "cli");
31
+ return (0, import_node_path10.join)(home, ".coder", "cli");
38
32
  }
39
33
  function createDefaultProviderSettings(provider) {
40
34
  const isCustom = provider === "custom";
@@ -42,8 +36,7 @@ function createDefaultProviderSettings(provider) {
42
36
  apiKeySource: "env",
43
37
  apiKey: "",
44
38
  apiKeyEnvVar: isCustom ? "CUSTOM_API_KEY" : PRESET_PROVIDERS[provider]?.defaultApiKeyEnvVar ?? "API_KEY",
45
- customBaseUrl: isCustom ? "https://api.example.com/v1" : "",
46
- showUsage: false
39
+ customBaseUrl: isCustom ? "https://api.example.com/v1" : ""
47
40
  };
48
41
  }
49
42
  function createDefaultConfig() {
@@ -55,7 +48,7 @@ function createDefaultConfig() {
55
48
  activeProvider: "deepseek",
56
49
  providers,
57
50
  lastModel: "deepseek-v4-flash",
58
- showUsage: false
51
+ tavilyApiKey: ""
59
52
  };
60
53
  }
61
54
  function getConfigFilePath() {
@@ -90,9 +83,12 @@ function mergeWithDefaults(raw) {
90
83
  activeProvider: PROVIDER_IDS.includes(String(raw.activeProvider ?? "")) ? raw.activeProvider : defaults.activeProvider,
91
84
  providers: mergeProviders(raw.providers, defaults.providers),
92
85
  lastModel: typeof raw.lastModel === "string" ? raw.lastModel : defaults.lastModel,
93
- showUsage: typeof raw.showUsage === "boolean" ? raw.showUsage : defaults.showUsage
86
+ tavilyApiKey: typeof raw.tavilyApiKey === "string" ? raw.tavilyApiKey : defaults.tavilyApiKey
94
87
  };
95
88
  }
89
+ function isPlainObject(value) {
90
+ return value !== null && typeof value === "object" && !Array.isArray(value);
91
+ }
96
92
  function mergeProviders(raw, defaults) {
97
93
  const result = { ...defaults };
98
94
  if (!raw || typeof raw !== "object") {
@@ -106,7 +102,9 @@ function mergeProviders(raw, defaults) {
106
102
  apiKey: typeof p.apiKey === "string" ? p.apiKey : result[id].apiKey,
107
103
  apiKeyEnvVar: typeof p.apiKeyEnvVar === "string" ? p.apiKeyEnvVar : result[id].apiKeyEnvVar,
108
104
  customBaseUrl: typeof p.customBaseUrl === "string" ? p.customBaseUrl : result[id].customBaseUrl,
109
- showUsage: typeof p.showUsage === "boolean" ? p.showUsage : result[id].showUsage
105
+ ...typeof p.supportsThinking === "boolean" ? { supportsThinking: p.supportsThinking } : {},
106
+ ...isPlainObject(p.thinkingEnabledParams) ? { thinkingEnabledParams: p.thinkingEnabledParams } : {},
107
+ ...isPlainObject(p.thinkingDisabledParams) ? { thinkingDisabledParams: p.thinkingDisabledParams } : {}
110
108
  };
111
109
  }
112
110
  }
@@ -166,6 +164,12 @@ function resolveApiKey(resolved) {
166
164
  }
167
165
  return envValue;
168
166
  }
167
+ function resolveTavilyApiKey(config) {
168
+ if (config.tavilyApiKey.trim()) {
169
+ return config.tavilyApiKey.trim();
170
+ }
171
+ return process.env.TAVILY_API_KEY?.trim() ?? "";
172
+ }
169
173
  function getConfigDirPath() {
170
174
  return getConfigDir();
171
175
  }
@@ -204,7 +208,7 @@ var init_config = __esm({
204
208
  ]
205
209
  };
206
210
  PROVIDER_IDS = ["deepseek", "glm", "agnes", "nvidia", "minimax", "custom"];
207
- CONFIG_FILE = "config.json";
211
+ CONFIG_FILE = "settings.json";
208
212
  }
209
213
  });
210
214
 
@@ -491,7 +495,8 @@ var replaceLinesHandler = async (rawArgs, context) => {
491
495
  action: "modified",
492
496
  bytesWritten: Buffer.byteLength(newContent, "utf-8"),
493
497
  linesAdded: replacementLines.length,
494
- linesRemoved: endLine - args.start_line + 1
498
+ linesRemoved: endLine - args.start_line + 1,
499
+ warning: "Rows may have shifted \u2014 re-read the file with read_file before your next edit."
495
500
  });
496
501
  } catch (error2) {
497
502
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -990,6 +995,27 @@ function readShellLogs(shellId, stream, offset = 0, limit = 4096) {
990
995
  truncated: offset + limit < totalBytes
991
996
  };
992
997
  }
998
+ function killAllShells() {
999
+ for (const shell of shells.values()) {
1000
+ if (shell.status === "running") {
1001
+ try {
1002
+ shell.process.kill("SIGTERM");
1003
+ } catch {
1004
+ }
1005
+ }
1006
+ }
1007
+ shells.clear();
1008
+ }
1009
+ function killShellsByTask(taskId) {
1010
+ for (const [id, shell] of shells) {
1011
+ if (shell.taskId === taskId && shell.status === "running") {
1012
+ try {
1013
+ shell.process.kill("SIGTERM");
1014
+ } catch {
1015
+ }
1016
+ }
1017
+ }
1018
+ }
993
1019
 
994
1020
  // src/handlers/shell.ts
995
1021
  var shellHandler = async (rawArgs, context) => {
@@ -1073,13 +1099,16 @@ var readShellLogsHandler = async (rawArgs, _context) => {
1073
1099
  };
1074
1100
 
1075
1101
  // src/handlers/web-search.ts
1102
+ init_config();
1076
1103
  var webSearchHandler = async (rawArgs, _context) => {
1077
1104
  const args = rawArgs;
1078
1105
  if (!args.search_term?.trim()) {
1079
1106
  return toolFailure("web_search", "invalid_arguments", "search_term is required");
1080
1107
  }
1108
+ const config = loadConfig();
1109
+ const tavilyKey = resolveTavilyApiKey(config);
1081
1110
  try {
1082
- const results = await performWebSearch(args.search_term, args.max_results ?? 5);
1111
+ const results = await performWebSearch(args.search_term, args.max_results ?? 5, tavilyKey);
1083
1112
  return toolSuccess("web_search", {
1084
1113
  query: args.search_term,
1085
1114
  results
@@ -1089,7 +1118,33 @@ var webSearchHandler = async (rawArgs, _context) => {
1089
1118
  return toolFailure("web_search", "search_error", message);
1090
1119
  }
1091
1120
  };
1092
- async function performWebSearch(query, maxResults) {
1121
+ async function performWebSearch(query, maxResults, tavilyKey) {
1122
+ if (tavilyKey) {
1123
+ try {
1124
+ const response = await fetch("https://api.tavily.com/search", {
1125
+ method: "POST",
1126
+ headers: { "Content-Type": "application/json" },
1127
+ body: JSON.stringify({
1128
+ api_key: tavilyKey,
1129
+ query,
1130
+ max_results: maxResults,
1131
+ search_depth: "basic"
1132
+ }),
1133
+ signal: AbortSignal.timeout(15e3)
1134
+ });
1135
+ if (response.ok) {
1136
+ const data = await response.json();
1137
+ if (data.results && data.results.length > 0) {
1138
+ return data.results.map((r) => ({
1139
+ title: r.title,
1140
+ url: r.url,
1141
+ snippet: r.content
1142
+ }));
1143
+ }
1144
+ }
1145
+ } catch {
1146
+ }
1147
+ }
1093
1148
  try {
1094
1149
  const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`;
1095
1150
  const response = await fetch(url, {
@@ -1116,10 +1171,13 @@ async function performWebSearch(query, maxResults) {
1116
1171
  }
1117
1172
  }
1118
1173
  }
1174
+ if (results.length === 0) {
1175
+ throw new Error("No results");
1176
+ }
1119
1177
  return results.slice(0, maxResults);
1120
1178
  } catch {
1121
1179
  throw new Error(
1122
- "Web search is currently unavailable. Configure a Tavily API key for web search support, or set the TAVILY_API_KEY environment variable."
1180
+ "Web search is currently unavailable. Configure a Tavily API key via `coder config tavilyApiKey <key>` or set the TAVILY_API_KEY environment variable."
1123
1181
  );
1124
1182
  }
1125
1183
  }
@@ -1248,125 +1306,9 @@ var todoWriteHandler = async (rawArgs, context) => {
1248
1306
  });
1249
1307
  };
1250
1308
 
1251
- // src/handlers/plans.ts
1309
+ // src/handlers/workspace-tree.ts
1252
1310
  var import_node_fs11 = require("node:fs");
1253
1311
  var import_node_path12 = require("node:path");
1254
- init_config();
1255
- function getPlansDir() {
1256
- const dir = (0, import_node_path12.join)(getConfigDirPath(), "plans");
1257
- if (!(0, import_node_fs11.existsSync)(dir)) {
1258
- (0, import_node_fs11.mkdirSync)(dir, { recursive: true });
1259
- }
1260
- return dir;
1261
- }
1262
- function getPlanFilePath(name) {
1263
- const safeName = name.replace(/[^a-z0-9-_.]/gi, "").toLowerCase();
1264
- return (0, import_node_path12.join)(getPlansDir(), `${safeName}.json`);
1265
- }
1266
- function readPlanFile(name) {
1267
- const filePath = getPlanFilePath(name);
1268
- if (!(0, import_node_fs11.existsSync)(filePath)) return null;
1269
- try {
1270
- return JSON.parse((0, import_node_fs11.readFileSync)(filePath, "utf-8"));
1271
- } catch {
1272
- return null;
1273
- }
1274
- }
1275
- function writePlanFile(name, plan) {
1276
- (0, import_node_fs11.writeFileSync)(getPlanFilePath(name), JSON.stringify(plan, null, 2), "utf-8");
1277
- }
1278
- function deletePlanFile(name) {
1279
- const filePath = getPlanFilePath(name);
1280
- if (!(0, import_node_fs11.existsSync)(filePath)) return false;
1281
- try {
1282
- (0, import_node_fs11.writeFileSync)(filePath, JSON.stringify({ deleted: true }), "utf-8");
1283
- return true;
1284
- } catch {
1285
- return false;
1286
- }
1287
- }
1288
- function listPlanFiles() {
1289
- return (0, import_node_fs11.readdirSync)(getPlansDir()).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
1290
- }
1291
- var planCreateHandler = async (rawArgs, _context) => {
1292
- const args = rawArgs;
1293
- if (!args.name?.trim() || !args.content?.trim()) {
1294
- return toolFailure("plan_create", "invalid_arguments", "name and content are required");
1295
- }
1296
- const name = args.name.trim().toLowerCase().replace(/[^a-z0-9-_.]/gi, "");
1297
- if (readPlanFile(name)) {
1298
- return toolFailure("plan_create", "exists", `Plan already exists: ${name}`);
1299
- }
1300
- writePlanFile(name, {
1301
- name,
1302
- title: args.title?.trim() ?? name,
1303
- content: args.content.trim(),
1304
- createdAt: Date.now(),
1305
- updatedAt: Date.now()
1306
- });
1307
- return toolSuccess("plan_create", { name, title: args.title?.trim() ?? name });
1308
- };
1309
- var planReadHandler = async (rawArgs, _context) => {
1310
- const args = rawArgs;
1311
- if (!args.name?.trim()) {
1312
- return toolFailure("plan_read", "invalid_arguments", "name is required");
1313
- }
1314
- const plan = readPlanFile(args.name.trim());
1315
- if (!plan) {
1316
- return toolFailure("plan_read", "not_found", `Plan not found: ${args.name}`);
1317
- }
1318
- return toolSuccess("plan_read", plan);
1319
- };
1320
- var planUpdateHandler = async (rawArgs, _context) => {
1321
- const args = rawArgs;
1322
- if (!args.name?.trim()) {
1323
- return toolFailure("plan_update", "invalid_arguments", "name is required");
1324
- }
1325
- const existing = readPlanFile(args.name.trim());
1326
- if (!existing) {
1327
- return toolFailure("plan_update", "not_found", `Plan not found: ${args.name}`);
1328
- }
1329
- writePlanFile(args.name.trim(), {
1330
- ...existing,
1331
- title: args.title?.trim() ?? existing.title,
1332
- content: args.content?.trim() ?? existing.content,
1333
- updatedAt: Date.now()
1334
- });
1335
- return toolSuccess("plan_update", { name: args.name.trim() });
1336
- };
1337
- var planEditHandler = async (rawArgs, _context) => {
1338
- const args = rawArgs;
1339
- if (!args.name?.trim()) {
1340
- return toolFailure("plan_edit", "invalid_arguments", "name is required");
1341
- }
1342
- const existing = readPlanFile(args.name.trim());
1343
- if (!existing) {
1344
- return toolFailure("plan_edit", "not_found", `Plan not found: ${args.name}`);
1345
- }
1346
- writePlanFile(args.name.trim(), {
1347
- ...existing,
1348
- content: existing.content + "\n\n## Edit Instructions\n\n" + (args.instructions?.trim() ?? ""),
1349
- updatedAt: Date.now()
1350
- });
1351
- return toolSuccess("plan_edit", { name: args.name.trim() });
1352
- };
1353
- var planDeleteHandler = async (rawArgs, _context) => {
1354
- const args = rawArgs;
1355
- if (!args.name?.trim()) {
1356
- return toolFailure("plan_delete", "invalid_arguments", "name is required");
1357
- }
1358
- deletePlanFile(args.name.trim());
1359
- return toolSuccess("plan_delete", { name: args.name.trim(), deleted: true });
1360
- };
1361
- var planListHandler = async (_rawArgs, _context) => {
1362
- const names = listPlanFiles();
1363
- const plans = names.map((name) => readPlanFile(name)).filter((p) => p !== null).map((p) => ({ name: p.name, title: p.title }));
1364
- return toolSuccess("plan_list", { plans, total: plans.length });
1365
- };
1366
-
1367
- // src/handlers/workspace-tree.ts
1368
- var import_node_fs12 = require("node:fs");
1369
- var import_node_path13 = require("node:path");
1370
1312
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1371
1313
  "node_modules",
1372
1314
  ".git",
@@ -1412,13 +1354,13 @@ var getWorkspaceTreeHandler = async (rawArgs, context) => {
1412
1354
  };
1413
1355
  function buildTree(rootDir, currentDir, depth) {
1414
1356
  const lines = [];
1415
- const relPath = (0, import_node_path13.relative)(rootDir, currentDir);
1357
+ const relPath = (0, import_node_path12.relative)(rootDir, currentDir);
1416
1358
  if (depth === 0) {
1417
- lines.push((0, import_node_path13.relative)(rootDir, currentDir) || ".");
1359
+ lines.push((0, import_node_path12.relative)(rootDir, currentDir) || ".");
1418
1360
  }
1419
1361
  let entries;
1420
1362
  try {
1421
- entries = (0, import_node_fs12.readdirSync)(currentDir);
1363
+ entries = (0, import_node_fs11.readdirSync)(currentDir);
1422
1364
  } catch {
1423
1365
  return lines;
1424
1366
  }
@@ -1428,9 +1370,9 @@ function buildTree(rootDir, currentDir, depth) {
1428
1370
  if (name.startsWith(".") && depth === 0 && name === ".git") continue;
1429
1371
  if (EXCLUDED_DIRS.has(name)) continue;
1430
1372
  if (name.startsWith(".") && depth > 0) continue;
1431
- const fullPath = (0, import_node_path13.resolve)(currentDir, name);
1373
+ const fullPath = (0, import_node_path12.resolve)(currentDir, name);
1432
1374
  try {
1433
- const stats = (0, import_node_fs12.statSync)(fullPath);
1375
+ const stats = (0, import_node_fs11.statSync)(fullPath);
1434
1376
  if (stats.isDirectory()) {
1435
1377
  dirs.push(name);
1436
1378
  } else {
@@ -1447,10 +1389,10 @@ function buildTree(rootDir, currentDir, depth) {
1447
1389
  const isLast = i === allEntries.length - 1;
1448
1390
  const prefix = depth === 0 ? isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 " : "";
1449
1391
  const childPrefix = depth === 0 ? isLast ? " " : "\u2502 " : "";
1450
- const fullPath = (0, import_node_path13.resolve)(currentDir, name);
1392
+ const fullPath = (0, import_node_path12.resolve)(currentDir, name);
1451
1393
  lines.push(`${prefix}${name}`);
1452
1394
  try {
1453
- const stats = (0, import_node_fs12.statSync)(fullPath);
1395
+ const stats = (0, import_node_fs11.statSync)(fullPath);
1454
1396
  if (stats.isDirectory()) {
1455
1397
  const childLines = buildTreeFromDepth(rootDir, fullPath, depth + 1, prefix || "", childPrefix);
1456
1398
  lines.push(...childLines);
@@ -1464,7 +1406,7 @@ function buildTreeFromDepth(rootDir, currentDir, depth, parentPrefix, prefix) {
1464
1406
  const lines = [];
1465
1407
  let entries;
1466
1408
  try {
1467
- entries = (0, import_node_fs12.readdirSync)(currentDir);
1409
+ entries = (0, import_node_fs11.readdirSync)(currentDir);
1468
1410
  } catch {
1469
1411
  return lines;
1470
1412
  }
@@ -1473,9 +1415,9 @@ function buildTreeFromDepth(rootDir, currentDir, depth, parentPrefix, prefix) {
1473
1415
  for (const name of entries) {
1474
1416
  if (EXCLUDED_DIRS.has(name)) continue;
1475
1417
  if (name.startsWith(".")) continue;
1476
- const fullPath = (0, import_node_path13.resolve)(currentDir, name);
1418
+ const fullPath = (0, import_node_path12.resolve)(currentDir, name);
1477
1419
  try {
1478
- const stats = (0, import_node_fs12.statSync)(fullPath);
1420
+ const stats = (0, import_node_fs11.statSync)(fullPath);
1479
1421
  if (stats.isDirectory()) {
1480
1422
  dirs.push(name);
1481
1423
  } else {
@@ -1493,9 +1435,9 @@ function buildTreeFromDepth(rootDir, currentDir, depth, parentPrefix, prefix) {
1493
1435
  const linePrefix = `${prefix}${isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}`;
1494
1436
  const childPrefix = `${prefix}${isLast ? " " : "\u2502 "}`;
1495
1437
  lines.push(`${linePrefix}${name}`);
1496
- const fullPath = (0, import_node_path13.resolve)(currentDir, name);
1438
+ const fullPath = (0, import_node_path12.resolve)(currentDir, name);
1497
1439
  try {
1498
- const stats = (0, import_node_fs12.statSync)(fullPath);
1440
+ const stats = (0, import_node_fs11.statSync)(fullPath);
1499
1441
  if (stats.isDirectory()) {
1500
1442
  const childLines = buildTreeFromDepth(rootDir, fullPath, depth + 1, "", childPrefix);
1501
1443
  lines.push(...childLines);
@@ -1600,7 +1542,7 @@ var AGENT_TOOL_DEFINITIONS = [
1600
1542
  type: "function",
1601
1543
  function: {
1602
1544
  name: "write_file",
1603
- description: "Create a new text file. Fails if the file already exists. Use replace_file or edit_file to modify existing files.",
1545
+ description: "Create a new text file. Fails if the file already exists. Use edit_file or replace_lines to modify existing files.",
1604
1546
  parameters: {
1605
1547
  type: "object",
1606
1548
  properties: {
@@ -1617,7 +1559,7 @@ var AGENT_TOOL_DEFINITIONS = [
1617
1559
  type: "function",
1618
1560
  function: {
1619
1561
  name: "replace_file",
1620
- description: "Replace the entire contents of an existing text file. Use expected_sha256 from read_file to avoid overwriting concurrent changes.",
1562
+ description: "Replace an existing text file with new content. Use as a last resort \u2014 prefer edit_file or replace_lines first.",
1621
1563
  parameters: {
1622
1564
  type: "object",
1623
1565
  properties: {
@@ -1636,7 +1578,7 @@ var AGENT_TOOL_DEFINITIONS = [
1636
1578
  type: "function",
1637
1579
  function: {
1638
1580
  name: "edit_file",
1639
- description: "Apply a targeted search-and-replace edit to an existing text file. Prefer this over replace_file for small changes.",
1581
+ description: "Apply a search-and-replace edit to an existing text file. The primary file editing tool \u2014 use this first.",
1640
1582
  parameters: {
1641
1583
  type: "object",
1642
1584
  properties: {
@@ -1943,13 +1885,7 @@ var HANDLER_MAP = {
1943
1885
  todo_write: todoWriteHandler,
1944
1886
  get_workspace_tree: getWorkspaceTreeHandler,
1945
1887
  spawn_subagent: spawnSubAgentHandler,
1946
- ask_question: askQuestionHandler,
1947
- plan_create: planCreateHandler,
1948
- plan_read: planReadHandler,
1949
- plan_update: planUpdateHandler,
1950
- plan_edit: planEditHandler,
1951
- plan_delete: planDeleteHandler,
1952
- plan_list: planListHandler
1888
+ ask_question: askQuestionHandler
1953
1889
  };
1954
1890
  var ASK_QUESTION_TOOL_NAME = "ask_question";
1955
1891
  var AGENT_MODE_EXCLUDED_TOOL_NAMES = /* @__PURE__ */ new Set([
@@ -1973,9 +1909,6 @@ function getToolDefinitions(agentMode) {
1973
1909
  (t) => ASK_MODE_TOOL_NAMES.has(t.function.name)
1974
1910
  );
1975
1911
  }
1976
- if (agentMode === "plan") {
1977
- return AGENT_TOOL_DEFINITIONS;
1978
- }
1979
1912
  return AGENT_TOOL_DEFINITIONS.filter(
1980
1913
  (t) => !AGENT_MODE_EXCLUDED_TOOL_NAMES.has(t.function.name)
1981
1914
  );
@@ -2011,6 +1944,45 @@ async function executeToolCall(name, rawArguments, context) {
2011
1944
  return handler(parsedArgs, context);
2012
1945
  }
2013
1946
 
1947
+ // src/agent/thinking-config.ts
1948
+ function resolveThinkingParams(provider, thinkingEnabled, override) {
1949
+ if (override) {
1950
+ return thinkingEnabled ? override.enabled : override.disabled;
1951
+ }
1952
+ const preset = THINKING_PRESETS[provider];
1953
+ if (!preset) return void 0;
1954
+ return thinkingEnabled ? preset.enabled : preset.disabled;
1955
+ }
1956
+ var THINKING_PRESETS = {
1957
+ deepseek: {
1958
+ // @see https://api-docs.deepseek.com/guides/thinking_mode
1959
+ enabled: {
1960
+ thinking: { type: "enabled" },
1961
+ reasoning_effort: "high"
1962
+ },
1963
+ disabled: { thinking: { type: "disabled" } }
1964
+ },
1965
+ glm: {
1966
+ enabled: { thinking: { type: "enabled" } },
1967
+ disabled: { thinking: { type: "disabled" } }
1968
+ },
1969
+ agnes: {
1970
+ enabled: { chat_template_kwargs: { enable_thinking: true } },
1971
+ disabled: {}
1972
+ },
1973
+ nvidia: {
1974
+ enabled: { chat_template_kwargs: { enable_thinking: true } },
1975
+ disabled: { chat_template_kwargs: { enable_thinking: false } }
1976
+ },
1977
+ minimax: {
1978
+ enabled: {
1979
+ thinking: { type: "adaptive" },
1980
+ reasoning_split: true
1981
+ },
1982
+ disabled: { thinking: { type: "disabled" } }
1983
+ }
1984
+ };
1985
+
2014
1986
  // src/agent/llm-stream.ts
2015
1987
  function chatCompletionsUrl(baseUrl) {
2016
1988
  const trimmed = baseUrl.trim().replace(/\/+$/, "");
@@ -2031,6 +2003,16 @@ async function startLLMStream(options, callbacks) {
2031
2003
  body.tools = options.tools;
2032
2004
  body.tool_choice = "auto";
2033
2005
  }
2006
+ if (options.thinkingEnabled !== void 0 && options.thinkingProvider) {
2007
+ const thinkingParams = resolveThinkingParams(
2008
+ options.thinkingProvider,
2009
+ options.thinkingEnabled,
2010
+ options.thinkingOverride
2011
+ );
2012
+ if (thinkingParams) {
2013
+ Object.assign(body, thinkingParams);
2014
+ }
2015
+ }
2034
2016
  try {
2035
2017
  const response = await fetch(url, {
2036
2018
  method: "POST",
@@ -2039,7 +2021,8 @@ async function startLLMStream(options, callbacks) {
2039
2021
  "Content-Type": "application/json",
2040
2022
  Accept: "text/event-stream"
2041
2023
  },
2042
- body: JSON.stringify(body)
2024
+ body: JSON.stringify(body),
2025
+ signal: options.signal
2043
2026
  });
2044
2027
  if (!response.ok) {
2045
2028
  const errorBody = await response.text().catch(() => "");
@@ -2056,8 +2039,22 @@ async function startLLMStream(options, callbacks) {
2056
2039
  }
2057
2040
  });
2058
2041
  let hasToolCalls = false;
2042
+ const onAbort = () => {
2043
+ reader.cancel().catch(() => {
2044
+ });
2045
+ };
2046
+ options.signal?.addEventListener("abort", onAbort, { once: true });
2059
2047
  while (true) {
2060
- const { done, value } = await reader.read();
2048
+ if (options.signal?.aborted) {
2049
+ break;
2050
+ }
2051
+ let chunk;
2052
+ try {
2053
+ chunk = await reader.read();
2054
+ } catch {
2055
+ break;
2056
+ }
2057
+ const { done, value } = chunk;
2061
2058
  if (done) break;
2062
2059
  buffer += decoder.decode(value, { stream: true });
2063
2060
  while (true) {
@@ -2181,7 +2178,24 @@ async function runAgentWithTools(input, toolContext, onEvent) {
2181
2178
  let messages = [...input.messages];
2182
2179
  let cumulativeUsage;
2183
2180
  while (true) {
2184
- const turn = await runSingleAgentTurn(input, messages, onEvent);
2181
+ if (input.signal?.aborted) {
2182
+ killShellsByTask(input.taskId);
2183
+ onEvent({ type: "status", taskId: input.taskId, status: "cancelled" });
2184
+ onEvent({ type: "done", taskId: input.taskId, usage: cumulativeUsage });
2185
+ return messages;
2186
+ }
2187
+ let turn;
2188
+ try {
2189
+ turn = await runSingleAgentTurn(input, messages, onEvent);
2190
+ } catch (err) {
2191
+ if (err instanceof Error && err.name === "AbortError") {
2192
+ killShellsByTask(input.taskId);
2193
+ onEvent({ type: "status", taskId: input.taskId, status: "cancelled" });
2194
+ onEvent({ type: "done", taskId: input.taskId, usage: cumulativeUsage });
2195
+ return messages;
2196
+ }
2197
+ throw err;
2198
+ }
2185
2199
  if (turn.usage) {
2186
2200
  cumulativeUsage = cumulativeUsage ? {
2187
2201
  promptTokens: cumulativeUsage.promptTokens + turn.usage.promptTokens,
@@ -2191,14 +2205,14 @@ async function runAgentWithTools(input, toolContext, onEvent) {
2191
2205
  }
2192
2206
  if (turn.toolCalls.length === 0) {
2193
2207
  if (turn.content || turn.reasoningContent) {
2194
- messages = [
2195
- ...messages,
2196
- {
2197
- role: "assistant",
2198
- content: turn.content || void 0,
2199
- reasoning_content: turn.reasoningContent || void 0
2200
- }
2201
- ];
2208
+ const assistantMessage = { role: "assistant" };
2209
+ if (turn.content.trim()) {
2210
+ assistantMessage.content = turn.content;
2211
+ }
2212
+ if (turn.reasoningContent.trim()) {
2213
+ assistantMessage.reasoning_content = turn.reasoningContent;
2214
+ }
2215
+ messages = [...messages, assistantMessage];
2202
2216
  }
2203
2217
  onEvent({ type: "done", taskId: input.taskId, usage: cumulativeUsage ?? turn.usage });
2204
2218
  onEvent({ type: "status", taskId: input.taskId, status: "completed" });
@@ -2219,7 +2233,11 @@ async function runSingleAgentTurn(input, messages, onEvent) {
2219
2233
  apiKey: input.apiKey,
2220
2234
  model: input.model,
2221
2235
  messages,
2222
- tools: getToolDefinitions(input.agentMode)
2236
+ tools: getToolDefinitions(input.agentMode),
2237
+ thinkingProvider: input.provider,
2238
+ thinkingEnabled: input.thinkingEnabled,
2239
+ thinkingOverride: input.thinkingParams,
2240
+ signal: input.signal
2223
2241
  },
2224
2242
  {
2225
2243
  onContent: (delta) => {
@@ -2329,31 +2347,434 @@ async function appendToolResults(messages, turn, context, onEvent) {
2329
2347
  // src/agent/environment/index.ts
2330
2348
  var import_node_os2 = require("node:os");
2331
2349
  var import_node_child_process2 = require("node:child_process");
2332
- var import_node_fs13 = require("node:fs");
2333
- var import_node_path14 = require("node:path");
2350
+ var import_node_fs12 = require("node:fs");
2351
+ var import_node_path13 = require("node:path");
2352
+
2353
+ // ../src/features/skills/system/registry.ts
2354
+ var OPERATING_PRINCIPLES_CONTENT = `# Agent Operating Principles
2355
+
2356
+ Your job is to understand the user's intent, make correct changes, verify the result, and communicate accurately.
2357
+
2358
+ ### Core rules
2359
+
2360
+ - Follow the user's request. Do not expand scope without a clear reason.
2361
+ - When the request has multiple valid interpretations, state your assumption
2362
+ explicitly and proceed. Only ask when the decision is costly to reverse.
2363
+ - Prefer evidence over confidence. Tool output is more reliable than assumptions.
2364
+ - Never present guesses as facts. Mark uncertainty plainly when evidence is incomplete.
2365
+ - Optimize for user-visible outcomes, not internal activity.
2366
+ - Keep changes correct, readable, maintainable, testable, and secure.
2367
+ - Do not push commits, rewrite history, or perform destructive actions unless explicitly instructed.
2368
+
2369
+ ### Decision order
2370
+
2371
+ 0. Determine whether this is a question, an exploration, or a change request.
2372
+ Answer questions directly; act only on clear instructions.
2373
+ 1. Understand the request.
2374
+ 2. Gather only the context needed to act safely.
2375
+ 3. Plan when the work has meaningful phases.
2376
+ 4. Modify the smallest surface that solves the problem.
2377
+ 5. Verify before claiming success.
2378
+ 6. Report the outcome, verification, and any remaining risk.
2379
+ `;
2380
+ var CONTEXT_AND_EVIDENCE_CONTENT = `# Context and Evidence
2381
+
2382
+ Treat provided context as useful signal, not guaranteed truth.
2383
+
2384
+ Do not assume:
2385
+
2386
+ - file contents
2387
+ - repository structure
2388
+ - command output
2389
+ - test results
2390
+ - git state
2391
+ - API behavior
2392
+ - web content
2393
+
2394
+ Use tools to confirm facts whenever the answer or change depends on them.
2395
+
2396
+ ### Evidence handling
2397
+
2398
+ - Read the relevant file before editing it.
2399
+ - Use search results to decide what to inspect, not as a substitute for inspection.
2400
+ - Treat shell output, linter output, test output, and git output as source-of-truth for the current workspace state.
2401
+ - If evidence contradicts your expectation, update your understanding immediately.
2402
+ - If required evidence is unavailable, say what is missing and avoid pretending the task is fully verified.
2403
+ `;
2404
+ var TOOL_USAGE_CONTENT = `# Tool Usage
2405
+
2406
+ Use tools when they provide evidence that would otherwise be guessed.
2407
+
2408
+ ### Local tools
2409
+
2410
+ - Use glob to find files by name pattern.
2411
+ - Use grep to search file contents by unique strings, symbols, paths, routes, config keys, or errors.
2412
+ - Use shell for builds, tests, git operations, package commands, and repository inspection.
2413
+ - Commands run non-interactively. Avoid commands that require interactive input.
2414
+ - For long-running commands such as dev servers or watch mode, run shell with \`block_until_ms=0\`, then await the returned \`shell_id\` when needed.
2415
+ - Use \`get_workspace_tree\` for a bird's-eye view of the project structure. It respects \`.gitignore\`, excludes large directories (\`node_modules\`, \`.git\`, \`dist\`, etc.) automatically, and paginates like \`read_file\` via \`start_line\` and \`max_lines\`. Prefer this over manually calling \`list_dir\` on every subdirectory.
2416
+ - **File editing workflow:** \`edit_file\` (search-and-replace, text-anchored) is the most common file editing tool \u2014 highest priority. If escaping issues arise, fall back to \`replace_lines\` (by line number, read the file again first to avoid line number drift). Last resort: \`replace_file\` (supports both partial and full replacement, but full replacement consumes many tokens \u2014 use sparingly). Use \`write_file\` to create new files. Use \`read_file\`'s \`sha256\` as \`expected_sha256\` for concurrent safety.
2417
+
2418
+ ### Web and skills
2419
+
2420
+ - Use web_search for current or external information, such as version-specific behavior or recent documentation.
2421
+ - Use browse_page to read a specific URL after web_search finds a promising primary source.
2422
+ - browse_page may not render JavaScript-heavy pages. Quote retrieved content instead of inventing details.
2423
+ - Use list_skills to inspect available skills.
2424
+ - Use read_skill before following a skill's instructions.
2425
+ - Use create_skill only when the user asks to save reusable instructions.
2426
+
2427
+ ### Failure handling
2428
+
2429
+ When a tool fails:
2430
+
2431
+ 1. Read the error code and message.
2432
+ 2. Form a new hypothesis.
2433
+ 3. Adjust the approach.
2434
+
2435
+ Do not repeat the same failing action without learning from the failure.
2436
+
2437
+ ### Shell tools
2438
+
2439
+ You have 5 shell tools. Use them together:
2440
+
2441
+ - **shell** \u2014 run a command. Set \`block_until_ms=0\` for background mode; returns a \`shell_id\`.
2442
+ - **await** \u2014 poll a background shell to completion. Pass the \`shell_id\` from shell.
2443
+ - **list_shells** \u2014 list active shells. Default shows running only; use \`status_filter="all"\` to see all states.
2444
+ - **read_shell_logs** \u2014 read stdout/stderr from any shell. Paginate with \`offset\` and \`limit\`.
2445
+ - **kill_shell** \u2014 kill a running shell by \`shell_id\`. Cannot kill human terminals.
2446
+
2447
+ Workflows:
2448
+ 1. **background + await**: \`shell(cmd, {block_until_ms: 0})\` \u2192 do other work \u2192 \`await({shell_id})\`
2449
+ 2. **monitor progress**: \`shell(background)\` \u2192 \`read_shell_logs\` to peek \u2192 \`await\` when done
2450
+ 3. **clean up**: \`list_shells({status_filter: "all"})\` \u2192 \`read_shell_logs\` \u2192 \`kill_shell\` if stuck
2451
+
2452
+ **Remote target limitation:** When using \`shell(target: "<alias>")\`, background mode does NOT return a usable \`shell_id\`, so companion tools (\`await\`, \`list_shells\`, \`kill_shell\`, \`read_shell_logs\`) are not supported. Only blocking mode works for remote commands.
2453
+
2454
+ ### spawn_subagent
2455
+
2456
+ Use spawn_subagent for independent tasks that require multi-step exploration, verification, or research.
2457
+
2458
+ Do not use it for simple lookups, single-file reads, or tasks that can be completed with a few direct tool calls.
2459
+
2460
+ Before spawning, ask:
2461
+ 1. Is the task independent?
2462
+ 2. Does it require significant exploration?
2463
+ 3. Would delegation improve focus?
2464
+
2465
+ If not, do the work yourself.
2466
+
2467
+ Provide a clear task description and expected output. Sub-agents should return findings, evidence, and uncertainties.
2468
+ `;
2469
+ var CODE_NAVIGATION_CONTENT = `# Code Navigation
2470
+
2471
+ Prefer direct signals over dependency wandering.
2472
+
2473
+ ### Default flow
2474
+
2475
+ \`\`\`text
2476
+ search -> inspect -> modify
2477
+ \`\`\`
2478
+
2479
+ Avoid manually tracing long chains from entry point to imports unless direct search is insufficient.
2480
+
2481
+ ### Search signals
2482
+
2483
+ Search for the most unique signal the target code would contain:
2484
+
2485
+ - function or method names: \`calculateTotal\`, \`handleSubmit\`
2486
+ - constants or variables: \`MAX_RETRY_COUNT\`, \`workspaceName\`
2487
+ - route strings: \`"/api/users"\`, \`"/login"\`
2488
+ - framework annotations: \`@RestController\`, \`@Service\`
2489
+ - trait, interface, or class names: \`UserRepository\`, \`impl Iterator\`
2490
+ - test descriptions: \`"should return 401"\`, \`testShould\`
2491
+ - config keys: \`database.url\`, \`logging.level\`
2492
+ - model, table, or schema names: \`users\`, \`class User\`
2493
+ - exact error messages from logs, tests, or users
2494
+
2495
+ ### Reading discipline
2496
+
2497
+ - Read only files needed to understand or modify the target behavior.
2498
+ - Prefer narrow reads around relevant code when files are large.
2499
+ - Expand outward only when the local context is insufficient.
2500
+ `;
2501
+ var TASK_PLANNING_CONTENT = `# Task Planning
2502
+
2503
+ Use the task-progress list to make meaningful multi-step work legible.
2504
+
2505
+ ### Create a task list when
2506
+
2507
+ - implementing a feature across exploration, edits, and verification
2508
+ - debugging with multiple hypotheses
2509
+ - refactoring or migrating several files or layers
2510
+ - finishing work with clear phases such as design, implement, test, polish
2511
+ - running long work where the user may return later
2512
+
2513
+ ### Skip a task list for
2514
+
2515
+ - short answers or explanations
2516
+ - one-command requests
2517
+ - a single obvious edit
2518
+ - pure exploration questions
2519
+ - trivial follow-ups
2520
+
2521
+ When unsure, prefer no list over a noisy one.
2522
+
2523
+ ### Task design
2524
+
2525
+ - Write outcome-oriented tasks: "Add OAuth callback validation", not "Read auth file".
2526
+ - Keep tasks coarse enough for the user to follow, usually 3-5 items.
2527
+ - Keep at most one task in progress.
2528
+ - Mark tasks complete as soon as they are actually complete.
2529
+ - Rename, add, cancel, or remove tasks when scope changes.
2530
+ `;
2531
+ var CODE_MODIFICATION_CONTENT = `# Code Modification
2532
+
2533
+ Make changes as a maintainer, not as a patch generator.
2534
+
2535
+ ### Before editing
2536
+
2537
+ 1. Locate the relevant implementation.
2538
+ 2. Understand surrounding context.
2539
+ 3. Identify the smallest change that solves the requested problem.
2540
+
2541
+ ### Editing rules
2542
+
2543
+ - Prefer minimal, targeted changes.
2544
+ - Follow existing naming, architecture, formatting, testing, and error-handling patterns.
2545
+ - Do not rewrite working code unnecessarily.
2546
+ - Do not change unrelated behavior.
2547
+ - Do not introduce style-only edits unless requested.
2548
+ - Do not remove functionality without a clear reason.
2549
+ - Do not overwrite user changes. If the working tree is dirty, work with existing changes instead of reverting them.
2550
+ - Use structured APIs or parsers for structured data when available.
2551
+
2552
+ ### High-risk areas
2553
+
2554
+ Be extra careful with authentication, authorization, persistence, migrations, production configuration, secrets, and destructive operations.
2555
+ `;
2556
+ var VERIFICATION_CONTENT = `# Verification
2557
+
2558
+ Do not claim success solely because code was changed.
2559
+
2560
+ ### After changing code
2561
+
2562
+ 1. Re-read the changed area when practical.
2563
+ 2. Review the diff to confirm only intended changes are included.
2564
+ 3. Before running any verification command, call list_shells first to check whether the user already has a running dev server or relevant process. If one exists, prefer telling the user to reload over starting a new instance. Only run a new verification command when no relevant process is already running.
2565
+ 4. Run the most relevant verification available.
2566
+ 5. Report what was verified and what was not.
2567
+
2568
+ ### Prefer relevant checks
2569
+
2570
+ - TypeScript: \`tsc --noEmit\`, framework type checks, or project scripts.
2571
+ - Tests: focused tests first, broader suites when risk is high.
2572
+ - Builds: run when the change can affect packaging, routing, generated output, or runtime wiring.
2573
+ - Linters: run or inspect diagnostics for changed files.
2574
+
2575
+ If verification fails, read the error, fix what is in scope, and rerun the relevant check. If verification cannot be run, state that clearly.
2576
+ `;
2577
+ var GIT_WORKFLOW_CONTENT = `# Git Workflow
2578
+
2579
+ Git operations must reflect actual repository state.
2580
+
2581
+ ### Before committing
2582
+
2583
+ - Review \`git status\`.
2584
+ - Review \`git diff\` and \`git diff --staged\` as appropriate.
2585
+ - Do not assume staged content matches the current task.
2586
+ - Do not commit secrets or credentials.
2587
+
2588
+ ### Staging
2589
+
2590
+ - Stage only files related to the intended change.
2591
+ - Avoid \`git add .\` and \`git add -A\` unless the user explicitly wants all changes included.
2592
+ - Leave unrelated modifications unstaged.
2593
+
2594
+ ### Commits
2595
+
2596
+ - Generate commit messages from staged changes only.
2597
+ - Use Conventional Commits: \`type(scope): summary\`.
2598
+ - Keep the subject under 72 characters.
2599
+ - Keep commits logically coherent.
2600
+
2601
+ ### Push behavior
2602
+
2603
+ - "commit" means commit only.
2604
+ - "push" means push only.
2605
+ - "commit and push" means both.
2606
+ - Never push unless explicitly instructed.
2607
+ `;
2608
+ var COMMUNICATION_CONTENT = `# Communication
2609
+
2610
+ Communicate conclusions, decisions, blockers, and verification results.
2611
+
2612
+ ### Style
2613
+
2614
+ - Be concise and direct.
2615
+ - Do not narrate every tool call.
2616
+ - Do not reveal hidden chain-of-thought.
2617
+ - Do not frame internal activity as an accomplishment.
2618
+ - Explain uncertainty and blockers honestly.
2619
+ - When reporting completion, include the user-visible result and verification performed.
2620
+
2621
+ ### Reviews
2622
+
2623
+ When the user asks for a review, prioritize findings first:
2624
+
2625
+ - correctness bugs
2626
+ - security risks
2627
+ - behavioral regressions
2628
+ - missing tests for meaningful risk
2629
+
2630
+ Order findings by severity. If no issues are found, say so and mention residual risk or unrun checks.
2631
+ `;
2632
+ var CODE_REVIEW_CONTENT = `# Code Review Workflow
2633
+
2634
+ Use this workflow when reviewing code rather than implementing changes.
2635
+
2636
+ ### Review focus
2637
+
2638
+ 1. Correctness and edge cases.
2639
+ 2. Security issues such as injection, XSS, authorization gaps, and leaked secrets.
2640
+ 3. Behavioral regressions.
2641
+ 4. Error handling and failure modes.
2642
+ 5. Test coverage for meaningful behavior.
2643
+ 6. Readability and maintainability when it affects future correctness.
2644
+
2645
+ ### Feedback format
2646
+
2647
+ - Lead with findings, ordered by severity.
2648
+ - Cite the specific file or symbol involved.
2649
+ - Explain the impact, not just the preference.
2650
+ - Keep summaries brief and secondary.
2651
+ - If there are no findings, say that clearly and list any verification gaps.
2652
+ `;
2653
+ var SYSTEM_SKILLS = [
2654
+ {
2655
+ id: "agent-operating-principles",
2656
+ slug: "agent-operating-principles",
2657
+ name: "Agent Operating Principles",
2658
+ description: "Core identity, priorities, and decision order for software engineering agent work.",
2659
+ content: OPERATING_PRINCIPLES_CONTENT,
2660
+ defaultEnabled: true,
2661
+ category: "core"
2662
+ },
2663
+ {
2664
+ id: "context-and-evidence",
2665
+ slug: "context-and-evidence",
2666
+ name: "Context and Evidence",
2667
+ description: "How to treat workspace context, tool output, uncertainty, and evidence before acting.",
2668
+ content: CONTEXT_AND_EVIDENCE_CONTENT,
2669
+ defaultEnabled: true,
2670
+ category: "core"
2671
+ },
2672
+ {
2673
+ id: "tool-usage",
2674
+ slug: "tool-usage",
2675
+ name: "Tool Usage",
2676
+ description: "When and how to use filesystem, shell, web, and skill tools for reliable evidence.",
2677
+ content: TOOL_USAGE_CONTENT,
2678
+ defaultEnabled: true,
2679
+ category: "core"
2680
+ },
2681
+ {
2682
+ id: "code-navigation",
2683
+ slug: "code-navigation",
2684
+ name: "Code Navigation",
2685
+ description: "Search-first navigation rules for locating relevant code without wasting context.",
2686
+ content: CODE_NAVIGATION_CONTENT,
2687
+ defaultEnabled: true,
2688
+ category: "development"
2689
+ },
2690
+ {
2691
+ id: "task-planning",
2692
+ slug: "task-planning",
2693
+ name: "Task Planning",
2694
+ description: "When and how to use the session task-progress list for multi-step work.",
2695
+ content: TASK_PLANNING_CONTENT,
2696
+ defaultEnabled: true,
2697
+ category: "workflow"
2698
+ },
2699
+ {
2700
+ id: "code-modification",
2701
+ slug: "code-modification",
2702
+ name: "Code Modification",
2703
+ description: "Rules for making minimal, maintainable, and project-consistent code changes.",
2704
+ content: CODE_MODIFICATION_CONTENT,
2705
+ defaultEnabled: true,
2706
+ category: "development"
2707
+ },
2708
+ {
2709
+ id: "verification",
2710
+ slug: "verification",
2711
+ name: "Verification",
2712
+ description: "How to validate changes before claiming success and how to report unverified work.",
2713
+ content: VERIFICATION_CONTENT,
2714
+ defaultEnabled: true,
2715
+ category: "workflow"
2716
+ },
2717
+ {
2718
+ id: "git-workflow",
2719
+ slug: "git-workflow",
2720
+ name: "Git Workflow",
2721
+ description: "Safe staging, commit, and push boundaries based on actual git state.",
2722
+ content: GIT_WORKFLOW_CONTENT,
2723
+ defaultEnabled: true,
2724
+ category: "workflow"
2725
+ },
2726
+ {
2727
+ id: "communication",
2728
+ slug: "communication",
2729
+ name: "Communication",
2730
+ description: "How to communicate outcomes, uncertainty, blockers, verification, and review findings.",
2731
+ content: COMMUNICATION_CONTENT,
2732
+ defaultEnabled: true,
2733
+ category: "core"
2734
+ },
2735
+ {
2736
+ id: "code-review",
2737
+ slug: "code-review",
2738
+ name: "Code Review Workflow",
2739
+ description: "Specialized workflow for reviewing code for correctness, security, regressions, and tests.",
2740
+ content: CODE_REVIEW_CONTENT,
2741
+ defaultEnabled: false,
2742
+ category: "review"
2743
+ }
2744
+ ];
2745
+ var slugSet = /* @__PURE__ */ new Set();
2746
+ for (const skill of SYSTEM_SKILLS) {
2747
+ if (slugSet.has(skill.slug)) {
2748
+ throw new Error(`Duplicate system skill slug: ${skill.slug}`);
2749
+ }
2750
+ slugSet.add(skill.slug);
2751
+ }
2752
+
2753
+ // src/agent/environment/index.ts
2334
2754
  function resolveAgentEnvironment(workspaceDir) {
2335
2755
  const os = `${(0, import_node_os2.platform)()} (${(0, import_node_os2.release)()})`;
2336
2756
  const shell = resolveShell();
2337
2757
  const isGitRepository = workspaceDir ? checkIsGitRepository(workspaceDir) : false;
2338
- const today = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
2339
- weekday: "long",
2340
- year: "numeric",
2341
- month: "long",
2342
- day: "numeric",
2343
- timeZone: "UTC"
2344
- });
2758
+ const today = formatToday(/* @__PURE__ */ new Date());
2345
2759
  let agentsMd = null;
2346
2760
  if (workspaceDir) {
2347
- const agentsMdPath = (0, import_node_path14.resolve)(workspaceDir, "AGENTS.md");
2348
- if ((0, import_node_fs13.existsSync)(agentsMdPath)) {
2349
- const content = (0, import_node_fs13.readFileSync)(agentsMdPath, "utf-8");
2761
+ const agentsMdPath = (0, import_node_path13.resolve)(workspaceDir, "AGENTS.md");
2762
+ if ((0, import_node_fs12.existsSync)(agentsMdPath)) {
2763
+ const content = (0, import_node_fs12.readFileSync)(agentsMdPath, "utf-8");
2350
2764
  agentsMd = {
2351
2765
  path: agentsMdPath,
2352
- content: content.length > 4e3 ? content.slice(0, 4e3) + "\n... [truncated]" : content,
2353
- truncated: content.length > 4e3
2766
+ content: content.length > 32e3 ? content.slice(0, 32e3) + "\n... [truncated]" : content,
2767
+ truncated: content.length > 32e3
2354
2768
  };
2355
2769
  }
2356
2770
  }
2771
+ const enabledSystemSkills = SYSTEM_SKILLS.filter(
2772
+ (skill) => skill.defaultEnabled
2773
+ ).map((skill) => ({
2774
+ slug: skill.slug,
2775
+ name: skill.name,
2776
+ content: skill.content
2777
+ }));
2357
2778
  return {
2358
2779
  workspaceDir,
2359
2780
  os,
@@ -2361,9 +2782,104 @@ function resolveAgentEnvironment(workspaceDir) {
2361
2782
  isGitRepository,
2362
2783
  today,
2363
2784
  agentsMd,
2364
- enabledSystemSkills: []
2785
+ enabledSystemSkills
2365
2786
  };
2366
2787
  }
2788
+ function buildSystemPrompt(env, agentMode) {
2789
+ const workspaceLine = env.workspaceDir ?? "not selected";
2790
+ const gitLine = env.isGitRepository ? "yes" : env.workspaceDir ? "no" : "unknown";
2791
+ const modeLine = buildModeLine(agentMode);
2792
+ const modeGuidance = buildModeGuidance(agentMode, env.workspaceDir);
2793
+ const allBlocks = [];
2794
+ allBlocks.push(
2795
+ [
2796
+ "You are Coder, a helpful terminal AI assistant.",
2797
+ "",
2798
+ "## Environment",
2799
+ "",
2800
+ `- workspaceDir: ${workspaceLine}`,
2801
+ `- os: ${env.os}`,
2802
+ `- shell: ${env.shell}`,
2803
+ `- gitRepository: ${gitLine}`,
2804
+ `- date: ${env.today}`,
2805
+ `- mode: ${modeLine}`
2806
+ ].join("\n")
2807
+ );
2808
+ allBlocks.push(
2809
+ [
2810
+ "## Communication Rules",
2811
+ "",
2812
+ "1. Reply in the same language the user uses. Be concise, accurate, and friendly.",
2813
+ "2. Do not act unless the user has clearly asked you to. Answering questions, explaining, and analyzing do not require action \u2014 stop before reaching for tools."
2814
+ ].join("\n")
2815
+ );
2816
+ for (const skill of env.enabledSystemSkills) {
2817
+ allBlocks.push(
2818
+ `## ${skill.name}
2819
+
2820
+ ${stripLeadingMarkdownH1(skill.content)}`
2821
+ );
2822
+ }
2823
+ if (env.agentsMd?.content.trim()) {
2824
+ allBlocks.push(buildProjectInstructionsSection(env.agentsMd));
2825
+ }
2826
+ if (modeGuidance.length > 0) {
2827
+ allBlocks.push(modeGuidance.join("\n"));
2828
+ }
2829
+ return allBlocks.join("\n\n---\n\n");
2830
+ }
2831
+ function buildModeLine(agentMode) {
2832
+ switch (agentMode) {
2833
+ case "ask":
2834
+ return "ask (read-only: can read files, search code, browse the web, and list skills \u2014 cannot modify files or run shell commands)";
2835
+ default:
2836
+ return "agent (full tool access)";
2837
+ }
2838
+ }
2839
+ function buildModeGuidance(agentMode, _workspaceDir) {
2840
+ if (agentMode === "ask") {
2841
+ return buildAskModeGuidance();
2842
+ }
2843
+ return [];
2844
+ }
2845
+ function buildAskModeGuidance() {
2846
+ return [
2847
+ "## Mode Guidance",
2848
+ "",
2849
+ "You are in Ask mode \u2014 you can only read files, search, and browse.",
2850
+ "When the user asks you to modify files, run commands, or perform any write operation:",
2851
+ " - Explain that the task requires write access.",
2852
+ " - Tell the user they can switch to Agent mode by using `coder run` instead of `coder ask` to give you full tool access.",
2853
+ `Do NOT silently refuse or just say "I can't do that." Always provide a clear path forward.`
2854
+ ];
2855
+ }
2856
+ function buildProjectInstructionsSection(agentsMd) {
2857
+ const lines = [
2858
+ "## Project instructions (AGENTS.md)",
2859
+ "",
2860
+ "Follow these project-specific rules when they do not conflict with the user's current message.",
2861
+ agentsMd.content.trimEnd()
2862
+ ];
2863
+ if (agentsMd.truncated) {
2864
+ lines.push(
2865
+ "",
2866
+ `Note: ${agentsMd.path} was truncated to 32 KB. Use read_file on ${agentsMd.path} to read the full file if needed.`
2867
+ );
2868
+ }
2869
+ return lines.join("\n");
2870
+ }
2871
+ function stripLeadingMarkdownH1(content) {
2872
+ return content.replace(/^#\s+[^\n]+\n+/, "").trim();
2873
+ }
2874
+ function formatToday(date) {
2875
+ return new Intl.DateTimeFormat("en-CA", {
2876
+ year: "numeric",
2877
+ month: "2-digit",
2878
+ day: "2-digit",
2879
+ weekday: "long",
2880
+ timeZoneName: "longOffset"
2881
+ }).format(date);
2882
+ }
2367
2883
  function resolveShell() {
2368
2884
  const p = (0, import_node_os2.platform)();
2369
2885
  if (p === "win32") {
@@ -2383,39 +2899,6 @@ function checkIsGitRepository(dir) {
2383
2899
  return false;
2384
2900
  }
2385
2901
  }
2386
- function buildSystemPrompt(env) {
2387
- const workspaceLine = env.workspaceDir ?? "not selected";
2388
- const gitLine = env.isGitRepository ? "yes" : env.workspaceDir ? "no" : "unknown";
2389
- const lines = [
2390
- "You are Coder, a helpful terminal AI assistant.",
2391
- "",
2392
- "## Environment",
2393
- `- Workspace: ${workspaceLine}`,
2394
- `- OS: ${env.os}`,
2395
- `- Shell: ${env.shell}`,
2396
- `- Git repository: ${gitLine}`,
2397
- `- Date: ${env.today}`,
2398
- "",
2399
- "## Available Tools",
2400
- "You have access to all standard tools: file operations, shell commands, web search, skills, and more.",
2401
- "",
2402
- "## Guidelines",
2403
- "- Always read files before editing them.",
2404
- "- Prefer targeted edits over full file replacements.",
2405
- "- Use shell commands only for builds, tests, git, and non-interactive CLI tasks.",
2406
- "- When a task requires multiple steps, create a plan first using todo_write.",
2407
- "- After completing a task, summarize what was done.",
2408
- "- Use ask_question when you need clarification from the user.",
2409
- ""
2410
- ];
2411
- if (env.agentsMd) {
2412
- lines.push("## Project Instructions (AGENTS.md)");
2413
- lines.push("");
2414
- lines.push(env.agentsMd.content);
2415
- lines.push("");
2416
- }
2417
- return lines.join("\n");
2418
- }
2419
2902
 
2420
2903
  // src/agent/session.ts
2421
2904
  init_config();
@@ -2478,12 +2961,28 @@ async function runAgentSession(prompt, options) {
2478
2961
  const resolvedConfig = resolveProviderConfig(config, providerId);
2479
2962
  const apiKey = resolveApiKey(resolvedConfig);
2480
2963
  const modelId = options.model ?? config.lastModel;
2964
+ const modelDef = resolvedConfig.models.find((m) => m.id === modelId);
2965
+ const supportsThinking = modelDef?.supportsThinking === true || config.providers[resolvedConfig.provider]?.supportsThinking === true;
2966
+ const thinkingEnabled = options.thinking !== void 0 && supportsThinking ? options.thinking : void 0;
2967
+ let thinkingParamsOverride;
2968
+ if (thinkingEnabled !== void 0) {
2969
+ const settings = config.providers[resolvedConfig.provider];
2970
+ if (settings?.thinkingEnabledParams && settings?.thinkingDisabledParams) {
2971
+ thinkingParamsOverride = {
2972
+ enabled: settings.thinkingEnabledParams,
2973
+ disabled: settings.thinkingDisabledParams
2974
+ };
2975
+ }
2976
+ }
2481
2977
  let messages;
2482
2978
  if (options.existingMessages && options.existingMessages.length > 0) {
2483
- messages = [...options.existingMessages, { role: "user", content: prompt }];
2979
+ messages = [
2980
+ ...sanitizeMessages(options.existingMessages),
2981
+ { role: "user", content: prompt }
2982
+ ];
2484
2983
  } else {
2485
2984
  const env = resolveAgentEnvironment(workspaceDir);
2486
- const systemPrompt = buildSystemPrompt(env);
2985
+ const systemPrompt = buildSystemPrompt(env, options.agentMode);
2487
2986
  messages = [
2488
2987
  { role: "system", content: systemPrompt },
2489
2988
  { role: "user", content: prompt }
@@ -2494,6 +2993,7 @@ async function runAgentSession(prompt, options) {
2494
2993
  let currentContent = "";
2495
2994
  let currentThinking = "";
2496
2995
  let hasContent = false;
2996
+ let streamStarted = false;
2497
2997
  const toolContext = {
2498
2998
  workspaceDir,
2499
2999
  sessionId: taskId,
@@ -2514,7 +3014,11 @@ async function runAgentSession(prompt, options) {
2514
3014
  apiKeyEnvVar: resolvedConfig.apiKeyEnvVar,
2515
3015
  model: modelId,
2516
3016
  messages,
2517
- agentMode: options.agentMode
3017
+ agentMode: options.agentMode,
3018
+ provider: providerId,
3019
+ thinkingEnabled,
3020
+ thinkingParams: thinkingParamsOverride,
3021
+ signal: options.signal
2518
3022
  },
2519
3023
  toolContext,
2520
3024
  (event) => {
@@ -2522,25 +3026,34 @@ async function runAgentSession(prompt, options) {
2522
3026
  case "thinking_delta": {
2523
3027
  currentThinking += event.delta;
2524
3028
  if (isStreaming && !hasContent) {
3029
+ if (!streamStarted) {
3030
+ streamStarted = true;
3031
+ writeLine("");
3032
+ }
2525
3033
  writeStream(dim(event.delta));
2526
3034
  }
2527
3035
  break;
2528
3036
  }
2529
3037
  case "content_delta": {
2530
- if (!hasContent && currentThinking && isStreaming) {
2531
- writeLine("");
2532
- hasContent = true;
2533
- }
2534
- hasContent = true;
2535
- currentContent += event.delta;
2536
3038
  if (isStreaming) {
3039
+ if (!streamStarted) {
3040
+ streamStarted = true;
3041
+ }
3042
+ if (!hasContent && currentThinking) {
3043
+ writeLine("");
3044
+ }
3045
+ hasContent = true;
3046
+ currentContent += event.delta;
2537
3047
  writeStream(event.delta);
2538
3048
  }
2539
3049
  break;
2540
3050
  }
2541
3051
  case "tool_call_started": {
2542
3052
  if (isStreaming) {
2543
- if (hasContent || currentThinking) {
3053
+ if (!streamStarted) {
3054
+ streamStarted = true;
3055
+ writeLine("");
3056
+ } else if (hasContent || currentThinking) {
2544
3057
  writeLine("");
2545
3058
  }
2546
3059
  writeLine(`${dim("\u{1F527}")} ${bold(event.name)}${dim("...")}`);
@@ -2559,34 +3072,25 @@ async function runAgentSession(prompt, options) {
2559
3072
  if (event.status === "completed") {
2560
3073
  if (isStreaming) {
2561
3074
  writeLine("");
2562
- writeLine(success(`
2563
- \u2713 Task completed`));
3075
+ writeLine(success("\u2713 Task completed"));
2564
3076
  } else {
2565
- writeLine(success(`\u2713 Task completed`));
3077
+ writeLine(success("\u2713 Task completed"));
2566
3078
  if (currentContent) {
2567
3079
  writeLine("\n" + currentContent);
2568
3080
  }
2569
3081
  }
2570
3082
  } else if (event.status === "failed") {
2571
- writeLine(error(`
2572
- \u2717 Task failed`));
3083
+ writeLine(error(`\u2717 Task failed`));
2573
3084
  } else if (event.status === "cancelled") {
2574
- writeLine(warning(`
2575
- \u26A0 Task cancelled`));
3085
+ writeLine(warning(`\u26A0 Task cancelled`));
2576
3086
  }
2577
3087
  break;
2578
3088
  }
2579
3089
  case "done": {
2580
- if (event.usage && config.showUsage) {
2581
- writeLine(dim(
2582
- ` Tokens: ${event.usage.promptTokens}\u2191 ${event.usage.completionTokens}\u2193 ${event.usage.totalTokens}\u2211`
2583
- ));
2584
- }
2585
3090
  break;
2586
3091
  }
2587
3092
  case "error": {
2588
- writeLine(error(`
2589
- \u2717 Error: ${event.message}`));
3093
+ writeLine(error(`\u2717 Error: ${event.message}`));
2590
3094
  break;
2591
3095
  }
2592
3096
  }
@@ -2594,11 +3098,19 @@ async function runAgentSession(prompt, options) {
2594
3098
  );
2595
3099
  return finalMessages;
2596
3100
  } catch (err) {
3101
+ killAllShells();
2597
3102
  const message = err instanceof Error ? err.message : String(err);
2598
3103
  writeError(error(`Fatal error: ${message}`));
2599
3104
  process.exit(1);
2600
3105
  }
2601
3106
  }
3107
+ function sanitizeMessages(messages) {
3108
+ return messages.filter((msg) => {
3109
+ if (msg.role !== "assistant") return true;
3110
+ const text = typeof msg.content === "string" ? msg.content : void 0;
3111
+ return Boolean(text?.trim()) || Boolean(msg.reasoning_content?.trim()) || Boolean(msg.tool_calls?.length);
3112
+ });
3113
+ }
2602
3114
 
2603
3115
  // src/commands/run.ts
2604
3116
  async function runCommand(prompt, options) {
@@ -2620,16 +3132,6 @@ async function askCommand(prompt, options) {
2620
3132
  });
2621
3133
  }
2622
3134
 
2623
- // src/commands/plan.ts
2624
- async function planCommand(prompt, options) {
2625
- await runAgentSession(prompt, {
2626
- ...options,
2627
- agentMode: "plan",
2628
- workspaceDir: options.workspace ?? process.cwd(),
2629
- interactive: false
2630
- });
2631
- }
2632
-
2633
3135
  // src/commands/repl.ts
2634
3136
  var import_node_readline2 = require("node:readline");
2635
3137
  init_config();
@@ -2642,10 +3144,13 @@ async function replCommand(options) {
2642
3144
  writeLine(dim(` Model: ${config.lastModel || "not set"}`));
2643
3145
  writeLine(dim(` Provider: ${config.activeProvider}`));
2644
3146
  writeLine(dim(` Workspace: ${workspaceDir}`));
2645
- writeLine(dim(` Type 'exit' or Ctrl+C to quit`));
3147
+ writeLine(dim(` Type '/help' for commands, '/exit' or Ctrl+C to quit`));
2646
3148
  writeLine(dim("\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"));
2647
3149
  writeLine("");
2648
3150
  let conversationMessages;
3151
+ let thinkingEnabled = true;
3152
+ let abortController = new AbortController();
3153
+ let agentRunning = false;
2649
3154
  const rl = (0, import_node_readline2.createInterface)({
2650
3155
  input: process.stdin,
2651
3156
  output: process.stdout,
@@ -2659,28 +3164,41 @@ async function replCommand(options) {
2659
3164
  rl.prompt();
2660
3165
  return;
2661
3166
  }
2662
- if (trimmed === "exit" || trimmed === "quit") {
3167
+ if (agentRunning) {
3168
+ rl.prompt();
3169
+ return;
3170
+ }
3171
+ if (trimmed === "/exit" || trimmed === "/quit") {
2663
3172
  rl.close();
2664
3173
  return;
2665
3174
  }
2666
- if (trimmed === "clear") {
3175
+ if (trimmed === "/clear") {
2667
3176
  console.clear();
2668
3177
  rl.prompt();
2669
3178
  return;
2670
3179
  }
2671
- if (trimmed === "help") {
3180
+ if (trimmed === "/new") {
3181
+ conversationMessages = void 0;
3182
+ abortController = new AbortController();
3183
+ writeLine(info("Started a new session. Context has been cleared."));
3184
+ rl.prompt();
3185
+ return;
3186
+ }
3187
+ if (trimmed === "/help") {
2672
3188
  writeLine(bold("\nREPL Commands:"));
2673
- writeLine(" <prompt> Ask the agent anything");
2674
- writeLine(" exit / quit Exit REPL");
2675
- writeLine(" clear Clear screen");
2676
- writeLine(" help Show this help");
2677
- writeLine(" model <id> Switch model");
3189
+ writeLine(" <prompt> Ask the agent anything");
3190
+ writeLine(" /exit, /quit Exit REPL");
3191
+ writeLine(" /clear Clear screen");
3192
+ writeLine(" /new Clear context, start a new session");
3193
+ writeLine(" /help Show this help");
3194
+ writeLine(" /model <id> Switch model");
3195
+ writeLine(` /thinking <on|off> Toggle deep thinking (currently: ${thinkingEnabled ? "on" : "off"})`);
2678
3196
  writeLine("");
2679
3197
  rl.prompt();
2680
3198
  return;
2681
3199
  }
2682
- if (trimmed.startsWith("model ")) {
2683
- const modelId = trimmed.slice(6).trim();
3200
+ if (trimmed.startsWith("/model ")) {
3201
+ const modelId = trimmed.slice(7).trim();
2684
3202
  if (modelId) {
2685
3203
  config.lastModel = modelId;
2686
3204
  const { saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -2690,109 +3208,52 @@ async function replCommand(options) {
2690
3208
  rl.prompt();
2691
3209
  return;
2692
3210
  }
2693
- rl.pause();
3211
+ if (trimmed.startsWith("/thinking ")) {
3212
+ const value = trimmed.slice(10).trim().toLowerCase();
3213
+ if (value === "on" || value === "true") {
3214
+ thinkingEnabled = true;
3215
+ writeLine(info("Deep thinking: on"));
3216
+ } else if (value === "off" || value === "false") {
3217
+ thinkingEnabled = false;
3218
+ writeLine(info("Deep thinking: off"));
3219
+ } else {
3220
+ writeLine(error(`Usage: /thinking <on|off> (currently: ${thinkingEnabled ? "on" : "off"})`));
3221
+ }
3222
+ rl.prompt();
3223
+ return;
3224
+ }
3225
+ agentRunning = true;
2694
3226
  try {
2695
3227
  conversationMessages = await runAgentSession(trimmed, {
2696
3228
  agentMode: "agent",
2697
3229
  workspaceDir,
2698
3230
  interactive: true,
2699
- model: options.model,
2700
- provider: options.provider,
2701
- existingMessages: conversationMessages
3231
+ thinking: thinkingEnabled,
3232
+ existingMessages: conversationMessages,
3233
+ signal: abortController.signal
2702
3234
  });
2703
3235
  } catch (err) {
2704
3236
  const message = err instanceof Error ? err.message : String(err);
2705
3237
  writeError(error(`Error: ${message}`));
2706
3238
  }
3239
+ agentRunning = false;
3240
+ abortController = new AbortController();
2707
3241
  writeLine("");
2708
3242
  rl.prompt();
2709
- rl.resume();
2710
3243
  });
2711
3244
  rl.on("close", () => {
3245
+ killAllShells();
2712
3246
  writeLine(dim("\nGoodbye! \u{1F44B}"));
2713
3247
  process.exit(0);
2714
3248
  });
2715
3249
  rl.on("SIGINT", () => {
2716
- rl.close();
2717
- });
2718
- }
2719
-
2720
- // src/commands/init.ts
2721
- init_config();
2722
- var import_node_readline3 = require("node:readline");
2723
- async function initCommand() {
2724
- writeLine(bold("\nCoder CLI \u2014 Initialization"));
2725
- writeLine(dim("\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"));
2726
- writeLine(`Config directory: ${getConfigDirPath()}`);
2727
- writeLine("");
2728
- const config = loadConfig();
2729
- writeLine(info("Select your AI provider:"));
2730
- const providers = ["deepseek", "glm", "agnes", "nvidia", "minimax", "custom"];
2731
- for (let i = 0; i < providers.length; i++) {
2732
- writeLine(` ${i + 1}. ${providers[i]}`);
2733
- }
2734
- writeLine("");
2735
- const providerIndex = await askQuestion("Provider number [1]: ");
2736
- const providerChoice = parseInt(providerIndex || "1", 10);
2737
- const selectedProvider = providers[Math.max(0, Math.min(providerChoice - 1, providers.length - 1))];
2738
- config.activeProvider = selectedProvider;
2739
- writeLine(` Selected: ${bold(selectedProvider)}`);
2740
- writeLine("");
2741
- const providerSettings = config.providers[selectedProvider];
2742
- const envVar = providerSettings.apiKeyEnvVar;
2743
- writeLine(info(`API Key Configuration for "${selectedProvider}":`));
2744
- writeLine(` You can set the ${bold(envVar)} environment variable,`);
2745
- writeLine(` or enter the key directly (stored in config file).`);
2746
- writeLine("");
2747
- const source = await askQuestion("Use environment variable? [Y/n]: ");
2748
- const useEnv = source.toLowerCase() !== "n";
2749
- if (useEnv) {
2750
- providerSettings.apiKeySource = "env";
2751
- writeLine(` Using ${bold(envVar)} environment variable.`);
2752
- writeLine(` Make sure to set it: export ${envVar}=<your-api-key>`);
2753
- } else {
2754
- providerSettings.apiKeySource = "manual";
2755
- const key = await askQuestion("Enter API key: ");
2756
- if (key.trim()) {
2757
- providerSettings.apiKey = key.trim();
2758
- writeLine(" API key saved to config.");
3250
+ if (agentRunning) {
3251
+ abortController.abort();
3252
+ writeLine("");
3253
+ writeLine(warning("\u26A0 Cancelled"));
2759
3254
  } else {
2760
- writeLine(` ${error("No key entered. Set it later with: coder config")}`);
2761
- }
2762
- }
2763
- writeLine("");
2764
- if (selectedProvider === "custom") {
2765
- const baseUrl = await askQuestion("Custom API base URL: ");
2766
- if (baseUrl.trim()) {
2767
- providerSettings.customBaseUrl = baseUrl.trim();
2768
- }
2769
- } else {
2770
- const customUrl = await askQuestion(`Custom base URL (leave empty for default): `);
2771
- if (customUrl.trim()) {
2772
- providerSettings.customBaseUrl = customUrl.trim();
2773
- }
2774
- writeLine("");
2775
- }
2776
- saveConfig(config);
2777
- writeLine(success("\n\u2713 Configuration saved!"));
2778
- writeLine(` Config file: ${getConfigFilePathExplicit()}`);
2779
- writeLine("");
2780
- writeLine(info("Quick start:"));
2781
- writeLine(' coder "What is in this directory?"');
2782
- writeLine(' coder ask "Explain this code"');
2783
- writeLine(" coder repl");
2784
- writeLine("");
2785
- }
2786
- function askQuestion(query) {
2787
- const rl = (0, import_node_readline3.createInterface)({
2788
- input: process.stdin,
2789
- output: process.stderr
2790
- });
2791
- return new Promise((resolve12) => {
2792
- rl.question(query, (answer) => {
2793
3255
  rl.close();
2794
- resolve12(answer);
2795
- });
3256
+ }
2796
3257
  });
2797
3258
  }
2798
3259
 
@@ -2820,7 +3281,8 @@ function showConfig(config) {
2820
3281
  writeLine(bold("Active Settings:"));
2821
3282
  writeLine(` Active provider: ${config.activeProvider}`);
2822
3283
  writeLine(` Last model: ${config.lastModel}`);
2823
- writeLine(` Show usage: ${config.showUsage}`);
3284
+ const tavilyKey = resolveTavilyApiKey(config);
3285
+ writeLine(` Web search: ${tavilyKey ? "configured" : "not configured (set via coder config tavilyApiKey <key> or TAVILY_API_KEY env var)"}`);
2824
3286
  writeLine("");
2825
3287
  for (const providerId of PROVIDER_IDS2) {
2826
3288
  const p = config.providers[providerId];
@@ -2829,13 +3291,38 @@ function showConfig(config) {
2829
3291
  writeLine(` API Key Env Var: ${p.apiKeyEnvVar}`);
2830
3292
  writeLine(` API Key (stored): ${p.apiKey ? "***" : "(not set)"}`);
2831
3293
  writeLine(` Custom Base URL: ${p.customBaseUrl || "(default)"}`);
3294
+ if (providerId === "custom") {
3295
+ writeLine(` Deep Thinking: ${p.supportsThinking ? "supported" : "not declared"}`);
3296
+ if (p.thinkingEnabledParams) {
3297
+ writeLine(` Thinking (on): ${JSON.stringify(p.thinkingEnabledParams)}`);
3298
+ }
3299
+ if (p.thinkingDisabledParams) {
3300
+ writeLine(` Thinking (off): ${JSON.stringify(p.thinkingDisabledParams)}`);
3301
+ }
3302
+ }
2832
3303
  writeLine("");
2833
3304
  }
2834
3305
  writeLine(dim("To change a value: coder config <key> <value>"));
2835
- writeLine(dim("Examples:"));
2836
- writeLine(dim(' coder config activeProvider "deepseek"'));
2837
- writeLine(dim(' coder config lastModel "deepseek-v4-flash"'));
2838
- writeLine(dim(' coder config providers.deepseek.apiKeySource "env"'));
3306
+ writeLine(dim(""));
3307
+ writeLine(dim("Quick start \u2014 custom provider:"));
3308
+ writeLine(dim(" coder config activeProvider custom"));
3309
+ writeLine(dim(' coder config providers.custom.customBaseUrl "https://api.openai.com/v1"'));
3310
+ writeLine(dim(" coder config providers.custom.supportsThinking true"));
3311
+ writeLine(dim(' coder config lastModel "gpt-4o"'));
3312
+ writeLine(dim(""));
3313
+ writeLine(dim(" # Way 1: env var (recommended)"));
3314
+ writeLine(dim(" coder config providers.custom.apiKeySource env"));
3315
+ writeLine(dim(' coder config providers.custom.apiKeyEnvVar "OPENAI_API_KEY"'));
3316
+ writeLine(dim(" export OPENAI_API_KEY=sk-xxx"));
3317
+ writeLine(dim(""));
3318
+ writeLine(dim(" # Way 2: stored in config"));
3319
+ writeLine(dim(" coder config providers.custom.apiKeySource manual"));
3320
+ writeLine(dim(' coder config providers.custom.apiKey "sk-xxx"'));
3321
+ writeLine(dim(""));
3322
+ writeLine(dim("View a value:"));
3323
+ writeLine(dim(" coder config activeProvider"));
3324
+ writeLine(dim(" coder config providers.deepseek.customBaseUrl"));
3325
+ writeLine(dim(" coder config providers.custom.thinkingEnabledParams"));
2839
3326
  writeLine("");
2840
3327
  }
2841
3328
  function showConfigValue(config, key) {
@@ -2847,6 +3334,16 @@ function showConfigValue(config, key) {
2847
3334
  writeLine(String(value));
2848
3335
  }
2849
3336
  async function setConfigValue(config, key, value) {
3337
+ if (key === "activeProvider" && !PROVIDER_IDS2.includes(value)) {
3338
+ writeLine(error(`Invalid provider "${value}". Valid providers: ${PROVIDER_IDS2.join(", ")}`));
3339
+ return;
3340
+ }
3341
+ if (key.endsWith(".apiKeySource") || key === "apiKeySource") {
3342
+ if (value !== "env" && value !== "manual") {
3343
+ writeLine(error(`Invalid apiKeySource "${value}". Must be "env" or "manual".`));
3344
+ return;
3345
+ }
3346
+ }
2850
3347
  setNestedValue(config, key, parseValue(value));
2851
3348
  saveConfig(config);
2852
3349
  writeLine(info(`Config updated: ${key} = ${value}`));
@@ -2888,42 +3385,30 @@ function setGlobalOptions(opts) {
2888
3385
  }
2889
3386
 
2890
3387
  // src/index.ts
2891
- var import_node_fs14 = require("node:fs");
2892
- var import_node_path15 = require("node:path");
2893
- var import_node_url = require("node:url");
2894
- var import_meta = {};
2895
- var __dirname = (0, import_node_path15.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
2896
- var pkg = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path15.join)(__dirname, "../package.json"), "utf-8"));
2897
- var VERSION = pkg.version;
3388
+ var VERSION = "0.1.108";
2898
3389
  async function main() {
2899
3390
  const rawArgs = process.argv.slice(2);
2900
3391
  if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
2901
3392
  printHelp();
2902
3393
  return;
2903
3394
  }
2904
- if (rawArgs.includes("--version") || rawArgs.includes("-V")) {
3395
+ if (rawArgs.includes("--version") || rawArgs.includes("-v")) {
2905
3396
  console.log(VERSION);
2906
3397
  return;
2907
3398
  }
3399
+ const globalOpts = extractGlobalOptions(rawArgs);
3400
+ setGlobalOptions(globalOpts);
2908
3401
  if (rawArgs.length === 0) {
2909
3402
  await replCommand({});
2910
3403
  return;
2911
3404
  }
2912
- const globalOpts = extractGlobalOptions(rawArgs);
2913
- setGlobalOptions(globalOpts);
2914
3405
  const firstNonFlag = rawArgs.find((a) => !a.startsWith("-"));
2915
3406
  const knownSubcommands = {
2916
3407
  ask: async (args, opts) => {
2917
3408
  await askCommand(args.join(" "), opts);
2918
3409
  },
2919
- plan: async (args, opts) => {
2920
- await planCommand(args.join(" "), opts);
2921
- },
2922
- repl: async () => {
2923
- await replCommand(globalOpts);
2924
- },
2925
- init: async () => {
2926
- await initCommand();
3410
+ run: async (args, opts) => {
3411
+ await runCommand(args.join(" "), opts);
2927
3412
  },
2928
3413
  config: async (args) => {
2929
3414
  await configCommand(args[0], args[1]);
@@ -2934,54 +3419,47 @@ async function main() {
2934
3419
  await knownSubcommands[firstNonFlag](cmdArgs, globalOpts);
2935
3420
  return;
2936
3421
  }
2937
- const promptParts = rawArgs.filter((a) => !a.startsWith("-"));
2938
- const prompt = promptParts.join(" ");
2939
- if (!prompt.trim()) {
2940
- await replCommand(globalOpts);
2941
- return;
2942
- }
2943
- await runCommand(prompt, globalOpts);
3422
+ writeLine(error(`Unknown subcommand "${firstNonFlag}".`));
3423
+ writeLine("");
3424
+ writeLine(info("Usage: coder <command> [options] [prompt...]"));
3425
+ writeLine(info("Run 'coder --help' for available commands."));
3426
+ process.exit(1);
2944
3427
  }
2945
3428
  function extractGlobalOptions(args) {
2946
3429
  const opts = {};
2947
3430
  for (let i = 0; i < args.length; i++) {
2948
3431
  const arg = args[i];
2949
- if (arg === "-m" || arg === "--model") {
2950
- opts.model = args[++i] ?? "";
2951
- } else if (arg === "-p" || arg === "--provider") {
2952
- opts.provider = args[++i] ?? "";
2953
- } else if (arg === "-w" || arg === "--workspace") {
3432
+ if (arg === "-w" || arg === "--workspace") {
2954
3433
  opts.workspace = args[++i] ?? "";
2955
- } else if (arg === "-y" || arg === "--yes") {
2956
- opts.yes = true;
2957
3434
  } else if (arg === "--no-stream") {
2958
3435
  opts.stream = false;
2959
3436
  } else if (arg === "--stream") {
2960
3437
  opts.stream = true;
3438
+ } else if (arg === "--thinking") {
3439
+ opts.thinking = true;
3440
+ } else if (arg === "--no-thinking") {
3441
+ opts.thinking = false;
2961
3442
  }
2962
3443
  }
2963
3444
  return opts;
2964
3445
  }
2965
3446
  function printHelp() {
2966
3447
  console.log(`
2967
- Usage: coder [options] [prompt...]
3448
+ Usage: coder <command> [options] [prompt...]
2968
3449
 
2969
3450
  Coder CLI \u2014 AI-powered coding assistant in the terminal
2970
3451
 
2971
3452
  Options:
2972
- -V, --version output the version number
2973
- -m, --model <model> Model ID to use
2974
- -p, --provider <provider> Provider ID (deepseek, glm, agnes, nvidia, minimax, custom)
3453
+ -v, --version output the version number
2975
3454
  -w, --workspace <path> Workspace directory
2976
- -y, --yes Auto-confirm prompts (unattended mode)
2977
- --no-stream Disable streaming output
3455
+ --no-stream Disable streaming output
3456
+ --thinking Enable deep thinking (for supported models)
3457
+ --no-thinking Disable deep thinking
2978
3458
  -h, --help display help for command
2979
3459
 
2980
3460
  Commands:
2981
3461
  ask [prompt...] Ask a question (read-only mode)
2982
- plan [prompt...] Create or work on a plan
2983
- repl Start interactive REPL session
2984
- init Initialize Coder CLI configuration
3462
+ run [prompt...] Run in full agent mode (can modify files, run commands)
2985
3463
  config [key] [value] View or edit configuration
2986
3464
  `);
2987
3465
  }