@alanwchat/coder 0.1.108 → 0.1.112

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 DELETED
@@ -1,3471 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Coder CLI — AI-powered coding assistant
4
- * Bundled with esbuild
5
- */
6
-
7
- "use strict";
8
- var __defProp = Object.defineProperty;
9
- var __getOwnPropNames = Object.getOwnPropertyNames;
10
- var __esm = (fn, res) => function __init() {
11
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
- };
13
- var __export = (target, all) => {
14
- for (var name in all)
15
- __defProp(target, name, { get: all[name], enumerable: true });
16
- };
17
-
18
- // src/config/index.ts
19
- var config_exports = {};
20
- __export(config_exports, {
21
- getConfigDirPath: () => getConfigDirPath,
22
- getConfigFilePathExplicit: () => getConfigFilePathExplicit,
23
- loadConfig: () => loadConfig,
24
- resolveApiKey: () => resolveApiKey,
25
- resolveProviderConfig: () => resolveProviderConfig,
26
- resolveTavilyApiKey: () => resolveTavilyApiKey,
27
- saveConfig: () => saveConfig
28
- });
29
- function getConfigDir() {
30
- const home = (0, import_node_os.homedir)();
31
- return (0, import_node_path10.join)(home, ".coder", "cli");
32
- }
33
- function createDefaultProviderSettings(provider) {
34
- const isCustom = provider === "custom";
35
- return {
36
- apiKeySource: "env",
37
- apiKey: "",
38
- apiKeyEnvVar: isCustom ? "CUSTOM_API_KEY" : PRESET_PROVIDERS[provider]?.defaultApiKeyEnvVar ?? "API_KEY",
39
- customBaseUrl: isCustom ? "https://api.example.com/v1" : ""
40
- };
41
- }
42
- function createDefaultConfig() {
43
- const providers = {};
44
- for (const id of PROVIDER_IDS) {
45
- providers[id] = createDefaultProviderSettings(id);
46
- }
47
- return {
48
- activeProvider: "deepseek",
49
- providers,
50
- lastModel: "deepseek-v4-flash",
51
- tavilyApiKey: ""
52
- };
53
- }
54
- function getConfigFilePath() {
55
- return (0, import_node_path10.join)(getConfigDir(), CONFIG_FILE);
56
- }
57
- function ensureConfigDir() {
58
- const dir = getConfigDir();
59
- if (!(0, import_node_fs9.existsSync)(dir)) {
60
- (0, import_node_fs9.mkdirSync)(dir, { recursive: true });
61
- }
62
- }
63
- function loadConfig() {
64
- const configPath = getConfigFilePath();
65
- if (!(0, import_node_fs9.existsSync)(configPath)) {
66
- const defaults = createDefaultConfig();
67
- saveConfig(defaults);
68
- return defaults;
69
- }
70
- try {
71
- const raw = (0, import_node_fs9.readFileSync)(configPath, "utf-8");
72
- const parsed = JSON.parse(raw);
73
- return mergeWithDefaults(parsed);
74
- } catch {
75
- const defaults = createDefaultConfig();
76
- saveConfig(defaults);
77
- return defaults;
78
- }
79
- }
80
- function mergeWithDefaults(raw) {
81
- const defaults = createDefaultConfig();
82
- return {
83
- activeProvider: PROVIDER_IDS.includes(String(raw.activeProvider ?? "")) ? raw.activeProvider : defaults.activeProvider,
84
- providers: mergeProviders(raw.providers, defaults.providers),
85
- lastModel: typeof raw.lastModel === "string" ? raw.lastModel : defaults.lastModel,
86
- tavilyApiKey: typeof raw.tavilyApiKey === "string" ? raw.tavilyApiKey : defaults.tavilyApiKey
87
- };
88
- }
89
- function isPlainObject(value) {
90
- return value !== null && typeof value === "object" && !Array.isArray(value);
91
- }
92
- function mergeProviders(raw, defaults) {
93
- const result = { ...defaults };
94
- if (!raw || typeof raw !== "object") {
95
- return result;
96
- }
97
- for (const id of PROVIDER_IDS) {
98
- const p = raw[id];
99
- if (p && typeof p === "object") {
100
- result[id] = {
101
- apiKeySource: p.apiKeySource === "manual" ? "manual" : "env",
102
- apiKey: typeof p.apiKey === "string" ? p.apiKey : result[id].apiKey,
103
- apiKeyEnvVar: typeof p.apiKeyEnvVar === "string" ? p.apiKeyEnvVar : result[id].apiKeyEnvVar,
104
- customBaseUrl: typeof p.customBaseUrl === "string" ? p.customBaseUrl : result[id].customBaseUrl,
105
- ...typeof p.supportsThinking === "boolean" ? { supportsThinking: p.supportsThinking } : {},
106
- ...isPlainObject(p.thinkingEnabledParams) ? { thinkingEnabledParams: p.thinkingEnabledParams } : {},
107
- ...isPlainObject(p.thinkingDisabledParams) ? { thinkingDisabledParams: p.thinkingDisabledParams } : {}
108
- };
109
- }
110
- }
111
- return result;
112
- }
113
- function saveConfig(config) {
114
- ensureConfigDir();
115
- const configPath = getConfigFilePath();
116
- (0, import_node_fs9.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
117
- }
118
- function resolveProviderConfig(config, providerId) {
119
- const provider = providerId ?? config.activeProvider;
120
- const settings = config.providers[provider];
121
- if (provider === "custom") {
122
- if (!settings.customBaseUrl.trim()) {
123
- throw new Error(
124
- "Custom provider selected but no base URL configured. Run `coder config` or manually edit the config file."
125
- );
126
- }
127
- return {
128
- provider,
129
- baseUrl: settings.customBaseUrl.trim(),
130
- apiKeySource: settings.apiKeySource,
131
- apiKey: settings.apiKey,
132
- apiKeyEnvVar: settings.apiKeyEnvVar.trim(),
133
- models: []
134
- };
135
- }
136
- const preset = PRESET_PROVIDERS[provider];
137
- if (!preset) {
138
- throw new Error(`Unknown provider: ${provider}`);
139
- }
140
- return {
141
- provider,
142
- baseUrl: settings.customBaseUrl.trim() || preset.baseUrl,
143
- apiKeySource: settings.apiKeySource,
144
- apiKey: settings.apiKey,
145
- apiKeyEnvVar: settings.apiKeyEnvVar.trim() || preset.defaultApiKeyEnvVar,
146
- models: settings.customBaseUrl.trim() ? [] : PRESET_MODELS[provider] ?? []
147
- };
148
- }
149
- function resolveApiKey(resolved) {
150
- if (resolved.apiKeySource === "manual") {
151
- if (!resolved.apiKey.trim()) {
152
- throw new Error(
153
- `API key for ${resolved.provider} is not configured. Set it with: coder config providers.<provider>.apiKey <key>`
154
- );
155
- }
156
- return resolved.apiKey.trim();
157
- }
158
- const envVar = resolved.apiKeyEnvVar;
159
- const envValue = process.env[envVar]?.trim();
160
- if (!envValue) {
161
- throw new Error(
162
- `Environment variable ${envVar} is not set. Set it with: export ${envVar}=<your-api-key>`
163
- );
164
- }
165
- return envValue;
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
- }
173
- function getConfigDirPath() {
174
- return getConfigDir();
175
- }
176
- function getConfigFilePathExplicit() {
177
- return getConfigFilePath();
178
- }
179
- var import_node_os, import_node_path10, import_node_fs9, PRESET_PROVIDERS, PRESET_MODELS, PROVIDER_IDS, CONFIG_FILE;
180
- var init_config = __esm({
181
- "src/config/index.ts"() {
182
- "use strict";
183
- import_node_os = require("node:os");
184
- import_node_path10 = require("node:path");
185
- import_node_fs9 = require("node:fs");
186
- PRESET_PROVIDERS = {
187
- deepseek: { baseUrl: "https://api.deepseek.com", defaultApiKeyEnvVar: "DEEPSEEK_API_KEY" },
188
- glm: { baseUrl: "https://open.bigmodel.cn/api/paas/v4", defaultApiKeyEnvVar: "GLM_API_KEY" },
189
- agnes: { baseUrl: "https://api.agnesai.com", defaultApiKeyEnvVar: "AGNES_API_KEY" },
190
- nvidia: { baseUrl: "https://integrate.api.nvidia.com/v1", defaultApiKeyEnvVar: "NVIDIA_API_KEY" },
191
- minimax: { baseUrl: "https://api.minimax.chat/v1", defaultApiKeyEnvVar: "MINIMAX_API_KEY" }
192
- };
193
- PRESET_MODELS = {
194
- deepseek: [
195
- { id: "deepseek-v4-flash", label: "DeepSeek V4 Flash", contextWindow: 1e6, supportsThinking: true, supportsMultimodal: false },
196
- { id: "deepseek-chat", label: "DeepSeek Chat", contextWindow: 1e6, supportsThinking: false, supportsMultimodal: true }
197
- ],
198
- glm: [
199
- { id: "glm-4", label: "GLM-4", contextWindow: 128e3, supportsThinking: false, supportsMultimodal: true },
200
- { id: "glm-4-plus", label: "GLM-4 Plus", contextWindow: 128e3, supportsThinking: true, supportsMultimodal: true }
201
- ],
202
- agnes: [
203
- { id: "agnes-v3", label: "Agnes V3", contextWindow: 128e3, supportsThinking: false, supportsMultimodal: true }
204
- ],
205
- nvidia: [],
206
- minimax: [
207
- { id: "minimax-m1", label: "MiniMax M1", contextWindow: 2e5, supportsThinking: true, supportsMultimodal: false }
208
- ]
209
- };
210
- PROVIDER_IDS = ["deepseek", "glm", "agnes", "nvidia", "minimax", "custom"];
211
- CONFIG_FILE = "settings.json";
212
- }
213
- });
214
-
215
- // src/handlers/list-dir.ts
216
- var import_node_fs = require("node:fs");
217
- var import_node_path = require("node:path");
218
-
219
- // src/handlers/result.ts
220
- function toolSuccess(tool, data) {
221
- return { ok: true, tool, data };
222
- }
223
- function toolFailure(tool, code, message) {
224
- return {
225
- ok: false,
226
- tool,
227
- error: { code, message }
228
- };
229
- }
230
-
231
- // src/handlers/list-dir.ts
232
- var listDirHandler = async (rawArgs, context) => {
233
- const args = rawArgs;
234
- if (!context.workspaceDir) {
235
- return toolFailure("list_dir", "workspace_required", "No workspace directory set");
236
- }
237
- const dirPath = (0, import_node_path.resolve)(context.workspaceDir, args.path);
238
- const maxDepth = args.recursive ? args.max_depth ?? 1 : 1;
239
- try {
240
- const entries = walkDirectory(dirPath, 0, maxDepth, args.show_hidden ?? false);
241
- return toolSuccess("list_dir", {
242
- path: (0, import_node_path.relative)(context.workspaceDir, dirPath),
243
- entries
244
- });
245
- } catch (error2) {
246
- const message = error2 instanceof Error ? error2.message : String(error2);
247
- return toolFailure("list_dir", "read_error", message);
248
- }
249
- };
250
- function walkDirectory(dirPath, currentDepth, maxDepth, showHidden) {
251
- const results = [];
252
- let entries;
253
- try {
254
- entries = (0, import_node_fs.readdirSync)(dirPath);
255
- } catch {
256
- return results;
257
- }
258
- for (const name of entries) {
259
- if (!showHidden && name.startsWith(".")) {
260
- continue;
261
- }
262
- const fullPath = (0, import_node_path.resolve)(dirPath, name);
263
- let stats;
264
- try {
265
- stats = (0, import_node_fs.statSync)(fullPath);
266
- } catch {
267
- continue;
268
- }
269
- results.push({
270
- name,
271
- path: fullPath,
272
- isDir: stats.isDirectory(),
273
- size: stats.isFile() ? stats.size : void 0
274
- });
275
- if (stats.isDirectory() && currentDepth < maxDepth) {
276
- results.push(...walkDirectory(fullPath, currentDepth + 1, maxDepth, showHidden));
277
- }
278
- }
279
- return results;
280
- }
281
-
282
- // src/handlers/read-file.ts
283
- var import_node_fs2 = require("node:fs");
284
- var import_node_path2 = require("node:path");
285
- var readFileHandler = async (rawArgs, context) => {
286
- const args = rawArgs;
287
- if (!context.workspaceDir) {
288
- return toolFailure("read_file", "workspace_required", "No workspace directory set");
289
- }
290
- const filePath = (0, import_node_path2.resolve)(context.workspaceDir, args.path);
291
- const startLine = args.start_line ?? 1;
292
- const maxLines = args.max_lines ?? 500;
293
- try {
294
- (0, import_node_fs2.statSync)(filePath);
295
- const content = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
296
- const lines = content.split("\n");
297
- const totalLines = lines.length;
298
- const endLine = Math.min(startLine + maxLines - 1, totalLines);
299
- const paginatedContent = lines.slice(startLine - 1, endLine).join("\n");
300
- return toolSuccess("read_file", {
301
- path: filePath,
302
- encoding: "utf-8",
303
- mimeType: "text/plain",
304
- content: paginatedContent,
305
- totalLines,
306
- startLine,
307
- endLine,
308
- truncated: endLine < totalLines
309
- });
310
- } catch (error2) {
311
- const message = error2 instanceof Error ? error2.message : String(error2);
312
- if (message.includes("ENOENT")) {
313
- return toolFailure("read_file", "not_found", `File not found: ${filePath}`);
314
- }
315
- return toolFailure("read_file", "read_error", message);
316
- }
317
- };
318
-
319
- // src/handlers/write-file.ts
320
- var import_node_fs3 = require("node:fs");
321
- var import_node_path3 = require("node:path");
322
- var writeFileHandler = async (rawArgs, context) => {
323
- const args = rawArgs;
324
- if (!context.workspaceDir) {
325
- return toolFailure("write_file", "workspace_required", "No workspace directory set");
326
- }
327
- const filePath = (0, import_node_path3.resolve)(context.workspaceDir, args.path);
328
- if (!filePath.startsWith(context.workspaceDir.replace(/\\/g, "/").replace(/\\/g, "/"))) {
329
- return toolFailure("write_file", "path_escape", "Path escapes workspace directory");
330
- }
331
- if ((0, import_node_fs3.existsSync)(filePath)) {
332
- return toolFailure("write_file", "file_exists", `File already exists: ${args.path}. Use replace_file to overwrite.`);
333
- }
334
- try {
335
- if (args.create_parent_dirs !== false) {
336
- const parentDir = (0, import_node_path3.dirname)(filePath);
337
- if (!(0, import_node_fs3.existsSync)(parentDir)) {
338
- (0, import_node_fs3.mkdirSync)(parentDir, { recursive: true });
339
- }
340
- }
341
- const lines = args.content.split("\n");
342
- (0, import_node_fs3.writeFileSync)(filePath, args.content, "utf-8");
343
- return toolSuccess("write_file", {
344
- path: args.path,
345
- action: "created",
346
- bytesWritten: Buffer.byteLength(args.content, "utf-8"),
347
- linesAdded: lines.length,
348
- linesRemoved: 0
349
- });
350
- } catch (error2) {
351
- const message = error2 instanceof Error ? error2.message : String(error2);
352
- return toolFailure("write_file", "write_error", message);
353
- }
354
- };
355
-
356
- // src/handlers/replace-file.ts
357
- var import_node_fs4 = require("node:fs");
358
- var import_node_path4 = require("node:path");
359
- var import_node_crypto = require("node:crypto");
360
- var replaceFileHandler = async (rawArgs, context) => {
361
- const args = rawArgs;
362
- if (!context.workspaceDir) {
363
- return toolFailure("replace_file", "workspace_required", "No workspace directory set");
364
- }
365
- const filePath = (0, import_node_path4.resolve)(context.workspaceDir, args.path);
366
- if (!(0, import_node_fs4.existsSync)(filePath)) {
367
- return toolFailure("replace_file", "not_found", `File not found: ${args.path}`);
368
- }
369
- try {
370
- const oldContent = (0, import_node_fs4.readFileSync)(filePath, "utf-8");
371
- const oldLines = oldContent.split("\n");
372
- const newLines = args.content.split("\n");
373
- if (args.expected_sha256) {
374
- const actualHash = (0, import_node_crypto.createHash)("sha256").update(oldContent).digest("hex");
375
- if (actualHash !== args.expected_sha256) {
376
- return toolFailure(
377
- "replace_file",
378
- "content_changed",
379
- `File content changed since last read. Expected SHA256: ${args.expected_sha256}, got: ${actualHash}. Read the file again to get the latest content.`
380
- );
381
- }
382
- }
383
- (0, import_node_fs4.writeFileSync)(filePath, args.content, "utf-8");
384
- return toolSuccess("replace_file", {
385
- path: args.path,
386
- action: "replaced",
387
- bytesWritten: Buffer.byteLength(args.content, "utf-8"),
388
- linesAdded: newLines.length,
389
- linesRemoved: oldLines.length
390
- });
391
- } catch (error2) {
392
- const message = error2 instanceof Error ? error2.message : String(error2);
393
- return toolFailure("replace_file", "write_error", message);
394
- }
395
- };
396
-
397
- // src/handlers/edit-file.ts
398
- var import_node_fs5 = require("node:fs");
399
- var import_node_path5 = require("node:path");
400
- var import_node_crypto2 = require("node:crypto");
401
- var editFileHandler = async (rawArgs, context) => {
402
- const args = rawArgs;
403
- if (!context.workspaceDir) {
404
- return toolFailure("edit_file", "workspace_required", "No workspace directory set");
405
- }
406
- const filePath = (0, import_node_path5.resolve)(context.workspaceDir, args.path);
407
- if (!(0, import_node_fs5.existsSync)(filePath)) {
408
- return toolFailure("edit_file", "not_found", `File not found: ${args.path}`);
409
- }
410
- try {
411
- const oldContent = (0, import_node_fs5.readFileSync)(filePath, "utf-8");
412
- const oldLines = oldContent.split("\n");
413
- if (args.expected_sha256) {
414
- const actualHash = (0, import_node_crypto2.createHash)("sha256").update(oldContent).digest("hex");
415
- if (actualHash !== args.expected_sha256) {
416
- return toolFailure(
417
- "edit_file",
418
- "content_changed",
419
- `File content changed since last read. Expected SHA256: ${args.expected_sha256}, got: ${actualHash}.`
420
- );
421
- }
422
- }
423
- let newContent;
424
- if (args.replace_all) {
425
- newContent = oldContent.split(args.old_string).join(args.new_string);
426
- } else {
427
- const index = oldContent.indexOf(args.old_string);
428
- if (index === -1) {
429
- return toolFailure(
430
- "edit_file",
431
- "string_not_found",
432
- `Could not find the exact text to replace in ${args.path}. The text must match exactly.`
433
- );
434
- }
435
- newContent = oldContent.slice(0, index) + args.new_string + oldContent.slice(index + args.old_string.length);
436
- }
437
- const newLines = newContent.split("\n");
438
- (0, import_node_fs5.writeFileSync)(filePath, newContent, "utf-8");
439
- const linesAdded = newLines.length - oldLines.length;
440
- const linesRemoved = oldLines.length - newLines.length;
441
- return toolSuccess("edit_file", {
442
- path: args.path,
443
- action: "modified",
444
- bytesWritten: Buffer.byteLength(newContent, "utf-8"),
445
- linesAdded: linesAdded > 0 ? linesAdded : 0,
446
- linesRemoved: linesRemoved > 0 ? linesRemoved : 0
447
- });
448
- } catch (error2) {
449
- const message = error2 instanceof Error ? error2.message : String(error2);
450
- return toolFailure("edit_file", "write_error", message);
451
- }
452
- };
453
-
454
- // src/handlers/replace-lines.ts
455
- var import_node_fs6 = require("node:fs");
456
- var import_node_path6 = require("node:path");
457
- var import_node_crypto3 = require("node:crypto");
458
- var replaceLinesHandler = async (rawArgs, context) => {
459
- const args = rawArgs;
460
- if (!context.workspaceDir) {
461
- return toolFailure("replace_lines", "workspace_required", "No workspace directory set");
462
- }
463
- const filePath = (0, import_node_path6.resolve)(context.workspaceDir, args.path);
464
- if (!(0, import_node_fs6.existsSync)(filePath)) {
465
- return toolFailure("replace_lines", "not_found", `File not found: ${args.path}`);
466
- }
467
- try {
468
- const oldContent = (0, import_node_fs6.readFileSync)(filePath, "utf-8");
469
- const oldLines = oldContent.split("\n");
470
- if (args.expected_sha256) {
471
- const actualHash = (0, import_node_crypto3.createHash)("sha256").update(oldContent).digest("hex");
472
- if (actualHash !== args.expected_sha256) {
473
- return toolFailure(
474
- "replace_lines",
475
- "content_changed",
476
- `File content changed since last read. Expected SHA256: ${args.expected_sha256}, got: ${actualHash}.`
477
- );
478
- }
479
- }
480
- if (args.start_line < 1 || args.end_line < args.start_line || args.start_line > oldLines.length) {
481
- return toolFailure(
482
- "replace_lines",
483
- "invalid_range",
484
- `Invalid line range: ${args.start_line}-${args.end_line}. File has ${oldLines.length} lines.`
485
- );
486
- }
487
- const endLine = Math.min(args.end_line, oldLines.length);
488
- const replacementLines = args.content === "" ? [] : args.content.split("\n");
489
- const before = oldLines.slice(0, args.start_line - 1);
490
- const after = oldLines.slice(endLine);
491
- const newContent = [...before, ...replacementLines, ...after].join("\n");
492
- (0, import_node_fs6.writeFileSync)(filePath, newContent, "utf-8");
493
- return toolSuccess("replace_lines", {
494
- path: args.path,
495
- action: "modified",
496
- bytesWritten: Buffer.byteLength(newContent, "utf-8"),
497
- linesAdded: replacementLines.length,
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."
500
- });
501
- } catch (error2) {
502
- const message = error2 instanceof Error ? error2.message : String(error2);
503
- return toolFailure("replace_lines", "write_error", message);
504
- }
505
- };
506
-
507
- // src/handlers/glob.ts
508
- var import_node_fs7 = require("node:fs");
509
- var import_node_path7 = require("node:path");
510
- var globHandler = async (rawArgs, context) => {
511
- const args = rawArgs;
512
- if (!context.workspaceDir) {
513
- return toolFailure("glob", "workspace_required", "No workspace directory set");
514
- }
515
- const searchDir = args.target_directory ? (0, import_node_path7.resolve)(context.workspaceDir, args.target_directory) : context.workspaceDir;
516
- const headLimit = args.head_limit ?? 100;
517
- const pattern = args.glob_pattern;
518
- try {
519
- const matches = simpleGlob(searchDir, pattern, context.workspaceDir, headLimit);
520
- return toolSuccess("glob", {
521
- pattern,
522
- targetDirectory: (0, import_node_path7.relative)(context.workspaceDir, searchDir),
523
- matches: matches.slice(0, headLimit),
524
- totalMatches: matches.length,
525
- truncated: matches.length > headLimit
526
- });
527
- } catch (error2) {
528
- const message = error2 instanceof Error ? error2.message : String(error2);
529
- return toolFailure("glob", "search_error", message);
530
- }
531
- };
532
- function simpleGlob(rootDir, pattern, workspaceDir, limit) {
533
- const results = [];
534
- function walk(dir) {
535
- if (results.length >= limit) return;
536
- let entries;
537
- try {
538
- entries = (0, import_node_fs7.readdirSync)(dir);
539
- } catch {
540
- return;
541
- }
542
- for (const name of entries) {
543
- if (results.length >= limit) return;
544
- if (name.startsWith(".")) continue;
545
- const fullPath = (0, import_node_path7.resolve)(dir, name);
546
- const relPath = (0, import_node_path7.relative)(workspaceDir, fullPath).replace(/\\/g, "/");
547
- let stats;
548
- try {
549
- stats = (0, import_node_fs7.statSync)(fullPath);
550
- } catch {
551
- continue;
552
- }
553
- const regex = globToRegex(pattern);
554
- if (regex.test(relPath)) {
555
- results.push(relPath);
556
- }
557
- if (stats.isDirectory()) {
558
- walk(fullPath);
559
- }
560
- }
561
- }
562
- walk(rootDir);
563
- results.sort();
564
- return results;
565
- }
566
- function globToRegex(pattern) {
567
- let regexStr = "^";
568
- let i = 0;
569
- while (i < pattern.length) {
570
- const ch = pattern[i];
571
- if (ch === "*") {
572
- if (i + 1 < pattern.length && pattern[i + 1] === "*") {
573
- regexStr += ".*";
574
- i += 2;
575
- if (i < pattern.length && pattern[i] === "/") {
576
- regexStr += "/?";
577
- i++;
578
- }
579
- } else {
580
- regexStr += "[^/]*";
581
- i++;
582
- }
583
- } else if (ch === "?") {
584
- regexStr += "[^/]";
585
- i++;
586
- } else if (ch === "." || ch === "+" || ch === "(" || ch === ")" || ch === "[" || ch === "]" || ch === "{" || ch === "}" || ch === "\\" || ch === "^" || ch === "$" || ch === "|") {
587
- regexStr += "\\" + ch;
588
- i++;
589
- } else {
590
- regexStr += ch;
591
- i++;
592
- }
593
- }
594
- regexStr += "$";
595
- return new RegExp(regexStr, "i");
596
- }
597
-
598
- // src/handlers/grep.ts
599
- var import_node_fs8 = require("node:fs");
600
- var import_node_path8 = require("node:path");
601
- var grepHandler = async (rawArgs, context) => {
602
- const args = rawArgs;
603
- if (!context.workspaceDir) {
604
- return toolFailure("grep", "workspace_required", "No workspace directory set");
605
- }
606
- const searchPath = args.path ? (0, import_node_path8.resolve)(context.workspaceDir, args.path) : context.workspaceDir;
607
- const headLimit = args.head_limit ?? 200;
608
- const offset = args.offset ?? 0;
609
- const ctxBefore = args.context_before ?? args.context ?? 0;
610
- const ctxAfter = args.context_after ?? args.context ?? 0;
611
- const flags = args.case_insensitive ? "gi" : "g";
612
- const outputMode = args.output_mode ?? "content";
613
- const multiline = args.multiline ?? false;
614
- try {
615
- let searchRegex;
616
- try {
617
- const regexFlags = multiline ? `${flags}s` : flags;
618
- searchRegex = new RegExp(args.pattern, regexFlags);
619
- } catch {
620
- return toolFailure("grep", "invalid_pattern", `Invalid regex pattern: ${args.pattern}`);
621
- }
622
- const files = collectFiles(searchPath);
623
- const matches = [];
624
- const fileMatchSet = /* @__PURE__ */ new Set();
625
- const fileCountMap = {};
626
- let totalMatches = 0;
627
- let skippedFiles = 0;
628
- for (const file of files) {
629
- if (totalMatches >= headLimit + offset) break;
630
- let content;
631
- try {
632
- content = (0, import_node_fs8.readFileSync)(file, "utf-8");
633
- } catch {
634
- skippedFiles++;
635
- continue;
636
- }
637
- const relativePath = (0, import_node_path8.relative)(context.workspaceDir, file);
638
- let fileHasMatch = false;
639
- let fileMatchCount = 0;
640
- if (multiline) {
641
- const lines = content.split("\n");
642
- searchRegex.lastIndex = 0;
643
- const fullMatch = searchRegex.exec(content);
644
- if (fullMatch) {
645
- fileHasMatch = true;
646
- do {
647
- totalMatches++;
648
- fileMatchCount++;
649
- searchRegex.lastIndex = fullMatch.index + 1;
650
- } while (searchRegex.exec(content));
651
- searchRegex.lastIndex = 0;
652
- if (outputMode === "content") {
653
- matches.push({
654
- path: relativePath,
655
- lineNumber: 1,
656
- line: fullMatch[0].slice(0, 200)
657
- });
658
- }
659
- }
660
- } else {
661
- const lines = content.split("\n");
662
- for (let lineNum = 0; lineNum < lines.length; lineNum++) {
663
- searchRegex.lastIndex = 0;
664
- if (searchRegex.test(lines[lineNum])) {
665
- totalMatches++;
666
- fileMatchCount++;
667
- if (outputMode === "files_with_matches") {
668
- fileMatchSet.add(relativePath);
669
- fileHasMatch = true;
670
- continue;
671
- }
672
- if (outputMode === "count") {
673
- fileHasMatch = true;
674
- continue;
675
- }
676
- if (totalMatches > offset && totalMatches <= offset + headLimit) {
677
- const contextBeforeLines = ctxBefore > 0 ? lines.slice(Math.max(0, lineNum - ctxBefore), lineNum) : void 0;
678
- const contextAfterLines = ctxAfter > 0 ? lines.slice(lineNum + 1, lineNum + 1 + ctxAfter) : void 0;
679
- matches.push({
680
- path: relativePath,
681
- lineNumber: lineNum + 1,
682
- line: lines[lineNum],
683
- contextBefore: contextBeforeLines?.length ? contextBeforeLines : void 0,
684
- contextAfter: contextAfterLines?.length ? contextAfterLines : void 0
685
- });
686
- }
687
- }
688
- }
689
- }
690
- if (fileHasMatch || fileMatchCount > 0) {
691
- fileMatchSet.add(relativePath);
692
- fileCountMap[relativePath] = fileMatchCount;
693
- }
694
- if (outputMode === "files_with_matches" && fileMatchSet.size >= headLimit) {
695
- break;
696
- }
697
- }
698
- const result = {
699
- pattern: args.pattern,
700
- path: (0, import_node_path8.relative)(context.workspaceDir, searchPath),
701
- outputMode,
702
- totalMatches,
703
- truncated: totalMatches > headLimit
704
- };
705
- if (outputMode === "content") {
706
- result.matches = matches;
707
- } else if (outputMode === "files_with_matches") {
708
- result.files = [...fileMatchSet];
709
- } else if (outputMode === "count") {
710
- result.files = [...fileMatchSet];
711
- result.fileCounts = fileCountMap;
712
- }
713
- if (skippedFiles > 0) {
714
- result.skippedFiles = skippedFiles;
715
- }
716
- return toolSuccess("grep", result);
717
- } catch (error2) {
718
- const message = error2 instanceof Error ? error2.message : String(error2);
719
- return toolFailure("grep", "search_error", message);
720
- }
721
- };
722
- function collectFiles(dirPath) {
723
- try {
724
- const stats = (0, import_node_fs8.statSync)(dirPath);
725
- if (stats.isFile()) {
726
- return [dirPath];
727
- }
728
- } catch {
729
- return [];
730
- }
731
- const results = [];
732
- function walk(dir) {
733
- let entries;
734
- try {
735
- entries = (0, import_node_fs8.readdirSync)(dir);
736
- } catch {
737
- return;
738
- }
739
- for (const name of entries) {
740
- if (name === ".git" || name === "node_modules") continue;
741
- const fullPath = (0, import_node_path8.resolve)(dir, name);
742
- let stats;
743
- try {
744
- stats = (0, import_node_fs8.statSync)(fullPath);
745
- } catch {
746
- continue;
747
- }
748
- if (stats.isDirectory()) {
749
- walk(fullPath);
750
- } else if (stats.isFile() && stats.size > 0) {
751
- results.push(fullPath);
752
- }
753
- }
754
- }
755
- walk(dirPath);
756
- return results;
757
- }
758
-
759
- // src/handlers/shell-manager.ts
760
- var import_node_child_process = require("node:child_process");
761
- var import_node_path9 = require("node:path");
762
- var shells = /* @__PURE__ */ new Map();
763
- var shellCounter = 0;
764
- function generateShellId() {
765
- shellCounter++;
766
- return `shell-${Date.now()}-${shellCounter}`;
767
- }
768
- async function executeShell(command, options) {
769
- const shellId = generateShellId();
770
- const cwd = options.workspaceDir ? options.workingDirectory ? (0, import_node_path9.resolve)(options.workspaceDir, options.workingDirectory) : options.workspaceDir : process.cwd();
771
- const startTime = Date.now();
772
- const blockMs = options.blockUntilMs ?? 3e4;
773
- const isWindows = process.platform === "win32";
774
- const shellCmd = isWindows ? "cmd.exe" : "/bin/sh";
775
- const shellArgs = isWindows ? ["/c", command] : ["-c", command];
776
- return new Promise((resolvePromise) => {
777
- const child = (0, import_node_child_process.spawn)(shellCmd, shellArgs, {
778
- cwd,
779
- stdio: ["pipe", "pipe", "pipe"],
780
- shell: false,
781
- env: { ...process.env }
782
- });
783
- let stdout = "";
784
- let stderr = "";
785
- let settled = false;
786
- const shell = {
787
- shellId,
788
- command,
789
- description: options.description,
790
- process: child,
791
- status: "running",
792
- stdout,
793
- stderr,
794
- workingDirectory: cwd,
795
- startedAtMs: startTime,
796
- taskId: options.taskId
797
- };
798
- child.stdout?.on("data", (data) => {
799
- stdout += data.toString();
800
- shell.stdout = stdout;
801
- });
802
- child.stderr?.on("data", (data) => {
803
- stderr += data.toString();
804
- shell.stderr = stderr;
805
- });
806
- const finish = (status, code) => {
807
- if (settled) return;
808
- settled = true;
809
- shell.status = status;
810
- shell.exitCode = code;
811
- const duration = Date.now() - startTime;
812
- const stdoutBytes = Buffer.byteLength(stdout);
813
- const stderrBytes = Buffer.byteLength(stderr);
814
- if (blockMs <= 0) {
815
- shells.set(shellId, shell);
816
- } else {
817
- shells.delete(shellId);
818
- }
819
- resolvePromise({
820
- command,
821
- description: options.description,
822
- workingDirectory: cwd,
823
- stdout,
824
- stderr,
825
- stdoutTruncated: stdoutBytes > 65536,
826
- stderrTruncated: stderrBytes > 65536,
827
- stdoutTotalBytes: stdoutBytes,
828
- stderrTotalBytes: stderrBytes,
829
- exitCode: code,
830
- durationMs: duration,
831
- status,
832
- shellId: blockMs <= 0 ? shellId : void 0
833
- });
834
- };
835
- child.on("error", (err) => {
836
- stderr += err.message;
837
- finish("failed", 1);
838
- });
839
- child.on("close", (code) => {
840
- finish(code === 0 ? "completed" : "failed", code ?? void 0);
841
- });
842
- if (blockMs > 0) {
843
- const timeout = setTimeout(() => {
844
- if (!settled) {
845
- child.kill();
846
- finish("timeout", void 0);
847
- }
848
- }, blockMs);
849
- child.on("close", () => clearTimeout(timeout));
850
- }
851
- if (blockMs <= 0) {
852
- shells.set(shellId, shell);
853
- const stdoutBytes = Buffer.byteLength(stdout);
854
- const stderrBytes = Buffer.byteLength(stderr);
855
- resolvePromise({
856
- command,
857
- description: options.description,
858
- workingDirectory: cwd,
859
- stdout: "",
860
- stderr: "",
861
- stdoutTruncated: false,
862
- stderrTruncated: false,
863
- stdoutTotalBytes: 0,
864
- stderrTotalBytes: 0,
865
- exitCode: void 0,
866
- durationMs: 0,
867
- status: "running",
868
- shellId
869
- });
870
- }
871
- });
872
- }
873
- async function awaitShell(shellId, blockUntilMs = 3e4) {
874
- const shell = shells.get(shellId);
875
- if (!shell) {
876
- return {
877
- command: "",
878
- workingDirectory: "",
879
- stdout: "",
880
- stderr: "",
881
- stdoutTruncated: false,
882
- stderrTruncated: false,
883
- stdoutTotalBytes: 0,
884
- stderrTotalBytes: 0,
885
- durationMs: 0,
886
- status: "completed",
887
- shellId
888
- };
889
- }
890
- return new Promise((resolvePromise) => {
891
- const startTime = Date.now();
892
- const checkProcess = () => {
893
- if (shell.exitCode !== void 0 || shell.status !== "running") {
894
- const duration = Date.now() - startTime;
895
- const stdoutBytes = Buffer.byteLength(shell.stdout);
896
- const stderrBytes = Buffer.byteLength(shell.stderr);
897
- resolvePromise({
898
- command: shell.command,
899
- description: shell.description,
900
- workingDirectory: shell.workingDirectory,
901
- stdout: shell.stdout,
902
- stderr: shell.stderr,
903
- stdoutTruncated: stdoutBytes > 65536,
904
- stderrTruncated: stderrBytes > 65536,
905
- stdoutTotalBytes: stdoutBytes,
906
- stderrTotalBytes: stderrBytes,
907
- exitCode: shell.exitCode,
908
- durationMs: duration,
909
- status: shell.status,
910
- shellId
911
- });
912
- return;
913
- }
914
- if (Date.now() - startTime > blockUntilMs) {
915
- const duration = Date.now() - startTime;
916
- const stdoutBytes = Buffer.byteLength(shell.stdout);
917
- const stderrBytes = Buffer.byteLength(shell.stderr);
918
- resolvePromise({
919
- command: shell.command,
920
- description: shell.description,
921
- workingDirectory: shell.workingDirectory,
922
- stdout: shell.stdout,
923
- stderr: shell.stderr,
924
- stdoutTruncated: stdoutBytes > 65536,
925
- stderrTruncated: stderrBytes > 65536,
926
- stdoutTotalBytes: stdoutBytes,
927
- stderrTotalBytes: stderrBytes,
928
- exitCode: shell.exitCode,
929
- durationMs: duration,
930
- status: shell.status,
931
- shellId
932
- });
933
- return;
934
- }
935
- setTimeout(checkProcess, 50);
936
- };
937
- shell.process.on("close", () => {
938
- checkProcess();
939
- });
940
- checkProcess();
941
- });
942
- }
943
- function listShells(statusFilter) {
944
- const result = [];
945
- for (const shell of shells.values()) {
946
- if (statusFilter && shell.status !== statusFilter && statusFilter !== "all") {
947
- continue;
948
- }
949
- result.push({
950
- shellId: shell.shellId,
951
- command: shell.command,
952
- description: shell.description,
953
- workingDirectory: shell.workingDirectory,
954
- status: shell.status,
955
- exitCode: shell.exitCode,
956
- startedAtMs: shell.startedAtMs,
957
- taskId: shell.taskId,
958
- stdout: shell.stdout,
959
- stderr: shell.stderr,
960
- stdoutTruncated: Buffer.byteLength(shell.stdout) > 65536,
961
- stderrTruncated: Buffer.byteLength(shell.stderr) > 65536
962
- });
963
- }
964
- return result;
965
- }
966
- function killShell(shellId) {
967
- const shell = shells.get(shellId);
968
- if (!shell) return false;
969
- try {
970
- shell.process.kill("SIGTERM");
971
- shell.status = "cancelled";
972
- setTimeout(() => {
973
- try {
974
- shell.process.kill("SIGKILL");
975
- } catch {
976
- }
977
- }, 2e3);
978
- return true;
979
- } catch {
980
- return false;
981
- }
982
- }
983
- function readShellLogs(shellId, stream, offset = 0, limit = 4096) {
984
- const shell = shells.get(shellId);
985
- if (!shell) {
986
- return { data: "", offset: 0, totalBytes: 0, truncated: false };
987
- }
988
- const content = stream === "stdout" ? shell.stdout : shell.stderr;
989
- const totalBytes = Buffer.byteLength(content);
990
- const sliced = content.slice(offset, offset + limit);
991
- return {
992
- data: sliced,
993
- offset: offset + sliced.length,
994
- totalBytes,
995
- truncated: offset + limit < totalBytes
996
- };
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
- }
1019
-
1020
- // src/handlers/shell.ts
1021
- var shellHandler = async (rawArgs, context) => {
1022
- const args = rawArgs;
1023
- if (!args.command?.trim()) {
1024
- return toolFailure("shell", "invalid_arguments", "Command is required");
1025
- }
1026
- try {
1027
- const result = await executeShell(args.command.trim(), {
1028
- workingDirectory: args.working_directory,
1029
- description: args.description,
1030
- blockUntilMs: args.block_until_ms ?? 3e4,
1031
- taskId: context.taskId,
1032
- workspaceDir: context.workspaceDir
1033
- });
1034
- return toolSuccess("shell", result);
1035
- } catch (error2) {
1036
- const message = error2 instanceof Error ? error2.message : String(error2);
1037
- return toolFailure("shell", "execution_error", message);
1038
- }
1039
- };
1040
-
1041
- // src/handlers/await-shell.ts
1042
- var awaitShellHandler = async (rawArgs, _context) => {
1043
- const args = rawArgs;
1044
- if (!args.shell_id?.trim()) {
1045
- return toolFailure("await", "invalid_arguments", "shell_id is required");
1046
- }
1047
- try {
1048
- const result = await awaitShell(args.shell_id.trim(), args.block_until_ms ?? 3e4);
1049
- return toolSuccess("await", result);
1050
- } catch (error2) {
1051
- const message = error2 instanceof Error ? error2.message : String(error2);
1052
- return toolFailure("await", "error", message);
1053
- }
1054
- };
1055
-
1056
- // src/handlers/list-shells.ts
1057
- var listShellsHandler = async (rawArgs, _context) => {
1058
- const args = rawArgs;
1059
- const shells2 = listShells(args.status_filter);
1060
- return toolSuccess("list_shells", {
1061
- shells: shells2,
1062
- total: shells2.length
1063
- });
1064
- };
1065
-
1066
- // src/handlers/kill-shell.ts
1067
- var killShellHandler = async (rawArgs, _context) => {
1068
- const args = rawArgs;
1069
- if (!args.shell_id?.trim()) {
1070
- return toolFailure("kill_shell", "invalid_arguments", "shell_id is required");
1071
- }
1072
- const killed = killShell(args.shell_id.trim());
1073
- return toolSuccess("kill_shell", {
1074
- shellId: args.shell_id.trim(),
1075
- killed
1076
- });
1077
- };
1078
-
1079
- // src/handlers/read-shell-logs.ts
1080
- var readShellLogsHandler = async (rawArgs, _context) => {
1081
- const args = rawArgs;
1082
- if (!args.shell_id?.trim()) {
1083
- return toolFailure("read_shell_logs", "invalid_arguments", "shell_id is required");
1084
- }
1085
- const result = readShellLogs(
1086
- args.shell_id.trim(),
1087
- args.stream ?? "stdout",
1088
- args.offset ?? 0,
1089
- args.limit ?? 4096
1090
- );
1091
- return toolSuccess("read_shell_logs", {
1092
- shellId: args.shell_id.trim(),
1093
- stream: args.stream ?? "stdout",
1094
- data: result.data,
1095
- offset: result.offset,
1096
- totalBytes: result.totalBytes,
1097
- truncated: result.truncated
1098
- });
1099
- };
1100
-
1101
- // src/handlers/web-search.ts
1102
- init_config();
1103
- var webSearchHandler = async (rawArgs, _context) => {
1104
- const args = rawArgs;
1105
- if (!args.search_term?.trim()) {
1106
- return toolFailure("web_search", "invalid_arguments", "search_term is required");
1107
- }
1108
- const config = loadConfig();
1109
- const tavilyKey = resolveTavilyApiKey(config);
1110
- try {
1111
- const results = await performWebSearch(args.search_term, args.max_results ?? 5, tavilyKey);
1112
- return toolSuccess("web_search", {
1113
- query: args.search_term,
1114
- results
1115
- });
1116
- } catch (error2) {
1117
- const message = error2 instanceof Error ? error2.message : String(error2);
1118
- return toolFailure("web_search", "search_error", message);
1119
- }
1120
- };
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
- }
1148
- try {
1149
- const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`;
1150
- const response = await fetch(url, {
1151
- signal: AbortSignal.timeout(1e4)
1152
- });
1153
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
1154
- const data = await response.json();
1155
- const results = [];
1156
- if (data.AbstractText) {
1157
- results.push({
1158
- title: data.AbstractSource ?? "Result",
1159
- url: data.AbstractURL ?? "",
1160
- snippet: data.AbstractText
1161
- });
1162
- }
1163
- if (data.RelatedTopics) {
1164
- for (const topic of data.RelatedTopics.slice(0, maxResults)) {
1165
- if (topic.Text && topic.FirstURL) {
1166
- results.push({
1167
- title: topic.Text.split(" - ")[0] ?? "Result",
1168
- url: topic.FirstURL,
1169
- snippet: topic.Text
1170
- });
1171
- }
1172
- }
1173
- }
1174
- if (results.length === 0) {
1175
- throw new Error("No results");
1176
- }
1177
- return results.slice(0, maxResults);
1178
- } catch {
1179
- throw new Error(
1180
- "Web search is currently unavailable. Configure a Tavily API key via `coder config tavilyApiKey <key>` or set the TAVILY_API_KEY environment variable."
1181
- );
1182
- }
1183
- }
1184
-
1185
- // src/handlers/browse-page.ts
1186
- var browsePageHandler = async (rawArgs, _context) => {
1187
- const args = rawArgs;
1188
- if (!args.url?.trim()) {
1189
- return toolFailure("browse_page", "invalid_arguments", "url is required");
1190
- }
1191
- try {
1192
- const response = await fetch(args.url.trim(), {
1193
- headers: {
1194
- "User-Agent": "Mozilla/5.0 (compatible; CoderCLI/1.0)",
1195
- Accept: "text/html,text/plain,*/*"
1196
- },
1197
- signal: AbortSignal.timeout(15e3)
1198
- });
1199
- const contentType = response.headers.get("content-type") ?? "text/plain";
1200
- const finalUrl = response.url;
1201
- const title = extractTitleFromUrl(finalUrl);
1202
- let content = await response.text();
1203
- const startLine = args.start_line ?? 1;
1204
- const maxLines = args.max_lines ?? 500;
1205
- const lines = content.split("\n");
1206
- const totalLines = lines.length;
1207
- const endLine = Math.min(startLine + maxLines - 1, totalLines);
1208
- const paginatedContent = lines.slice(startLine - 1, endLine).join("\n");
1209
- return toolSuccess("browse_page", {
1210
- url: args.url,
1211
- finalUrl,
1212
- title,
1213
- content: paginatedContent,
1214
- truncated: endLine < totalLines,
1215
- statusCode: response.status,
1216
- contentType
1217
- });
1218
- } catch (error2) {
1219
- const message = error2 instanceof Error ? error2.message : String(error2);
1220
- if (message.includes("fetch")) {
1221
- return toolFailure("browse_page", "network_error", `Failed to fetch URL: ${message}`);
1222
- }
1223
- return toolFailure("browse_page", "error", message);
1224
- }
1225
- };
1226
- function extractTitleFromUrl(url) {
1227
- try {
1228
- const parsed = new URL(url);
1229
- return parsed.hostname;
1230
- } catch {
1231
- return void 0;
1232
- }
1233
- }
1234
-
1235
- // src/handlers/todos.ts
1236
- var import_node_fs10 = require("node:fs");
1237
- var import_node_path11 = require("node:path");
1238
- init_config();
1239
- function getTodosFilePath(sessionId) {
1240
- const dir = (0, import_node_path11.join)(getConfigDirPath(), "todos");
1241
- if (!(0, import_node_fs10.existsSync)(dir)) {
1242
- (0, import_node_fs10.mkdirSync)(dir, { recursive: true });
1243
- }
1244
- const safeSessionId = (sessionId ?? "default").replace(/[^a-z0-9-]/gi, "");
1245
- return (0, import_node_path11.join)(dir, `${safeSessionId}.json`);
1246
- }
1247
- function readTodos(sessionId) {
1248
- const filePath = getTodosFilePath(sessionId);
1249
- if (!(0, import_node_fs10.existsSync)(filePath)) return [];
1250
- try {
1251
- return JSON.parse((0, import_node_fs10.readFileSync)(filePath, "utf-8"));
1252
- } catch {
1253
- return [];
1254
- }
1255
- }
1256
- function writeTodos(todos, sessionId) {
1257
- (0, import_node_fs10.writeFileSync)(getTodosFilePath(sessionId), JSON.stringify(todos, null, 2), "utf-8");
1258
- }
1259
- var todoReadHandler = async (rawArgs, context) => {
1260
- const args = rawArgs;
1261
- const sessionId = args.session_id ?? context.sessionId;
1262
- const todos = readTodos(sessionId);
1263
- const snapshot = todos.sort((a, b) => a.order - b.order).map((t) => ({
1264
- id: t.id,
1265
- content: t.content,
1266
- status: t.status
1267
- }));
1268
- return toolSuccess("todo_read", {
1269
- sessionId: sessionId ?? null,
1270
- todos: snapshot,
1271
- total: snapshot.length,
1272
- active: snapshot.filter((t) => t.status === "pending" || t.status === "in_progress").length,
1273
- completed: snapshot.filter((t) => t.status === "completed").length
1274
- });
1275
- };
1276
- var todoWriteHandler = async (rawArgs, context) => {
1277
- const args = rawArgs;
1278
- const sessionId = args.session_id ?? context.sessionId;
1279
- if (!args.todos || !Array.isArray(args.todos)) {
1280
- return toolFailure("todo_write", "invalid_arguments", "todos array is required");
1281
- }
1282
- const existing = readTodos(sessionId);
1283
- const existingMap = new Map(existing.map((t) => [t.id, t]));
1284
- for (let i = 0; i < args.todos.length; i++) {
1285
- const input = args.todos[i];
1286
- const existingRecord = existingMap.get(input.id);
1287
- if (existingRecord) {
1288
- existingRecord.content = input.content;
1289
- existingRecord.status = input.status;
1290
- existingRecord.updatedAt = Date.now();
1291
- } else {
1292
- existing.push({
1293
- id: input.id,
1294
- content: input.content,
1295
- status: input.status,
1296
- order: i,
1297
- createdAt: Date.now(),
1298
- updatedAt: Date.now()
1299
- });
1300
- }
1301
- }
1302
- writeTodos(existing, sessionId);
1303
- return toolSuccess("todo_write", {
1304
- total: existing.length,
1305
- updated: args.todos.length
1306
- });
1307
- };
1308
-
1309
- // src/handlers/workspace-tree.ts
1310
- var import_node_fs11 = require("node:fs");
1311
- var import_node_path12 = require("node:path");
1312
- var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1313
- "node_modules",
1314
- ".git",
1315
- "dist",
1316
- "build",
1317
- "target",
1318
- ".next",
1319
- "out",
1320
- ".cache",
1321
- ".turbo",
1322
- "coverage",
1323
- ".nyc_output",
1324
- "__pycache__",
1325
- ".venv",
1326
- "venv",
1327
- ".history"
1328
- ]);
1329
- var getWorkspaceTreeHandler = async (rawArgs, context) => {
1330
- const args = rawArgs;
1331
- if (!context.workspaceDir) {
1332
- return toolFailure("get_workspace_tree", "workspace_required", "No workspace directory set");
1333
- }
1334
- try {
1335
- const rootDir = context.workspaceDir;
1336
- const treeLines = buildTree(rootDir, rootDir, 0);
1337
- const fullText = treeLines.join("\n");
1338
- const totalLines = treeLines.length;
1339
- const startLine = args.start_line ?? 1;
1340
- const maxLines = args.max_lines ?? 500;
1341
- const endLine = Math.min(startLine + maxLines - 1, totalLines);
1342
- const paginatedText = treeLines.slice(startLine - 1, endLine).join("\n");
1343
- return toolSuccess("get_workspace_tree", {
1344
- treeText: paginatedText,
1345
- totalLines,
1346
- startLine,
1347
- endLine,
1348
- truncated: endLine < totalLines
1349
- });
1350
- } catch (error2) {
1351
- const message = error2 instanceof Error ? error2.message : String(error2);
1352
- return toolFailure("get_workspace_tree", "error", message);
1353
- }
1354
- };
1355
- function buildTree(rootDir, currentDir, depth) {
1356
- const lines = [];
1357
- const relPath = (0, import_node_path12.relative)(rootDir, currentDir);
1358
- if (depth === 0) {
1359
- lines.push((0, import_node_path12.relative)(rootDir, currentDir) || ".");
1360
- }
1361
- let entries;
1362
- try {
1363
- entries = (0, import_node_fs11.readdirSync)(currentDir);
1364
- } catch {
1365
- return lines;
1366
- }
1367
- const dirs = [];
1368
- const files = [];
1369
- for (const name of entries) {
1370
- if (name.startsWith(".") && depth === 0 && name === ".git") continue;
1371
- if (EXCLUDED_DIRS.has(name)) continue;
1372
- if (name.startsWith(".") && depth > 0) continue;
1373
- const fullPath = (0, import_node_path12.resolve)(currentDir, name);
1374
- try {
1375
- const stats = (0, import_node_fs11.statSync)(fullPath);
1376
- if (stats.isDirectory()) {
1377
- dirs.push(name);
1378
- } else {
1379
- files.push(name);
1380
- }
1381
- } catch {
1382
- }
1383
- }
1384
- dirs.sort();
1385
- files.sort();
1386
- const allEntries = [...dirs, ...files];
1387
- for (let i = 0; i < allEntries.length; i++) {
1388
- const name = allEntries[i];
1389
- const isLast = i === allEntries.length - 1;
1390
- const prefix = depth === 0 ? isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 " : "";
1391
- const childPrefix = depth === 0 ? isLast ? " " : "\u2502 " : "";
1392
- const fullPath = (0, import_node_path12.resolve)(currentDir, name);
1393
- lines.push(`${prefix}${name}`);
1394
- try {
1395
- const stats = (0, import_node_fs11.statSync)(fullPath);
1396
- if (stats.isDirectory()) {
1397
- const childLines = buildTreeFromDepth(rootDir, fullPath, depth + 1, prefix || "", childPrefix);
1398
- lines.push(...childLines);
1399
- }
1400
- } catch {
1401
- }
1402
- }
1403
- return lines;
1404
- }
1405
- function buildTreeFromDepth(rootDir, currentDir, depth, parentPrefix, prefix) {
1406
- const lines = [];
1407
- let entries;
1408
- try {
1409
- entries = (0, import_node_fs11.readdirSync)(currentDir);
1410
- } catch {
1411
- return lines;
1412
- }
1413
- const dirs = [];
1414
- const files = [];
1415
- for (const name of entries) {
1416
- if (EXCLUDED_DIRS.has(name)) continue;
1417
- if (name.startsWith(".")) continue;
1418
- const fullPath = (0, import_node_path12.resolve)(currentDir, name);
1419
- try {
1420
- const stats = (0, import_node_fs11.statSync)(fullPath);
1421
- if (stats.isDirectory()) {
1422
- dirs.push(name);
1423
- } else {
1424
- files.push(name);
1425
- }
1426
- } catch {
1427
- }
1428
- }
1429
- dirs.sort();
1430
- files.sort();
1431
- const allEntries = [...dirs, ...files];
1432
- for (let i = 0; i < allEntries.length; i++) {
1433
- const name = allEntries[i];
1434
- const isLast = i === allEntries.length - 1;
1435
- const linePrefix = `${prefix}${isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}`;
1436
- const childPrefix = `${prefix}${isLast ? " " : "\u2502 "}`;
1437
- lines.push(`${linePrefix}${name}`);
1438
- const fullPath = (0, import_node_path12.resolve)(currentDir, name);
1439
- try {
1440
- const stats = (0, import_node_fs11.statSync)(fullPath);
1441
- if (stats.isDirectory()) {
1442
- const childLines = buildTreeFromDepth(rootDir, fullPath, depth + 1, "", childPrefix);
1443
- lines.push(...childLines);
1444
- }
1445
- } catch {
1446
- }
1447
- }
1448
- return lines;
1449
- }
1450
-
1451
- // src/handlers/ask-question.ts
1452
- var import_node_readline = require("node:readline");
1453
- var askQuestionHandler = async (rawArgs, _context) => {
1454
- const args = rawArgs;
1455
- if (!args.question?.trim()) {
1456
- return toolFailure("ask_question", "invalid_arguments", "question is required");
1457
- }
1458
- const rl = (0, import_node_readline.createInterface)({
1459
- input: process.stdin,
1460
- output: process.stderr
1461
- });
1462
- const answer = await new Promise((resolve12) => {
1463
- process.stderr.write(`
1464
- \x1B[33m\u2753 ${args.question}\x1B[0m
1465
- > `);
1466
- rl.once("line", (line) => {
1467
- resolve12(line.trim());
1468
- });
1469
- });
1470
- rl.close();
1471
- if (!answer) {
1472
- return toolFailure("ask_question", "no_answer", "User did not provide an answer");
1473
- }
1474
- return toolSuccess("ask_question", {
1475
- question: args.question,
1476
- answer
1477
- });
1478
- };
1479
-
1480
- // src/handlers/spawn-subagent.ts
1481
- var spawnAgentFunction = null;
1482
- var spawnSubAgentHandler = async (rawArgs, _context) => {
1483
- const args = rawArgs;
1484
- if (!args.task?.trim()) {
1485
- return toolFailure("spawn_subagent", "invalid_arguments", "task is required");
1486
- }
1487
- if (!spawnAgentFunction) {
1488
- return toolFailure(
1489
- "spawn_subagent",
1490
- "not_available",
1491
- "Sub-agent spawning is not available in the current configuration."
1492
- );
1493
- }
1494
- try {
1495
- const result = await spawnAgentFunction(args.task, args.context, args.tools);
1496
- return toolSuccess("spawn_subagent", result);
1497
- } catch (error2) {
1498
- const message = error2 instanceof Error ? error2.message : String(error2);
1499
- return toolFailure("spawn_subagent", "execution_error", message);
1500
- }
1501
- };
1502
-
1503
- // src/handlers/index.ts
1504
- var AGENT_TOOL_DEFINITIONS = [
1505
- {
1506
- type: "function",
1507
- function: {
1508
- name: "list_dir",
1509
- description: "List files and directories under a path. Relative paths are resolved against the workspace root.",
1510
- parameters: {
1511
- type: "object",
1512
- properties: {
1513
- path: { type: "string", description: "Relative or absolute path within the workspace." },
1514
- recursive: { type: "boolean", description: "Whether to list entries recursively.", default: false },
1515
- max_depth: { type: "integer", description: "Maximum recursion depth when recursive is true.", default: 1 },
1516
- show_hidden: { type: "boolean", description: "Whether to include dotfiles and dot-directories.", default: false }
1517
- },
1518
- required: ["path"],
1519
- additionalProperties: false
1520
- }
1521
- }
1522
- },
1523
- {
1524
- type: "function",
1525
- function: {
1526
- name: "read_file",
1527
- description: "Read a text file with line numbers and pagination. Relative paths are resolved against the workspace root.",
1528
- parameters: {
1529
- type: "object",
1530
- properties: {
1531
- path: { type: "string", description: "Relative or absolute path to the file within the workspace." },
1532
- start_line: { type: "integer", description: "First line to read (1-based).", default: 1 },
1533
- max_lines: { type: "integer", description: "Maximum number of lines to return.", default: 500 },
1534
- respect_gitignore: { type: "boolean", description: "Whether to refuse reading paths ignored by .gitignore.", default: true }
1535
- },
1536
- required: ["path"],
1537
- additionalProperties: false
1538
- }
1539
- }
1540
- },
1541
- {
1542
- type: "function",
1543
- function: {
1544
- name: "write_file",
1545
- description: "Create a new text file. Fails if the file already exists. Use edit_file or replace_lines to modify existing files.",
1546
- parameters: {
1547
- type: "object",
1548
- properties: {
1549
- path: { type: "string", description: "Relative or absolute path to the new file within the workspace." },
1550
- content: { type: "string", description: "Full file content to write." },
1551
- create_parent_dirs: { type: "boolean", description: "Whether to create missing parent directories.", default: true }
1552
- },
1553
- required: ["path", "content"],
1554
- additionalProperties: false
1555
- }
1556
- }
1557
- },
1558
- {
1559
- type: "function",
1560
- function: {
1561
- name: "replace_file",
1562
- description: "Replace an existing text file with new content. Use as a last resort \u2014 prefer edit_file or replace_lines first.",
1563
- parameters: {
1564
- type: "object",
1565
- properties: {
1566
- path: { type: "string", description: "Relative or absolute path to the file within the workspace." },
1567
- content: { type: "string", description: "Full replacement file content." },
1568
- expected_sha256: { type: "string", description: "SHA256 hash from read_file. Rejects the write if the file changed." },
1569
- create_backup: { type: "boolean", description: "Whether to save a backup copy under .history before writing.", default: false },
1570
- respect_gitignore: { type: "boolean", description: "Whether to refuse editing paths ignored by .gitignore.", default: true }
1571
- },
1572
- required: ["path", "content"],
1573
- additionalProperties: false
1574
- }
1575
- }
1576
- },
1577
- {
1578
- type: "function",
1579
- function: {
1580
- name: "edit_file",
1581
- description: "Apply a search-and-replace edit to an existing text file. The primary file editing tool \u2014 use this first.",
1582
- parameters: {
1583
- type: "object",
1584
- properties: {
1585
- path: { type: "string", description: "Relative or absolute path to the file within the workspace." },
1586
- old_string: { type: "string", description: "Exact text to replace. Must match uniquely unless replace_all is true." },
1587
- new_string: { type: "string", description: "Replacement text." },
1588
- expected_sha256: { type: "string", description: "SHA256 hash from read_file. Rejects the edit if the file changed." },
1589
- replace_all: { type: "boolean", description: "Whether to replace every occurrence of old_string.", default: false },
1590
- create_backup: { type: "boolean", description: "Whether to save a backup copy under .history before writing.", default: false },
1591
- respect_gitignore: { type: "boolean", description: "Whether to refuse editing paths ignored by .gitignore.", default: true }
1592
- },
1593
- required: ["path", "old_string", "new_string"],
1594
- additionalProperties: false
1595
- }
1596
- }
1597
- },
1598
- {
1599
- type: "function",
1600
- function: {
1601
- name: "replace_lines",
1602
- description: "Replace a range of lines in an existing text file by line number. Read the file with read_file first to see line numbers.",
1603
- parameters: {
1604
- type: "object",
1605
- properties: {
1606
- path: { type: "string", description: "Relative or absolute path to the file within the workspace." },
1607
- start_line: { type: "integer", description: "First line to replace (1-based)." },
1608
- end_line: { type: "integer", description: "Last line to replace (inclusive, 1-based)." },
1609
- content: { type: "string", description: "Replacement content for the specified line range." },
1610
- expected_sha256: { type: "string", description: "SHA256 hash from read_file. Rejects the edit if the file changed." },
1611
- create_backup: { type: "boolean", description: "Whether to save a backup copy under .history before writing.", default: false },
1612
- respect_gitignore: { type: "boolean", description: "Whether to refuse editing paths ignored by .gitignore.", default: true }
1613
- },
1614
- required: ["path", "start_line", "end_line", "content"],
1615
- additionalProperties: false
1616
- }
1617
- }
1618
- },
1619
- {
1620
- type: "function",
1621
- function: {
1622
- name: "glob",
1623
- description: "Find files by glob pattern under a directory. Relative paths are resolved against the workspace root.",
1624
- parameters: {
1625
- type: "object",
1626
- properties: {
1627
- glob_pattern: { type: "string", description: "Glob pattern such as **/*.tsx or src/**/*.rs." },
1628
- target_directory: { type: "string", description: "Directory to search from. Defaults to the workspace root." },
1629
- head_limit: { type: "integer", description: "Maximum number of matching paths to return.", default: 100 },
1630
- respect_gitignore: { type: "boolean", description: "Whether to skip paths ignored by .gitignore.", default: true }
1631
- },
1632
- required: ["glob_pattern"],
1633
- additionalProperties: false
1634
- }
1635
- }
1636
- },
1637
- {
1638
- type: "function",
1639
- function: {
1640
- name: "grep",
1641
- description: "Search file contents with a regex pattern. Relative paths are resolved against the workspace root.",
1642
- parameters: {
1643
- type: "object",
1644
- properties: {
1645
- pattern: { type: "string", description: "Regular expression pattern to search for." },
1646
- path: { type: "string", description: "File or directory to search. Defaults to the workspace root." },
1647
- glob: { type: "string", description: "Optional glob filter to limit searched files." },
1648
- output_mode: { type: "string", enum: ["content", "files_with_matches", "count"], default: "content" },
1649
- case_insensitive: { type: "boolean", description: "Whether to ignore letter case while matching.", default: false },
1650
- context_before: { type: "integer", description: "Number of lines to include before each match." },
1651
- context_after: { type: "integer", description: "Number of lines to include after each match." },
1652
- context: { type: "integer", description: "Number of lines to include before and after each match." },
1653
- head_limit: { type: "integer", description: "Maximum number of results to return.", default: 200 },
1654
- offset: { type: "integer", description: "Number of results to skip in content mode.", default: 0 },
1655
- multiline: { type: "boolean", description: "Whether . should match newlines.", default: false },
1656
- respect_gitignore: { type: "boolean", description: "Whether to skip paths ignored by .gitignore.", default: true }
1657
- },
1658
- required: ["pattern"],
1659
- additionalProperties: false
1660
- }
1661
- }
1662
- },
1663
- {
1664
- type: "function",
1665
- function: {
1666
- name: "shell",
1667
- description: "Execute a shell command in the workspace. Use for builds, tests, git, and other CLI tasks.",
1668
- parameters: {
1669
- type: "object",
1670
- properties: {
1671
- command: { type: "string", description: "The shell command to execute." },
1672
- description: { type: "string", description: "Short human-readable description for display only." },
1673
- working_directory: { type: "string", description: "Directory to run the command in, relative to workspace root." },
1674
- block_until_ms: { type: "integer", description: "Max wait time in ms. Default 30000. Use 0 for background mode.", default: 3e4 }
1675
- },
1676
- required: ["command"],
1677
- additionalProperties: false
1678
- }
1679
- }
1680
- },
1681
- {
1682
- type: "function",
1683
- function: {
1684
- name: "await",
1685
- description: "Poll a background shell started with shell(block_until_ms=0) until it completes or times out.",
1686
- parameters: {
1687
- type: "object",
1688
- properties: {
1689
- shell_id: { type: "string", description: "The shell_id returned from a background shell invocation." },
1690
- block_until_ms: { type: "integer", description: "Max wait time in ms before returning current output.", default: 3e4 }
1691
- },
1692
- required: ["shell_id"],
1693
- additionalProperties: false
1694
- }
1695
- }
1696
- },
1697
- {
1698
- type: "function",
1699
- function: {
1700
- name: "list_shells",
1701
- description: "List background shell processes started by the agent.",
1702
- parameters: {
1703
- type: "object",
1704
- properties: {
1705
- status_filter: { type: "string", description: 'Filter by status. Default "running". Use "all" to include completed and failed shells.' }
1706
- },
1707
- additionalProperties: false
1708
- }
1709
- }
1710
- },
1711
- {
1712
- type: "function",
1713
- function: {
1714
- name: "kill_shell",
1715
- description: "Kill a running background shell process by shell_id.",
1716
- parameters: {
1717
- type: "object",
1718
- properties: {
1719
- shell_id: { type: "string", description: "The shell_id to terminate." }
1720
- },
1721
- required: ["shell_id"],
1722
- additionalProperties: false
1723
- }
1724
- }
1725
- },
1726
- {
1727
- type: "function",
1728
- function: {
1729
- name: "read_shell_logs",
1730
- description: "Read logs from a shell process in batches.",
1731
- parameters: {
1732
- type: "object",
1733
- properties: {
1734
- shell_id: { type: "string", description: "The shell_id to read logs from." },
1735
- stream: { type: "string", enum: ["stdout", "stderr"], default: "stdout" },
1736
- offset: { type: "integer", description: "Byte offset to start reading from.", default: 0 },
1737
- limit: { type: "integer", description: "Maximum bytes to return.", default: 4096 }
1738
- },
1739
- required: ["shell_id"],
1740
- additionalProperties: false
1741
- }
1742
- }
1743
- },
1744
- {
1745
- type: "function",
1746
- function: {
1747
- name: "web_search",
1748
- description: "Search the web for real-time information outside training data.",
1749
- parameters: {
1750
- type: "object",
1751
- properties: {
1752
- search_term: { type: "string", description: "The search term to look up on the web." },
1753
- max_results: { type: "integer", description: "Maximum number of search results to return.", default: 5 },
1754
- explanation: { type: "string", description: "One sentence explanation of why this search is being used." }
1755
- },
1756
- required: ["search_term"],
1757
- additionalProperties: false
1758
- }
1759
- }
1760
- },
1761
- {
1762
- type: "function",
1763
- function: {
1764
- name: "browse_page",
1765
- description: "Fetch a public web page and return readable Markdown content.",
1766
- parameters: {
1767
- type: "object",
1768
- properties: {
1769
- url: { type: "string", description: "The URL to fetch. Must be http or https." },
1770
- max_lines: { type: "integer", description: "Maximum number of lines to return.", default: 500 },
1771
- start_line: { type: "integer", description: "First line to read (1-based).", default: 1 },
1772
- explanation: { type: "string", description: "One sentence explanation of why this page is being fetched." }
1773
- },
1774
- required: ["url"],
1775
- additionalProperties: false
1776
- }
1777
- }
1778
- },
1779
- {
1780
- type: "function",
1781
- function: {
1782
- name: "todo_read",
1783
- description: "Read the current structured todo list for the session.",
1784
- parameters: {
1785
- type: "object",
1786
- properties: {
1787
- session_id: { type: "string", description: "Optional session ID." }
1788
- },
1789
- additionalProperties: false
1790
- }
1791
- }
1792
- },
1793
- {
1794
- type: "function",
1795
- function: {
1796
- name: "todo_write",
1797
- description: "Create and update a structured todo list for the session.",
1798
- parameters: {
1799
- type: "object",
1800
- properties: {
1801
- todos: {
1802
- type: "array",
1803
- items: {
1804
- type: "object",
1805
- properties: {
1806
- id: { type: "string" },
1807
- content: { type: "string" },
1808
- status: { type: "string", enum: ["pending", "in_progress", "completed", "cancelled"] }
1809
- },
1810
- required: ["id", "content", "status"]
1811
- }
1812
- },
1813
- session_id: { type: "string", description: "Optional session ID." }
1814
- },
1815
- required: ["todos"],
1816
- additionalProperties: false
1817
- }
1818
- }
1819
- },
1820
- {
1821
- type: "function",
1822
- function: {
1823
- name: "get_workspace_tree",
1824
- description: "Display the workspace directory tree structure with depth recursion.",
1825
- parameters: {
1826
- type: "object",
1827
- properties: {
1828
- max_lines: { type: "integer", description: "Maximum number of lines to return.", default: 500 },
1829
- start_line: { type: "integer", description: "First line to return (1-based).", default: 1 }
1830
- },
1831
- additionalProperties: false
1832
- }
1833
- }
1834
- },
1835
- {
1836
- type: "function",
1837
- function: {
1838
- name: "spawn_subagent",
1839
- description: "Spawn a sub-agent to complete an independent sub-task.",
1840
- parameters: {
1841
- type: "object",
1842
- properties: {
1843
- task: { type: "string", description: "The task description for the sub-agent." },
1844
- context: { type: "string", description: "Optional additional context or constraints." },
1845
- tools: { type: "array", items: { type: "string" }, description: "Optional whitelist of tool names." }
1846
- },
1847
- required: ["task"],
1848
- additionalProperties: false
1849
- }
1850
- }
1851
- },
1852
- {
1853
- type: "function",
1854
- function: {
1855
- name: "ask_question",
1856
- description: "Ask the user a question when you need additional information to proceed.",
1857
- parameters: {
1858
- type: "object",
1859
- properties: {
1860
- question: { type: "string", description: "The question to ask the user." }
1861
- },
1862
- required: ["question"],
1863
- additionalProperties: false
1864
- }
1865
- }
1866
- }
1867
- ];
1868
- var HANDLER_MAP = {
1869
- list_dir: listDirHandler,
1870
- read_file: readFileHandler,
1871
- write_file: writeFileHandler,
1872
- replace_file: replaceFileHandler,
1873
- edit_file: editFileHandler,
1874
- replace_lines: replaceLinesHandler,
1875
- glob: globHandler,
1876
- grep: grepHandler,
1877
- shell: shellHandler,
1878
- await: awaitShellHandler,
1879
- list_shells: listShellsHandler,
1880
- kill_shell: killShellHandler,
1881
- read_shell_logs: readShellLogsHandler,
1882
- web_search: webSearchHandler,
1883
- browse_page: browsePageHandler,
1884
- todo_read: todoReadHandler,
1885
- todo_write: todoWriteHandler,
1886
- get_workspace_tree: getWorkspaceTreeHandler,
1887
- spawn_subagent: spawnSubAgentHandler,
1888
- ask_question: askQuestionHandler
1889
- };
1890
- var ASK_QUESTION_TOOL_NAME = "ask_question";
1891
- var AGENT_MODE_EXCLUDED_TOOL_NAMES = /* @__PURE__ */ new Set([
1892
- ASK_QUESTION_TOOL_NAME
1893
- ]);
1894
- var ASK_MODE_TOOL_NAMES = /* @__PURE__ */ new Set([
1895
- "list_dir",
1896
- "read_file",
1897
- "glob",
1898
- "grep",
1899
- "web_search",
1900
- "browse_page",
1901
- "todo_read",
1902
- "list_shells",
1903
- "read_shell_logs",
1904
- "get_workspace_tree"
1905
- ]);
1906
- function getToolDefinitions(agentMode) {
1907
- if (agentMode === "ask") {
1908
- return AGENT_TOOL_DEFINITIONS.filter(
1909
- (t) => ASK_MODE_TOOL_NAMES.has(t.function.name)
1910
- );
1911
- }
1912
- return AGENT_TOOL_DEFINITIONS.filter(
1913
- (t) => !AGENT_MODE_EXCLUDED_TOOL_NAMES.has(t.function.name)
1914
- );
1915
- }
1916
- function getToolHandler(name) {
1917
- return HANDLER_MAP[name];
1918
- }
1919
- async function executeToolCall(name, rawArguments, context) {
1920
- const handler = getToolHandler(name);
1921
- if (!handler) {
1922
- return {
1923
- ok: false,
1924
- tool: name,
1925
- error: {
1926
- code: "unknown_tool",
1927
- message: `Unknown tool: ${name}`
1928
- }
1929
- };
1930
- }
1931
- let parsedArgs;
1932
- try {
1933
- parsedArgs = rawArguments.trim() ? JSON.parse(rawArguments) : {};
1934
- } catch {
1935
- return {
1936
- ok: false,
1937
- tool: name,
1938
- error: {
1939
- code: "invalid_arguments",
1940
- message: "Tool arguments must be valid JSON"
1941
- }
1942
- };
1943
- }
1944
- return handler(parsedArgs, context);
1945
- }
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
-
1986
- // src/agent/llm-stream.ts
1987
- function chatCompletionsUrl(baseUrl) {
1988
- const trimmed = baseUrl.trim().replace(/\/+$/, "");
1989
- if (trimmed.endsWith("/v1")) {
1990
- return `${trimmed}/chat/completions`;
1991
- }
1992
- return `${trimmed}/v1/chat/completions`;
1993
- }
1994
- async function startLLMStream(options, callbacks) {
1995
- const url = chatCompletionsUrl(options.baseUrl);
1996
- const body = {
1997
- model: options.model,
1998
- messages: options.messages.map(formatMessage),
1999
- stream: true,
2000
- stream_options: { include_usage: true }
2001
- };
2002
- if (options.tools && options.tools.length > 0) {
2003
- body.tools = options.tools;
2004
- body.tool_choice = "auto";
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
- }
2016
- try {
2017
- const response = await fetch(url, {
2018
- method: "POST",
2019
- headers: {
2020
- Authorization: `Bearer ${options.apiKey}`,
2021
- "Content-Type": "application/json",
2022
- Accept: "text/event-stream"
2023
- },
2024
- body: JSON.stringify(body),
2025
- signal: options.signal
2026
- });
2027
- if (!response.ok) {
2028
- const errorBody = await response.text().catch(() => "");
2029
- throw new Error(`API error (${response.status}): ${errorBody || response.statusText}`);
2030
- }
2031
- if (!response.body) {
2032
- throw new Error("Response body is empty");
2033
- }
2034
- const reader = response.body.getReader();
2035
- const decoder = new TextDecoder();
2036
- let buffer = "";
2037
- const toolCallAccumulator = new ToolCallAccumulator({
2038
- onIdentified: (id, name) => {
2039
- }
2040
- });
2041
- let hasToolCalls = false;
2042
- const onAbort = () => {
2043
- reader.cancel().catch(() => {
2044
- });
2045
- };
2046
- options.signal?.addEventListener("abort", onAbort, { once: true });
2047
- while (true) {
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;
2058
- if (done) break;
2059
- buffer += decoder.decode(value, { stream: true });
2060
- while (true) {
2061
- const lineBreak = buffer.indexOf("\n");
2062
- if (lineBreak === -1) break;
2063
- const line = buffer.slice(0, lineBreak).trim();
2064
- buffer = buffer.slice(lineBreak + 1);
2065
- processLine(line, callbacks, toolCallAccumulator);
2066
- }
2067
- }
2068
- if (buffer.trim()) {
2069
- processLine(buffer.trim(), callbacks, toolCallAccumulator);
2070
- }
2071
- const finalToolCalls = toolCallAccumulator.finalize();
2072
- for (const call of finalToolCalls) {
2073
- callbacks.onToolCall(call.id, call.name, call.arguments);
2074
- }
2075
- callbacks.onDone();
2076
- } catch (error2) {
2077
- if (error2 instanceof Error) {
2078
- callbacks.onError(error2);
2079
- } else {
2080
- callbacks.onError(new Error(String(error2)));
2081
- }
2082
- }
2083
- }
2084
- function processLine(line, callbacks, toolCalls) {
2085
- if (!line || line.startsWith(":")) return;
2086
- const payload = line.startsWith("data:") ? line.slice(5).trim() : line;
2087
- if (payload === "[DONE]") return;
2088
- let parsed;
2089
- try {
2090
- parsed = JSON.parse(payload);
2091
- } catch {
2092
- return;
2093
- }
2094
- const choices = parsed.choices;
2095
- const choice = choices?.[0];
2096
- const delta = choice?.delta;
2097
- if (delta) {
2098
- if (delta.reasoning_content) {
2099
- callbacks.onReasoning(delta.reasoning_content);
2100
- }
2101
- if (delta.content) {
2102
- callbacks.onContent(delta.content);
2103
- }
2104
- const toolCallDeltas = delta.tool_calls;
2105
- if (toolCallDeltas) {
2106
- for (const tcDelta of toolCallDeltas) {
2107
- const index = tcDelta.index;
2108
- const fn = tcDelta.function;
2109
- const id = tcDelta.id;
2110
- if (id) {
2111
- toolCalls.startToolCall(index, id, fn?.name);
2112
- } else if (fn?.arguments) {
2113
- toolCalls.appendArguments(index, fn.arguments);
2114
- }
2115
- }
2116
- }
2117
- }
2118
- const usage = parsed.usage;
2119
- if (usage?.prompt_tokens !== void 0) {
2120
- callbacks.onUsage({
2121
- promptTokens: usage.prompt_tokens,
2122
- completionTokens: usage.completion_tokens,
2123
- totalTokens: usage.total_tokens
2124
- });
2125
- }
2126
- }
2127
- var ToolCallAccumulator = class {
2128
- pending = /* @__PURE__ */ new Map();
2129
- finalized = false;
2130
- onIdentified;
2131
- constructor(opts) {
2132
- this.onIdentified = opts.onIdentified;
2133
- }
2134
- startToolCall(index, id, name) {
2135
- this.pending.set(index, { index, id, name, arguments: "" });
2136
- }
2137
- appendArguments(index, args) {
2138
- const existing = this.pending.get(index);
2139
- if (existing) {
2140
- existing.arguments += args;
2141
- }
2142
- }
2143
- finalize() {
2144
- if (this.finalized) return [];
2145
- this.finalized = true;
2146
- const calls = Array.from(this.pending.values()).sort((a, b) => a.index - b.index).map((c) => ({
2147
- id: c.id,
2148
- name: c.name,
2149
- arguments: c.arguments
2150
- }));
2151
- return calls;
2152
- }
2153
- };
2154
- function formatMessage(msg) {
2155
- const formatted = {
2156
- role: msg.role
2157
- };
2158
- if (msg.content !== void 0) {
2159
- formatted.content = msg.content;
2160
- }
2161
- if (msg.reasoning_content) {
2162
- formatted.reasoning_content = msg.reasoning_content;
2163
- }
2164
- if (msg.tool_calls) {
2165
- formatted.tool_calls = msg.tool_calls;
2166
- }
2167
- if (msg.tool_call_id) {
2168
- formatted.tool_call_id = msg.tool_call_id;
2169
- }
2170
- if (msg.name) {
2171
- formatted.name = msg.name;
2172
- }
2173
- return formatted;
2174
- }
2175
-
2176
- // src/agent/runner.ts
2177
- async function runAgentWithTools(input, toolContext, onEvent) {
2178
- let messages = [...input.messages];
2179
- let cumulativeUsage;
2180
- while (true) {
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
- }
2199
- if (turn.usage) {
2200
- cumulativeUsage = cumulativeUsage ? {
2201
- promptTokens: cumulativeUsage.promptTokens + turn.usage.promptTokens,
2202
- completionTokens: cumulativeUsage.completionTokens + turn.usage.completionTokens,
2203
- totalTokens: cumulativeUsage.totalTokens + turn.usage.totalTokens
2204
- } : turn.usage;
2205
- }
2206
- if (turn.toolCalls.length === 0) {
2207
- if (turn.content || turn.reasoningContent) {
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];
2216
- }
2217
- onEvent({ type: "done", taskId: input.taskId, usage: cumulativeUsage ?? turn.usage });
2218
- onEvent({ type: "status", taskId: input.taskId, status: "completed" });
2219
- return messages;
2220
- }
2221
- messages = await appendToolResults(messages, turn, toolContext, onEvent);
2222
- }
2223
- }
2224
- async function runSingleAgentTurn(input, messages, onEvent) {
2225
- return new Promise((resolve12, reject) => {
2226
- let content = "";
2227
- let reasoningContent = "";
2228
- const toolCalls = [];
2229
- let turnUsage;
2230
- startLLMStream(
2231
- {
2232
- baseUrl: input.baseUrl,
2233
- apiKey: input.apiKey,
2234
- model: input.model,
2235
- messages,
2236
- tools: getToolDefinitions(input.agentMode),
2237
- thinkingProvider: input.provider,
2238
- thinkingEnabled: input.thinkingEnabled,
2239
- thinkingOverride: input.thinkingParams,
2240
- signal: input.signal
2241
- },
2242
- {
2243
- onContent: (delta) => {
2244
- content += delta;
2245
- onEvent({ type: "content_delta", taskId: input.taskId, delta });
2246
- },
2247
- onReasoning: (delta) => {
2248
- reasoningContent += delta;
2249
- onEvent({ type: "thinking_delta", taskId: input.taskId, delta });
2250
- },
2251
- onToolCall: (id, name, args) => {
2252
- toolCalls.push({ id, name, arguments: args });
2253
- onEvent({ type: "tool_call_pending", taskId: input.taskId, toolCallId: id, name });
2254
- },
2255
- onUsage: (usage) => {
2256
- turnUsage = usage;
2257
- },
2258
- onDone: () => {
2259
- if (toolCalls.length > 0) {
2260
- for (const call of toolCalls) {
2261
- let parsedInput;
2262
- try {
2263
- parsedInput = call.arguments.trim() ? JSON.parse(call.arguments) : {};
2264
- } catch {
2265
- parsedInput = {};
2266
- }
2267
- onEvent({
2268
- type: "tool_call_started",
2269
- taskId: input.taskId,
2270
- toolCallId: call.id,
2271
- name: call.name,
2272
- input: parsedInput
2273
- });
2274
- }
2275
- }
2276
- resolve12({ toolCalls, content, reasoningContent, usage: turnUsage });
2277
- },
2278
- onError: (error2) => {
2279
- reject(error2);
2280
- }
2281
- }
2282
- );
2283
- });
2284
- }
2285
- async function appendToolResults(messages, turn, context, onEvent) {
2286
- const assistantMessage = {
2287
- role: "assistant",
2288
- content: turn.content || void 0,
2289
- reasoning_content: turn.reasoningContent || void 0,
2290
- tool_calls: turn.toolCalls.map((tc) => ({
2291
- id: tc.id,
2292
- type: "function",
2293
- function: { name: tc.name, arguments: tc.arguments }
2294
- }))
2295
- };
2296
- const nextMessages = [...messages, assistantMessage];
2297
- const results = await Promise.all(
2298
- turn.toolCalls.map(async (call) => {
2299
- try {
2300
- const result = await executeToolCall(call.name, call.arguments, context);
2301
- if (result.ok) {
2302
- onEvent({
2303
- type: "tool_call_finished",
2304
- taskId: context.taskId ?? "",
2305
- toolCallId: call.id,
2306
- output: result.data
2307
- });
2308
- } else {
2309
- onEvent({
2310
- type: "tool_call_finished",
2311
- taskId: context.taskId ?? "",
2312
- toolCallId: call.id,
2313
- errorText: result.error?.message
2314
- });
2315
- }
2316
- return { id: call.id, result };
2317
- } catch (error2) {
2318
- const errorText = error2 instanceof Error ? error2.message : String(error2);
2319
- onEvent({
2320
- type: "tool_call_finished",
2321
- taskId: context.taskId ?? "",
2322
- toolCallId: call.id,
2323
- errorText
2324
- });
2325
- return {
2326
- id: call.id,
2327
- result: {
2328
- ok: false,
2329
- tool: call.name,
2330
- error: { code: "execution_error", message: errorText }
2331
- }
2332
- };
2333
- }
2334
- })
2335
- );
2336
- for (const { id, result } of results) {
2337
- const content = result.ok ? JSON.stringify(result.data, null, 2) : JSON.stringify({ error: result.error }, null, 2);
2338
- nextMessages.push({
2339
- role: "tool",
2340
- tool_call_id: id,
2341
- content
2342
- });
2343
- }
2344
- return nextMessages;
2345
- }
2346
-
2347
- // src/agent/environment/index.ts
2348
- var import_node_os2 = require("node:os");
2349
- var import_node_child_process2 = require("node:child_process");
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
2754
- function resolveAgentEnvironment(workspaceDir) {
2755
- const os = `${(0, import_node_os2.platform)()} (${(0, import_node_os2.release)()})`;
2756
- const shell = resolveShell();
2757
- const isGitRepository = workspaceDir ? checkIsGitRepository(workspaceDir) : false;
2758
- const today = formatToday(/* @__PURE__ */ new Date());
2759
- let agentsMd = null;
2760
- if (workspaceDir) {
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");
2764
- agentsMd = {
2765
- path: agentsMdPath,
2766
- content: content.length > 32e3 ? content.slice(0, 32e3) + "\n... [truncated]" : content,
2767
- truncated: content.length > 32e3
2768
- };
2769
- }
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
- }));
2778
- return {
2779
- workspaceDir,
2780
- os,
2781
- shell,
2782
- isGitRepository,
2783
- today,
2784
- agentsMd,
2785
- enabledSystemSkills
2786
- };
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
- }
2883
- function resolveShell() {
2884
- const p = (0, import_node_os2.platform)();
2885
- if (p === "win32") {
2886
- return process.env.COMSPEC || "cmd.exe";
2887
- }
2888
- return process.env.SHELL || "/bin/sh";
2889
- }
2890
- function checkIsGitRepository(dir) {
2891
- try {
2892
- (0, import_node_child_process2.execSync)("git rev-parse --is-inside-work-tree", {
2893
- cwd: dir,
2894
- stdio: "pipe",
2895
- timeout: 3e3
2896
- });
2897
- return true;
2898
- } catch {
2899
- return false;
2900
- }
2901
- }
2902
-
2903
- // src/agent/session.ts
2904
- init_config();
2905
-
2906
- // src/ui/index.ts
2907
- var import_node_tty = require("node:tty");
2908
- var stdoutIsTTY = (0, import_node_tty.isatty)(process.stdout.fd);
2909
- var stderrIsTTY = (0, import_node_tty.isatty)(process.stderr.fd);
2910
- var colors = {
2911
- reset: "\x1B[0m",
2912
- bold: "\x1B[1m",
2913
- dim: "\x1B[2m",
2914
- italic: "\x1B[3m",
2915
- red: "\x1B[31m",
2916
- green: "\x1B[32m",
2917
- yellow: "\x1B[33m",
2918
- blue: "\x1B[34m",
2919
- magenta: "\x1B[35m",
2920
- cyan: "\x1B[36m",
2921
- gray: "\x1B[90m"
2922
- };
2923
- function colorize(text, color) {
2924
- if (!stdoutIsTTY) return text;
2925
- return `${colors[color]}${text}${colors.reset}`;
2926
- }
2927
- function dim(text) {
2928
- return colorize(text, "dim");
2929
- }
2930
- function bold(text) {
2931
- if (!stdoutIsTTY) return text;
2932
- return `${colors.bold}${text}${colors.reset}`;
2933
- }
2934
- function error(text) {
2935
- return colorize(text, "red");
2936
- }
2937
- function success(text) {
2938
- return colorize(text, "green");
2939
- }
2940
- function warning(text) {
2941
- return colorize(text, "yellow");
2942
- }
2943
- function info(text) {
2944
- return colorize(text, "cyan");
2945
- }
2946
- function writeStream(text) {
2947
- process.stdout.write(text);
2948
- }
2949
- function writeLine(text) {
2950
- process.stdout.write(text + "\n");
2951
- }
2952
- function writeError(text) {
2953
- process.stderr.write(text + "\n");
2954
- }
2955
-
2956
- // src/agent/session.ts
2957
- async function runAgentSession(prompt, options) {
2958
- const config = loadConfig();
2959
- const workspaceDir = options.workspaceDir || null;
2960
- const providerId = options.provider ?? config.activeProvider;
2961
- const resolvedConfig = resolveProviderConfig(config, providerId);
2962
- const apiKey = resolveApiKey(resolvedConfig);
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
- }
2977
- let messages;
2978
- if (options.existingMessages && options.existingMessages.length > 0) {
2979
- messages = [
2980
- ...sanitizeMessages(options.existingMessages),
2981
- { role: "user", content: prompt }
2982
- ];
2983
- } else {
2984
- const env = resolveAgentEnvironment(workspaceDir);
2985
- const systemPrompt = buildSystemPrompt(env, options.agentMode);
2986
- messages = [
2987
- { role: "system", content: systemPrompt },
2988
- { role: "user", content: prompt }
2989
- ];
2990
- }
2991
- const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2992
- const isStreaming = options.stream !== false;
2993
- let currentContent = "";
2994
- let currentThinking = "";
2995
- let hasContent = false;
2996
- let streamStarted = false;
2997
- const toolContext = {
2998
- workspaceDir,
2999
- sessionId: taskId,
3000
- taskId,
3001
- agentMode: options.agentMode
3002
- };
3003
- if (!isStreaming) {
3004
- writeLine(`${info("\u2139")} Running agent in ${bold(options.agentMode)} mode...
3005
- `);
3006
- }
3007
- try {
3008
- const finalMessages = await runAgentWithTools(
3009
- {
3010
- taskId,
3011
- baseUrl: resolvedConfig.baseUrl,
3012
- apiKey,
3013
- apiKeySource: resolvedConfig.apiKeySource,
3014
- apiKeyEnvVar: resolvedConfig.apiKeyEnvVar,
3015
- model: modelId,
3016
- messages,
3017
- agentMode: options.agentMode,
3018
- provider: providerId,
3019
- thinkingEnabled,
3020
- thinkingParams: thinkingParamsOverride,
3021
- signal: options.signal
3022
- },
3023
- toolContext,
3024
- (event) => {
3025
- switch (event.type) {
3026
- case "thinking_delta": {
3027
- currentThinking += event.delta;
3028
- if (isStreaming && !hasContent) {
3029
- if (!streamStarted) {
3030
- streamStarted = true;
3031
- writeLine("");
3032
- }
3033
- writeStream(dim(event.delta));
3034
- }
3035
- break;
3036
- }
3037
- case "content_delta": {
3038
- if (isStreaming) {
3039
- if (!streamStarted) {
3040
- streamStarted = true;
3041
- }
3042
- if (!hasContent && currentThinking) {
3043
- writeLine("");
3044
- }
3045
- hasContent = true;
3046
- currentContent += event.delta;
3047
- writeStream(event.delta);
3048
- }
3049
- break;
3050
- }
3051
- case "tool_call_started": {
3052
- if (isStreaming) {
3053
- if (!streamStarted) {
3054
- streamStarted = true;
3055
- writeLine("");
3056
- } else if (hasContent || currentThinking) {
3057
- writeLine("");
3058
- }
3059
- writeLine(`${dim("\u{1F527}")} ${bold(event.name)}${dim("...")}`);
3060
- }
3061
- break;
3062
- }
3063
- case "tool_call_finished": {
3064
- if (event.errorText) {
3065
- writeLine(` ${error("\u2717")} ${dim(event.errorText.slice(0, 200))}`);
3066
- } else if (isStreaming) {
3067
- writeLine(` ${success("\u2713")} ${dim("done")}`);
3068
- }
3069
- break;
3070
- }
3071
- case "status": {
3072
- if (event.status === "completed") {
3073
- if (isStreaming) {
3074
- writeLine("");
3075
- writeLine(success("\u2713 Task completed"));
3076
- } else {
3077
- writeLine(success("\u2713 Task completed"));
3078
- if (currentContent) {
3079
- writeLine("\n" + currentContent);
3080
- }
3081
- }
3082
- } else if (event.status === "failed") {
3083
- writeLine(error(`\u2717 Task failed`));
3084
- } else if (event.status === "cancelled") {
3085
- writeLine(warning(`\u26A0 Task cancelled`));
3086
- }
3087
- break;
3088
- }
3089
- case "done": {
3090
- break;
3091
- }
3092
- case "error": {
3093
- writeLine(error(`\u2717 Error: ${event.message}`));
3094
- break;
3095
- }
3096
- }
3097
- }
3098
- );
3099
- return finalMessages;
3100
- } catch (err) {
3101
- killAllShells();
3102
- const message = err instanceof Error ? err.message : String(err);
3103
- writeError(error(`Fatal error: ${message}`));
3104
- process.exit(1);
3105
- }
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
- }
3114
-
3115
- // src/commands/run.ts
3116
- async function runCommand(prompt, options) {
3117
- await runAgentSession(prompt, {
3118
- ...options,
3119
- agentMode: "agent",
3120
- workspaceDir: options.workspace ?? process.cwd(),
3121
- interactive: false
3122
- });
3123
- }
3124
-
3125
- // src/commands/ask.ts
3126
- async function askCommand(prompt, options) {
3127
- await runAgentSession(prompt, {
3128
- ...options,
3129
- agentMode: "ask",
3130
- workspaceDir: options.workspace ?? process.cwd(),
3131
- interactive: false
3132
- });
3133
- }
3134
-
3135
- // src/commands/repl.ts
3136
- var import_node_readline2 = require("node:readline");
3137
- init_config();
3138
- async function replCommand(options) {
3139
- const config = loadConfig();
3140
- const workspaceDir = options.workspace ?? process.cwd();
3141
- writeLine("");
3142
- writeLine(bold("Coder CLI \u2014 Interactive REPL"));
3143
- 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"));
3144
- writeLine(dim(` Model: ${config.lastModel || "not set"}`));
3145
- writeLine(dim(` Provider: ${config.activeProvider}`));
3146
- writeLine(dim(` Workspace: ${workspaceDir}`));
3147
- writeLine(dim(` Type '/help' for commands, '/exit' or Ctrl+C to quit`));
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"));
3149
- writeLine("");
3150
- let conversationMessages;
3151
- let thinkingEnabled = true;
3152
- let abortController = new AbortController();
3153
- let agentRunning = false;
3154
- const rl = (0, import_node_readline2.createInterface)({
3155
- input: process.stdin,
3156
- output: process.stdout,
3157
- prompt: `${bold("coder")}> `,
3158
- terminal: true
3159
- });
3160
- rl.prompt();
3161
- rl.on("line", async (line) => {
3162
- const trimmed = line.trim();
3163
- if (!trimmed) {
3164
- rl.prompt();
3165
- return;
3166
- }
3167
- if (agentRunning) {
3168
- rl.prompt();
3169
- return;
3170
- }
3171
- if (trimmed === "/exit" || trimmed === "/quit") {
3172
- rl.close();
3173
- return;
3174
- }
3175
- if (trimmed === "/clear") {
3176
- console.clear();
3177
- rl.prompt();
3178
- return;
3179
- }
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") {
3188
- writeLine(bold("\nREPL Commands:"));
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"})`);
3196
- writeLine("");
3197
- rl.prompt();
3198
- return;
3199
- }
3200
- if (trimmed.startsWith("/model ")) {
3201
- const modelId = trimmed.slice(7).trim();
3202
- if (modelId) {
3203
- config.lastModel = modelId;
3204
- const { saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3205
- saveConfig2(config);
3206
- writeLine(info(`Model set to: ${modelId}`));
3207
- }
3208
- rl.prompt();
3209
- return;
3210
- }
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;
3226
- try {
3227
- conversationMessages = await runAgentSession(trimmed, {
3228
- agentMode: "agent",
3229
- workspaceDir,
3230
- interactive: true,
3231
- thinking: thinkingEnabled,
3232
- existingMessages: conversationMessages,
3233
- signal: abortController.signal
3234
- });
3235
- } catch (err) {
3236
- const message = err instanceof Error ? err.message : String(err);
3237
- writeError(error(`Error: ${message}`));
3238
- }
3239
- agentRunning = false;
3240
- abortController = new AbortController();
3241
- writeLine("");
3242
- rl.prompt();
3243
- });
3244
- rl.on("close", () => {
3245
- killAllShells();
3246
- writeLine(dim("\nGoodbye! \u{1F44B}"));
3247
- process.exit(0);
3248
- });
3249
- rl.on("SIGINT", () => {
3250
- if (agentRunning) {
3251
- abortController.abort();
3252
- writeLine("");
3253
- writeLine(warning("\u26A0 Cancelled"));
3254
- } else {
3255
- rl.close();
3256
- }
3257
- });
3258
- }
3259
-
3260
- // src/commands/config.ts
3261
- init_config();
3262
- var PROVIDER_IDS2 = ["deepseek", "glm", "agnes", "nvidia", "minimax", "custom"];
3263
- async function configCommand(key, value) {
3264
- const config = loadConfig();
3265
- if (!key) {
3266
- showConfig(config);
3267
- return;
3268
- }
3269
- if (key && value !== void 0) {
3270
- await setConfigValue(config, key, value);
3271
- return;
3272
- }
3273
- showConfigValue(config, key);
3274
- }
3275
- function showConfig(config) {
3276
- writeLine(bold("\nCoder CLI Configuration"));
3277
- 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"));
3278
- writeLine(`Config directory: ${getConfigDirPath()}`);
3279
- writeLine(`Config file: ${getConfigFilePathExplicit()}`);
3280
- writeLine("");
3281
- writeLine(bold("Active Settings:"));
3282
- writeLine(` Active provider: ${config.activeProvider}`);
3283
- writeLine(` Last model: ${config.lastModel}`);
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)"}`);
3286
- writeLine("");
3287
- for (const providerId of PROVIDER_IDS2) {
3288
- const p = config.providers[providerId];
3289
- writeLine(bold(`Provider: ${providerId}`));
3290
- writeLine(` API Key Source: ${p.apiKeySource}`);
3291
- writeLine(` API Key Env Var: ${p.apiKeyEnvVar}`);
3292
- writeLine(` API Key (stored): ${p.apiKey ? "***" : "(not set)"}`);
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
- }
3303
- writeLine("");
3304
- }
3305
- writeLine(dim("To change a value: coder config <key> <value>"));
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"));
3326
- writeLine("");
3327
- }
3328
- function showConfigValue(config, key) {
3329
- const value = getNestedValue(config, key);
3330
- if (value === void 0) {
3331
- writeLine(warning(`Config key not found: ${key}`));
3332
- return;
3333
- }
3334
- writeLine(String(value));
3335
- }
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
- }
3347
- setNestedValue(config, key, parseValue(value));
3348
- saveConfig(config);
3349
- writeLine(info(`Config updated: ${key} = ${value}`));
3350
- }
3351
- function getNestedValue(obj, path) {
3352
- const parts = path.split(".");
3353
- let current = obj;
3354
- for (const part of parts) {
3355
- if (current === null || current === void 0 || typeof current !== "object") {
3356
- return void 0;
3357
- }
3358
- current = current[part];
3359
- }
3360
- return current;
3361
- }
3362
- function setNestedValue(obj, path, value) {
3363
- const parts = path.split(".");
3364
- let current = obj;
3365
- for (let i = 0; i < parts.length - 1; i++) {
3366
- if (!(parts[i] in current)) {
3367
- current[parts[i]] = {};
3368
- }
3369
- current = current[parts[i]];
3370
- }
3371
- current[parts[parts.length - 1]] = value;
3372
- }
3373
- function parseValue(value) {
3374
- if (value === "true") return true;
3375
- if (value === "false") return false;
3376
- const num = Number(value);
3377
- if (!isNaN(num) && value.trim() !== "") return num;
3378
- return value;
3379
- }
3380
-
3381
- // src/commands/common.ts
3382
- var _globalOptions = {};
3383
- function setGlobalOptions(opts) {
3384
- _globalOptions = opts;
3385
- }
3386
-
3387
- // src/index.ts
3388
- var VERSION = "0.1.108";
3389
- async function main() {
3390
- const rawArgs = process.argv.slice(2);
3391
- if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
3392
- printHelp();
3393
- return;
3394
- }
3395
- if (rawArgs.includes("--version") || rawArgs.includes("-v")) {
3396
- console.log(VERSION);
3397
- return;
3398
- }
3399
- const globalOpts = extractGlobalOptions(rawArgs);
3400
- setGlobalOptions(globalOpts);
3401
- if (rawArgs.length === 0) {
3402
- await replCommand({});
3403
- return;
3404
- }
3405
- const firstNonFlag = rawArgs.find((a) => !a.startsWith("-"));
3406
- const knownSubcommands = {
3407
- ask: async (args, opts) => {
3408
- await askCommand(args.join(" "), opts);
3409
- },
3410
- run: async (args, opts) => {
3411
- await runCommand(args.join(" "), opts);
3412
- },
3413
- config: async (args) => {
3414
- await configCommand(args[0], args[1]);
3415
- }
3416
- };
3417
- if (firstNonFlag && knownSubcommands[firstNonFlag]) {
3418
- const cmdArgs = rawArgs.slice(rawArgs.indexOf(firstNonFlag) + 1).filter((a) => !a.startsWith("-"));
3419
- await knownSubcommands[firstNonFlag](cmdArgs, globalOpts);
3420
- return;
3421
- }
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);
3427
- }
3428
- function extractGlobalOptions(args) {
3429
- const opts = {};
3430
- for (let i = 0; i < args.length; i++) {
3431
- const arg = args[i];
3432
- if (arg === "-w" || arg === "--workspace") {
3433
- opts.workspace = args[++i] ?? "";
3434
- } else if (arg === "--no-stream") {
3435
- opts.stream = false;
3436
- } else if (arg === "--stream") {
3437
- opts.stream = true;
3438
- } else if (arg === "--thinking") {
3439
- opts.thinking = true;
3440
- } else if (arg === "--no-thinking") {
3441
- opts.thinking = false;
3442
- }
3443
- }
3444
- return opts;
3445
- }
3446
- function printHelp() {
3447
- console.log(`
3448
- Usage: coder <command> [options] [prompt...]
3449
-
3450
- Coder CLI \u2014 AI-powered coding assistant in the terminal
3451
-
3452
- Options:
3453
- -v, --version output the version number
3454
- -w, --workspace <path> Workspace directory
3455
- --no-stream Disable streaming output
3456
- --thinking Enable deep thinking (for supported models)
3457
- --no-thinking Disable deep thinking
3458
- -h, --help display help for command
3459
-
3460
- Commands:
3461
- ask [prompt...] Ask a question (read-only mode)
3462
- run [prompt...] Run in full agent mode (can modify files, run commands)
3463
- config [key] [value] View or edit configuration
3464
- `);
3465
- }
3466
- main().catch((err) => {
3467
- const message = err instanceof Error ? err.message : String(err);
3468
- writeError(error(`Fatal: ${message}`));
3469
- process.exit(1);
3470
- });
3471
- //# sourceMappingURL=index.cjs.map