@oh-my-pi/pi-coding-agent 12.7.5 → 12.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -37
- package/README.md +9 -1052
- package/package.json +7 -7
- package/src/cli/args.ts +1 -0
- package/src/cli/update-cli.ts +49 -35
- package/src/cli/web-search-cli.ts +3 -2
- package/src/commands/web-search.ts +1 -0
- package/src/config/model-registry.ts +6 -0
- package/src/config/model-resolver.ts +2 -0
- package/src/config/settings-schema.ts +25 -3
- package/src/config/settings.ts +1 -0
- package/src/extensibility/extensions/wrapper.ts +20 -13
- package/src/extensibility/slash-commands.ts +12 -91
- package/src/lsp/client.ts +24 -27
- package/src/lsp/index.ts +92 -42
- package/src/mcp/config-writer.ts +33 -0
- package/src/mcp/config.ts +6 -1
- package/src/mcp/types.ts +1 -0
- package/src/modes/components/custom-editor.ts +8 -5
- package/src/modes/components/settings-defs.ts +2 -1
- package/src/modes/controllers/command-controller.ts +12 -6
- package/src/modes/controllers/input-controller.ts +21 -186
- package/src/modes/controllers/mcp-command-controller.ts +60 -3
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/types.ts +1 -1
- package/src/sdk.ts +23 -1
- package/src/secrets/index.ts +116 -0
- package/src/secrets/obfuscator.ts +269 -0
- package/src/secrets/regex.ts +21 -0
- package/src/session/agent-session.ts +143 -21
- package/src/session/compaction/branch-summarization.ts +2 -2
- package/src/session/compaction/compaction.ts +10 -3
- package/src/session/compaction/utils.ts +25 -1
- package/src/slash-commands/builtin-registry.ts +419 -0
- package/src/web/scrapers/github.ts +50 -12
- package/src/web/search/index.ts +5 -5
- package/src/web/search/provider.ts +13 -2
- package/src/web/search/providers/brave.ts +165 -0
- package/src/web/search/types.ts +1 -1
- package/docs/compaction.md +0 -436
- package/docs/config-usage.md +0 -176
- package/docs/custom-tools.md +0 -585
- package/docs/environment-variables.md +0 -257
- package/docs/extension-loading.md +0 -106
- package/docs/extensions.md +0 -1342
- package/docs/fs-scan-cache-architecture.md +0 -50
- package/docs/hooks.md +0 -906
- package/docs/models.md +0 -234
- package/docs/python-repl.md +0 -110
- package/docs/rpc.md +0 -1173
- package/docs/sdk.md +0 -1039
- package/docs/session-tree-plan.md +0 -84
- package/docs/session.md +0 -368
- package/docs/skills.md +0 -254
- package/docs/theme.md +0 -696
- package/docs/tree.md +0 -206
- package/docs/tui.md +0 -487
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import type { SettingPath, SettingValue } from "../config/settings";
|
|
2
|
+
import { settings } from "../config/settings";
|
|
3
|
+
import type { InteractiveModeContext } from "../modes/types";
|
|
4
|
+
|
|
5
|
+
/** Declarative subcommand definition for commands like /mcp. */
|
|
6
|
+
export interface SubcommandDef {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
/** Usage hint shown as dim ghost text, e.g. "<name> [--scope project|user]". */
|
|
10
|
+
usage?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Declarative builtin slash command definition used by autocomplete and help UI. */
|
|
14
|
+
export interface BuiltinSlashCommand {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
/** Subcommands for dropdown completion (e.g. /mcp add, /mcp list). */
|
|
18
|
+
subcommands?: SubcommandDef[];
|
|
19
|
+
/** Static inline hint when command takes a simple argument (no subcommands). */
|
|
20
|
+
inlineHint?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ParsedBuiltinSlashCommand {
|
|
24
|
+
name: string;
|
|
25
|
+
args: string;
|
|
26
|
+
text: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface BuiltinSlashCommandSpec extends BuiltinSlashCommand {
|
|
30
|
+
aliases?: string[];
|
|
31
|
+
allowArgs?: boolean;
|
|
32
|
+
handle: (command: ParsedBuiltinSlashCommand, runtime: BuiltinSlashCommandRuntime) => Promise<void> | void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface BuiltinSlashCommandRuntime {
|
|
36
|
+
ctx: InteractiveModeContext;
|
|
37
|
+
handleBackgroundCommand: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseBuiltinSlashCommand(text: string): ParsedBuiltinSlashCommand | null {
|
|
41
|
+
if (!text.startsWith("/")) return null;
|
|
42
|
+
const body = text.slice(1);
|
|
43
|
+
if (!body) return null;
|
|
44
|
+
|
|
45
|
+
const firstWhitespace = body.search(/\s/);
|
|
46
|
+
if (firstWhitespace === -1) {
|
|
47
|
+
return {
|
|
48
|
+
name: body,
|
|
49
|
+
args: "",
|
|
50
|
+
text,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: body.slice(0, firstWhitespace),
|
|
56
|
+
args: body.slice(firstWhitespace).trim(),
|
|
57
|
+
text,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const shutdownHandler = (_command: ParsedBuiltinSlashCommand, runtime: BuiltinSlashCommandRuntime): void => {
|
|
62
|
+
runtime.ctx.editor.setText("");
|
|
63
|
+
void runtime.ctx.shutdown();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
67
|
+
{
|
|
68
|
+
name: "settings",
|
|
69
|
+
description: "Open settings menu",
|
|
70
|
+
handle: (_command, runtime) => {
|
|
71
|
+
runtime.ctx.showSettingsSelector();
|
|
72
|
+
runtime.ctx.editor.setText("");
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "plan",
|
|
77
|
+
description: "Toggle plan mode (agent plans before executing)",
|
|
78
|
+
handle: async (_command, runtime) => {
|
|
79
|
+
await runtime.ctx.handlePlanModeCommand();
|
|
80
|
+
runtime.ctx.editor.setText("");
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "model",
|
|
85
|
+
aliases: ["models"],
|
|
86
|
+
description: "Select model (opens selector UI)",
|
|
87
|
+
handle: (_command, runtime) => {
|
|
88
|
+
runtime.ctx.showModelSelector();
|
|
89
|
+
runtime.ctx.editor.setText("");
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "export",
|
|
94
|
+
description: "Export session to HTML file",
|
|
95
|
+
inlineHint: "[path]",
|
|
96
|
+
allowArgs: true,
|
|
97
|
+
handle: async (command, runtime) => {
|
|
98
|
+
await runtime.ctx.handleExportCommand(command.text);
|
|
99
|
+
runtime.ctx.editor.setText("");
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "dump",
|
|
104
|
+
description: "Copy session transcript to clipboard",
|
|
105
|
+
handle: async (_command, runtime) => {
|
|
106
|
+
await runtime.ctx.handleDumpCommand();
|
|
107
|
+
runtime.ctx.editor.setText("");
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "share",
|
|
112
|
+
description: "Share session as a secret GitHub gist",
|
|
113
|
+
handle: async (_command, runtime) => {
|
|
114
|
+
await runtime.ctx.handleShareCommand();
|
|
115
|
+
runtime.ctx.editor.setText("");
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "browser",
|
|
120
|
+
description: "Toggle browser headless vs visible mode",
|
|
121
|
+
subcommands: [
|
|
122
|
+
{ name: "headless", description: "Switch to headless mode" },
|
|
123
|
+
{ name: "visible", description: "Switch to visible mode" },
|
|
124
|
+
],
|
|
125
|
+
allowArgs: true,
|
|
126
|
+
handle: async (command, runtime) => {
|
|
127
|
+
const arg = command.args.toLowerCase();
|
|
128
|
+
const current = settings.get("browser.headless" as SettingPath) as boolean;
|
|
129
|
+
let next = current;
|
|
130
|
+
if (!(settings.get("browser.enabled" as SettingPath) as boolean)) {
|
|
131
|
+
runtime.ctx.showWarning("Browser tool is disabled (enable in settings)");
|
|
132
|
+
runtime.ctx.editor.setText("");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!arg) {
|
|
136
|
+
next = !current;
|
|
137
|
+
} else if (["headless", "hidden"].includes(arg)) {
|
|
138
|
+
next = true;
|
|
139
|
+
} else if (["visible", "show", "headful"].includes(arg)) {
|
|
140
|
+
next = false;
|
|
141
|
+
} else {
|
|
142
|
+
runtime.ctx.showStatus("Usage: /browser [headless|visible]");
|
|
143
|
+
runtime.ctx.editor.setText("");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
settings.set("browser.headless" as SettingPath, next as SettingValue<SettingPath>);
|
|
147
|
+
const tool = runtime.ctx.session.getToolByName("browser");
|
|
148
|
+
if (tool && "restartForModeChange" in tool) {
|
|
149
|
+
try {
|
|
150
|
+
await (tool as { restartForModeChange: () => Promise<void> }).restartForModeChange();
|
|
151
|
+
} catch (error) {
|
|
152
|
+
runtime.ctx.showWarning(
|
|
153
|
+
`Failed to restart browser: ${error instanceof Error ? error.message : String(error)}`,
|
|
154
|
+
);
|
|
155
|
+
runtime.ctx.editor.setText("");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
runtime.ctx.showStatus(`Browser mode: ${next ? "headless" : "visible"}`);
|
|
160
|
+
runtime.ctx.editor.setText("");
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "copy",
|
|
165
|
+
description: "Copy last agent message to clipboard",
|
|
166
|
+
handle: async (_command, runtime) => {
|
|
167
|
+
await runtime.ctx.handleCopyCommand();
|
|
168
|
+
runtime.ctx.editor.setText("");
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "session",
|
|
173
|
+
description: "Show session info and stats",
|
|
174
|
+
handle: async (_command, runtime) => {
|
|
175
|
+
await runtime.ctx.handleSessionCommand();
|
|
176
|
+
runtime.ctx.editor.setText("");
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "usage",
|
|
181
|
+
description: "Show provider usage and limits",
|
|
182
|
+
handle: async (_command, runtime) => {
|
|
183
|
+
await runtime.ctx.handleUsageCommand();
|
|
184
|
+
runtime.ctx.editor.setText("");
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "changelog",
|
|
189
|
+
description: "Show changelog entries",
|
|
190
|
+
subcommands: [{ name: "full", description: "Show complete changelog" }],
|
|
191
|
+
allowArgs: true,
|
|
192
|
+
handle: async (command, runtime) => {
|
|
193
|
+
const showFull = command.args.split(/\s+/).filter(Boolean).includes("full");
|
|
194
|
+
await runtime.ctx.handleChangelogCommand(showFull);
|
|
195
|
+
runtime.ctx.editor.setText("");
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "hotkeys",
|
|
200
|
+
description: "Show all keyboard shortcuts",
|
|
201
|
+
handle: (_command, runtime) => {
|
|
202
|
+
runtime.ctx.handleHotkeysCommand();
|
|
203
|
+
runtime.ctx.editor.setText("");
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "extensions",
|
|
208
|
+
aliases: ["status"],
|
|
209
|
+
description: "Open Extension Control Center dashboard",
|
|
210
|
+
handle: (_command, runtime) => {
|
|
211
|
+
runtime.ctx.showExtensionsDashboard();
|
|
212
|
+
runtime.ctx.editor.setText("");
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "branch",
|
|
217
|
+
description: "Create a new branch from a previous message",
|
|
218
|
+
handle: (_command, runtime) => {
|
|
219
|
+
if (settings.get("doubleEscapeAction") === "tree") {
|
|
220
|
+
runtime.ctx.showTreeSelector();
|
|
221
|
+
} else {
|
|
222
|
+
runtime.ctx.showUserMessageSelector();
|
|
223
|
+
}
|
|
224
|
+
runtime.ctx.editor.setText("");
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: "fork",
|
|
229
|
+
description: "Create a new fork from a previous message",
|
|
230
|
+
handle: async (_command, runtime) => {
|
|
231
|
+
runtime.ctx.editor.setText("");
|
|
232
|
+
await runtime.ctx.handleForkCommand();
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: "tree",
|
|
237
|
+
description: "Navigate session tree (switch branches)",
|
|
238
|
+
handle: (_command, runtime) => {
|
|
239
|
+
runtime.ctx.showTreeSelector();
|
|
240
|
+
runtime.ctx.editor.setText("");
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "login",
|
|
245
|
+
description: "Login with OAuth provider",
|
|
246
|
+
handle: (_command, runtime) => {
|
|
247
|
+
void runtime.ctx.showOAuthSelector("login");
|
|
248
|
+
runtime.ctx.editor.setText("");
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: "logout",
|
|
253
|
+
description: "Logout from OAuth provider",
|
|
254
|
+
handle: (_command, runtime) => {
|
|
255
|
+
void runtime.ctx.showOAuthSelector("logout");
|
|
256
|
+
runtime.ctx.editor.setText("");
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "mcp",
|
|
261
|
+
description: "Manage MCP servers (add, list, remove, test)",
|
|
262
|
+
subcommands: [
|
|
263
|
+
{
|
|
264
|
+
name: "add",
|
|
265
|
+
description: "Add a new MCP server",
|
|
266
|
+
usage: "<name> [--scope project|user] [--url <url>] [-- <command...>]",
|
|
267
|
+
},
|
|
268
|
+
{ name: "list", description: "List all configured MCP servers" },
|
|
269
|
+
{ name: "remove", description: "Remove an MCP server", usage: "<name> [--scope project|user]" },
|
|
270
|
+
{ name: "test", description: "Test connection to a server", usage: "<name>" },
|
|
271
|
+
{ name: "reauth", description: "Reauthorize OAuth for a server", usage: "<name>" },
|
|
272
|
+
{ name: "unauth", description: "Remove OAuth auth from a server", usage: "<name>" },
|
|
273
|
+
{ name: "enable", description: "Enable an MCP server", usage: "<name>" },
|
|
274
|
+
{ name: "disable", description: "Disable an MCP server", usage: "<name>" },
|
|
275
|
+
{ name: "reload", description: "Force reload MCP runtime tools" },
|
|
276
|
+
{ name: "help", description: "Show help message" },
|
|
277
|
+
],
|
|
278
|
+
allowArgs: true,
|
|
279
|
+
handle: async (command, runtime) => {
|
|
280
|
+
runtime.ctx.editor.addToHistory(command.text);
|
|
281
|
+
runtime.ctx.editor.setText("");
|
|
282
|
+
await runtime.ctx.handleMCPCommand(command.text);
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "new",
|
|
287
|
+
description: "Start a new session",
|
|
288
|
+
handle: async (_command, runtime) => {
|
|
289
|
+
runtime.ctx.editor.setText("");
|
|
290
|
+
await runtime.ctx.handleClearCommand();
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "compact",
|
|
295
|
+
description: "Manually compact the session context",
|
|
296
|
+
inlineHint: "[focus instructions]",
|
|
297
|
+
allowArgs: true,
|
|
298
|
+
handle: async (command, runtime) => {
|
|
299
|
+
const customInstructions = command.args || undefined;
|
|
300
|
+
runtime.ctx.editor.setText("");
|
|
301
|
+
await runtime.ctx.handleCompactCommand(customInstructions);
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "handoff",
|
|
306
|
+
description: "Hand off session context to a new session",
|
|
307
|
+
inlineHint: "[focus instructions]",
|
|
308
|
+
allowArgs: true,
|
|
309
|
+
handle: async (command, runtime) => {
|
|
310
|
+
const customInstructions = command.args || undefined;
|
|
311
|
+
runtime.ctx.editor.setText("");
|
|
312
|
+
await runtime.ctx.handleHandoffCommand(customInstructions);
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: "resume",
|
|
317
|
+
description: "Resume a different session",
|
|
318
|
+
handle: (_command, runtime) => {
|
|
319
|
+
runtime.ctx.showSessionSelector();
|
|
320
|
+
runtime.ctx.editor.setText("");
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "background",
|
|
325
|
+
aliases: ["bg"],
|
|
326
|
+
description: "Detach UI and continue running in background",
|
|
327
|
+
handle: (_command, runtime) => {
|
|
328
|
+
runtime.ctx.editor.setText("");
|
|
329
|
+
runtime.handleBackgroundCommand();
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: "debug",
|
|
334
|
+
description: "Write debug log (TUI state and messages)",
|
|
335
|
+
handle: (_command, runtime) => {
|
|
336
|
+
runtime.ctx.showDebugSelector();
|
|
337
|
+
runtime.ctx.editor.setText("");
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: "memory",
|
|
342
|
+
description: "Inspect and operate memory maintenance",
|
|
343
|
+
subcommands: [
|
|
344
|
+
{ name: "view", description: "Show current memory injection payload" },
|
|
345
|
+
{ name: "clear", description: "Clear persisted memory data and artifacts" },
|
|
346
|
+
{ name: "reset", description: "Alias for clear" },
|
|
347
|
+
{ name: "enqueue", description: "Enqueue memory consolidation maintenance" },
|
|
348
|
+
{ name: "rebuild", description: "Alias for enqueue" },
|
|
349
|
+
],
|
|
350
|
+
allowArgs: true,
|
|
351
|
+
handle: async (command, runtime) => {
|
|
352
|
+
runtime.ctx.editor.setText("");
|
|
353
|
+
await runtime.ctx.handleMemoryCommand(command.text);
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: "move",
|
|
358
|
+
description: "Move session to a different working directory",
|
|
359
|
+
inlineHint: "<path>",
|
|
360
|
+
allowArgs: true,
|
|
361
|
+
handle: async (command, runtime) => {
|
|
362
|
+
const targetPath = command.args;
|
|
363
|
+
if (!targetPath) {
|
|
364
|
+
runtime.ctx.showError("Usage: /move <path>");
|
|
365
|
+
runtime.ctx.editor.setText("");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
runtime.ctx.editor.setText("");
|
|
369
|
+
await runtime.ctx.handleMoveCommand(targetPath);
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "exit",
|
|
374
|
+
description: "Exit the application",
|
|
375
|
+
handle: shutdownHandler,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: "quit",
|
|
379
|
+
description: "Quit the application",
|
|
380
|
+
handle: shutdownHandler,
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
const BUILTIN_SLASH_COMMAND_LOOKUP = new Map<string, BuiltinSlashCommandSpec>();
|
|
385
|
+
for (const command of BUILTIN_SLASH_COMMAND_REGISTRY) {
|
|
386
|
+
BUILTIN_SLASH_COMMAND_LOOKUP.set(command.name, command);
|
|
387
|
+
for (const alias of command.aliases ?? []) {
|
|
388
|
+
BUILTIN_SLASH_COMMAND_LOOKUP.set(alias, command);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Builtin command metadata used for slash-command autocomplete and help text. */
|
|
393
|
+
export const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand> = BUILTIN_SLASH_COMMAND_REGISTRY.map(
|
|
394
|
+
command => ({
|
|
395
|
+
name: command.name,
|
|
396
|
+
description: command.description,
|
|
397
|
+
subcommands: command.subcommands,
|
|
398
|
+
inlineHint: command.inlineHint,
|
|
399
|
+
}),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Execute a builtin slash command when it matches known command syntax.
|
|
404
|
+
*
|
|
405
|
+
* Returns true when a builtin command consumed the input; false otherwise.
|
|
406
|
+
*/
|
|
407
|
+
export async function executeBuiltinSlashCommand(text: string, runtime: BuiltinSlashCommandRuntime): Promise<boolean> {
|
|
408
|
+
const parsed = parseBuiltinSlashCommand(text);
|
|
409
|
+
if (!parsed) return false;
|
|
410
|
+
|
|
411
|
+
const command = BUILTIN_SLASH_COMMAND_LOOKUP.get(parsed.name);
|
|
412
|
+
if (!command) return false;
|
|
413
|
+
if (parsed.args.length > 0 && !command.allowArgs) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
await command.handle(parsed, runtime);
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
@@ -11,6 +11,12 @@ interface GitHubUrl {
|
|
|
11
11
|
number?: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
interface GitHubIssueComment {
|
|
15
|
+
user: { login: string };
|
|
16
|
+
created_at: string;
|
|
17
|
+
body: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Parse GitHub URL into components
|
|
16
22
|
*/
|
|
@@ -105,6 +111,44 @@ export async function fetchGitHubApi(
|
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Fetch all issue comments with pagination.
|
|
116
|
+
*/
|
|
117
|
+
async function fetchGitHubIssueComments(
|
|
118
|
+
owner: string,
|
|
119
|
+
repo: string,
|
|
120
|
+
issueNumber: number,
|
|
121
|
+
expectedCount: number,
|
|
122
|
+
timeout: number,
|
|
123
|
+
signal?: AbortSignal,
|
|
124
|
+
): Promise<GitHubIssueComment[]> {
|
|
125
|
+
const perPage = 100;
|
|
126
|
+
const comments: GitHubIssueComment[] = [];
|
|
127
|
+
|
|
128
|
+
for (let page = 1; comments.length < expectedCount; page++) {
|
|
129
|
+
const result = await fetchGitHubApi(
|
|
130
|
+
`/repos/${owner}/${repo}/issues/${issueNumber}/comments?per_page=${perPage}&page=${page}`,
|
|
131
|
+
timeout,
|
|
132
|
+
signal,
|
|
133
|
+
);
|
|
134
|
+
if (!result.ok || !Array.isArray(result.data)) {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const pageComments = result.data as GitHubIssueComment[];
|
|
139
|
+
if (pageComments.length === 0) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
comments.push(...pageComments);
|
|
144
|
+
if (pageComments.length < perPage) {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return comments;
|
|
150
|
+
}
|
|
151
|
+
|
|
108
152
|
/**
|
|
109
153
|
* Render GitHub issue/PR to markdown
|
|
110
154
|
*/
|
|
@@ -146,18 +190,12 @@ async function renderGitHubIssue(
|
|
|
146
190
|
|
|
147
191
|
// Fetch comments if any
|
|
148
192
|
if (issue.comments > 0) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
md += `## Comments (${issue.comments})\n\n`;
|
|
156
|
-
for (const comment of commentsResult.data as Array<{
|
|
157
|
-
user: { login: string };
|
|
158
|
-
created_at: string;
|
|
159
|
-
body: string;
|
|
160
|
-
}>) {
|
|
193
|
+
const comments = await fetchGitHubIssueComments(gh.owner, gh.repo, issue.number, issue.comments, timeout, signal);
|
|
194
|
+
if (comments.length > 0) {
|
|
195
|
+
const commentCount =
|
|
196
|
+
issue.comments > comments.length ? `${comments.length} of ${issue.comments}` : `${comments.length}`;
|
|
197
|
+
md += `## Comments (${commentCount})\n\n`;
|
|
198
|
+
for (const comment of comments) {
|
|
161
199
|
md += `### @${comment.user.login} · ${comment.created_at}\n\n`;
|
|
162
200
|
md += `${comment.body}\n\n---\n\n`;
|
|
163
201
|
}
|
package/src/web/search/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified Web Search Tool
|
|
3
3
|
*
|
|
4
|
-
* Single tool supporting Anthropic, Perplexity, Exa, Jina, Gemini, Codex, and Z.AI
|
|
4
|
+
* Single tool supporting Anthropic, Perplexity, Exa, Brave, Jina, Gemini, Codex, and Z.AI
|
|
5
5
|
* providers with provider-specific parameters exposed conditionally.
|
|
6
6
|
*
|
|
7
7
|
* When EXA_API_KEY is available, additional specialized tools are exposed:
|
|
@@ -33,13 +33,13 @@ import { SearchProviderError } from "./types";
|
|
|
33
33
|
export const webSearchSchema = Type.Object({
|
|
34
34
|
query: Type.String({ description: "Search query" }),
|
|
35
35
|
provider: Type.Optional(
|
|
36
|
-
StringEnum(["auto", "exa", "jina", "zai", "anthropic", "perplexity", "gemini", "codex"], {
|
|
36
|
+
StringEnum(["auto", "exa", "brave", "jina", "zai", "anthropic", "perplexity", "gemini", "codex"], {
|
|
37
37
|
description: "Search provider (default: auto)",
|
|
38
38
|
}),
|
|
39
39
|
),
|
|
40
40
|
recency: Type.Optional(
|
|
41
41
|
StringEnum(["day", "week", "month", "year"], {
|
|
42
|
-
description: "Recency filter (Perplexity)",
|
|
42
|
+
description: "Recency filter (Brave, Perplexity)",
|
|
43
43
|
}),
|
|
44
44
|
),
|
|
45
45
|
limit: Type.Optional(Type.Number({ description: "Max results to return" })),
|
|
@@ -47,7 +47,7 @@ export const webSearchSchema = Type.Object({
|
|
|
47
47
|
|
|
48
48
|
export type SearchParams = {
|
|
49
49
|
query: string;
|
|
50
|
-
provider?: "auto" | "exa" | "jina" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex";
|
|
50
|
+
provider?: "auto" | "exa" | "brave" | "jina" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex";
|
|
51
51
|
recency?: "day" | "week" | "month" | "year";
|
|
52
52
|
limit?: number;
|
|
53
53
|
/** Maximum output tokens. Defaults to 4096. */
|
|
@@ -236,7 +236,7 @@ export async function runSearchQuery(
|
|
|
236
236
|
/**
|
|
237
237
|
* Web search tool implementation.
|
|
238
238
|
*
|
|
239
|
-
* Supports Anthropic, Perplexity, Exa, Jina, Gemini, Codex, and Z.AI providers with automatic fallback.
|
|
239
|
+
* Supports Anthropic, Perplexity, Exa, Brave, Jina, Gemini, Codex, and Z.AI providers with automatic fallback.
|
|
240
240
|
* Session is accepted for interface consistency but not used.
|
|
241
241
|
*/
|
|
242
242
|
export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails> {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AnthropicProvider } from "./providers/anthropic";
|
|
2
2
|
import type { SearchProvider } from "./providers/base";
|
|
3
|
+
import { BraveProvider } from "./providers/brave";
|
|
3
4
|
import { CodexProvider } from "./providers/codex";
|
|
4
5
|
import { ExaProvider } from "./providers/exa";
|
|
5
6
|
import { GeminiProvider } from "./providers/gemini";
|
|
@@ -13,6 +14,7 @@ export { SearchProvider } from "./providers/base";
|
|
|
13
14
|
|
|
14
15
|
const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
|
|
15
16
|
exa: new ExaProvider(),
|
|
17
|
+
brave: new BraveProvider(),
|
|
16
18
|
jina: new JinaProvider(),
|
|
17
19
|
perplexity: new PerplexityProvider(),
|
|
18
20
|
zai: new ZaiProvider(),
|
|
@@ -21,7 +23,16 @@ const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
|
|
|
21
23
|
codex: new CodexProvider(),
|
|
22
24
|
} as const;
|
|
23
25
|
|
|
24
|
-
const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
|
|
26
|
+
const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
|
|
27
|
+
"exa",
|
|
28
|
+
"brave",
|
|
29
|
+
"jina",
|
|
30
|
+
"perplexity",
|
|
31
|
+
"anthropic",
|
|
32
|
+
"gemini",
|
|
33
|
+
"codex",
|
|
34
|
+
"zai",
|
|
35
|
+
];
|
|
25
36
|
|
|
26
37
|
export function getSearchProvider(provider: SearchProviderId): SearchProvider {
|
|
27
38
|
return SEARCH_PROVIDERS[provider];
|
|
@@ -35,7 +46,7 @@ export function setPreferredSearchProvider(provider: SearchProviderId | "auto"):
|
|
|
35
46
|
preferredProvId = provider;
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
/** Determine which providers are configured (priority
|
|
49
|
+
/** Determine which providers are configured (priority: Exa → Brave → Jina → Perplexity → Anthropic → Gemini → Codex → Z.AI) */
|
|
39
50
|
export async function resolveProviderChain(
|
|
40
51
|
preferredProvider: SearchProviderId | "auto" = preferredProvId,
|
|
41
52
|
): Promise<SearchProvider[]> {
|