@trim21/personal-pi-extensions 0.1.650 → 0.1.652
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/README.md +67 -9
- package/package.json +5 -13
- package/src/aft/index.ts +40 -23
- package/src/aft/tools.ts +10 -9
- package/src/claude-code/files.ts +99 -54
- package/src/claude-code/glob.ts +8 -5
- package/src/claude-code/grep.ts +8 -5
- package/src/claude-code/index.ts +56 -10
- package/src/claude-code/search.ts +4 -3
- package/src/claude-code/session-tools.ts +4 -3
- package/src/claude-code/shell.ts +3 -8
- package/src/gh/index.ts +48 -25
- package/src/gh/tools/download-release-assets.ts +3 -3
- package/src/gh/tools/get-workflow-jobs.ts +3 -3
- package/src/gh/tools/list-issues.ts +3 -3
- package/src/gh/tools/list-prs.ts +3 -3
- package/src/gh/tools/list-releases.ts +3 -3
- package/src/gh/tools/list-workflow-runs.ts +3 -3
- package/src/gh/tools/read-ci-logs.ts +3 -4
- package/src/gh/tools/read-issue-comments.ts +3 -3
- package/src/gh/tools/read-issue.ts +3 -3
- package/src/gh/tools/read-pr-comments.ts +3 -3
- package/src/gh/tools/read-pr-diff.ts +3 -3
- package/src/gh/tools/read-pr-status.ts +3 -3
- package/src/gh/tools/read-pr.ts +3 -3
- package/src/gh/tools/read-release.ts +3 -3
- package/src/gh/tools/read-repo.ts +3 -3
- package/src/gh/tools/wait-commit-checks.ts +3 -3
- package/src/gh/tools/wait-pr-checks.ts +3 -3
- package/src/gh/tools/watch-run.ts +3 -3
- package/src/index.ts +120 -0
- package/src/lib/lsp/inspect-tool.ts +5 -5
- package/src/lib/lsp/rename-tool.ts +4 -7
- package/src/lib/tool-bus.ts +126 -0
- package/src/lib/tool-registration.ts +83 -0
- package/src/lib/tool-services.ts +65 -0
- package/src/lib/tool-units.ts +102 -0
- package/src/lib/tools-config.ts +277 -0
- package/src/opencode/bash.ts +7 -2
- package/src/opencode/files.ts +90 -43
- package/src/opencode/glob.ts +7 -1
- package/src/opencode/grep.ts +7 -1
- package/src/opencode/index.ts +66 -21
- package/src/opencode/question.ts +8 -2
- package/src/opencode/todo.ts +8 -2
- package/src/orphan-worker-cleaner.ts +38 -0
- package/src/spawn-agent.ts +160 -175
- package/src/talk/index.ts +92 -66
- package/src/vision-agent.ts +31 -7
- package/src/web/fetch.ts +89 -72
- package/src/web/search.ts +14 -2
- package/src/gh-readonly.ts +0 -10
package/src/vision-agent.ts
CHANGED
|
@@ -28,6 +28,8 @@ import { Value } from "typebox/value";
|
|
|
28
28
|
|
|
29
29
|
import { completeText, type CompleteTextResult, type ModelRegistryLike } from "./lib/model-call.js";
|
|
30
30
|
import { type ToolPendant } from "./lib/pendant.js";
|
|
31
|
+
import type { ToolBus } from "./lib/tool-bus.js";
|
|
32
|
+
import { createToolRegistration } from "./lib/tool-registration.js";
|
|
31
33
|
|
|
32
34
|
// ── constants ────────────────────────────────────────────────────────────────
|
|
33
35
|
|
|
@@ -296,7 +298,23 @@ export async function callVision(
|
|
|
296
298
|
|
|
297
299
|
// ── extension ────────────────────────────────────────────────────────────────
|
|
298
300
|
|
|
299
|
-
|
|
301
|
+
/** 视觉代理工具集(未配置视觉模型时返回 undefined):入口从它取注册函数。 */
|
|
302
|
+
export function createVisionTools(): { register(bus: ToolBus): void } | undefined {
|
|
303
|
+
// 未配置视觉模型就不注册工具:agent 看不到也调不到。provider 的
|
|
304
|
+
// baseUrl/apiKey 由 pi 的模型注册表在调用时解析(execute 里 find),
|
|
305
|
+
// 配置好 settings.json / models.json 后重新加载(/reload)即可生效。
|
|
306
|
+
if (!loadVisionConfig()?.model) {
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
register(bus) {
|
|
311
|
+
registerVisionTool(bus);
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default function visionAgent(pi: ExtensionAPI): void {
|
|
317
|
+
const registration = createToolRegistration(pi);
|
|
300
318
|
// 只依赖 input 字段,不绑定 pi 内部类型
|
|
301
319
|
interface AnyModel {
|
|
302
320
|
id?: string;
|
|
@@ -334,15 +352,21 @@ export default function visionAgent(pi: ExtensionAPI) {
|
|
|
334
352
|
syncVisionMode(ctx.model);
|
|
335
353
|
});
|
|
336
354
|
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const visionConfig = loadVisionConfig();
|
|
341
|
-
if (!visionConfig?.model) {
|
|
355
|
+
// 未配置视觉模型就不注册工具(见 createVisionTools)。
|
|
356
|
+
const tools = createVisionTools();
|
|
357
|
+
if (!tools) {
|
|
342
358
|
return;
|
|
343
359
|
}
|
|
344
360
|
|
|
345
|
-
|
|
361
|
+
// 工具在 session_start 里注册:禁用规则可以带 models,只有那时才知道本会话
|
|
362
|
+
// 的模型。
|
|
363
|
+
registration.onSessionStart(() => {
|
|
364
|
+
tools.register(registration.bus);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function registerVisionTool(bus: ToolBus): void {
|
|
369
|
+
bus.register({
|
|
346
370
|
name: TOOL_NAME,
|
|
347
371
|
label: "Describe Image",
|
|
348
372
|
description:
|
package/src/web/fetch.ts
CHANGED
|
@@ -24,6 +24,8 @@ import { Type } from "typebox";
|
|
|
24
24
|
import { egress } from "../lib/egress.js";
|
|
25
25
|
import { resolvePathArg } from "../lib/path.js";
|
|
26
26
|
import { createRequestPolicy } from "../lib/request-policy.js";
|
|
27
|
+
import type { ToolBus } from "../lib/tool-bus.js";
|
|
28
|
+
import { registerToolsOnSessionStart } from "../lib/tool-registration.js";
|
|
27
29
|
import { guardWriteAccess } from "../lib/write-guard.js";
|
|
28
30
|
|
|
29
31
|
const MAX_REDIRECTS = 5;
|
|
@@ -358,83 +360,98 @@ function truncateMarkdown(text: string): { text: string; truncated: boolean } {
|
|
|
358
360
|
return { text: (cut > 0 ? sliced.slice(0, cut) : sliced) + "\n…(已截断)", truncated: true };
|
|
359
361
|
}
|
|
360
362
|
|
|
361
|
-
export
|
|
362
|
-
//
|
|
363
|
-
//
|
|
363
|
+
export function createWebFetchTool(pi: ExtensionAPI): (bus: ToolBus) => void {
|
|
364
|
+
// 自己建一份非沙盒请求策略并订阅 pi.events:/bwrap-deny-request 在 bash 入口
|
|
365
|
+
// 切换时会同步过来。
|
|
364
366
|
const policy = createRequestPolicy(pi.events);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
Type.
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
absolutePath: destination,
|
|
397
|
-
policy,
|
|
398
|
-
signal,
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
try {
|
|
367
|
+
return (bus) => {
|
|
368
|
+
bus.register({
|
|
369
|
+
name: "web_fetch",
|
|
370
|
+
label: "Web Fetch",
|
|
371
|
+
description:
|
|
372
|
+
"Fetch a URL and return its content as markdown (HTML pages) or raw text " +
|
|
373
|
+
"(JSON/XML/plain-text API responses). With output_path the body is saved to that " +
|
|
374
|
+
"file verbatim instead — any content type, no extraction, no truncation — and the " +
|
|
375
|
+
"result is the JSON summary {url, file_path, content_type, bytes} rather than the " +
|
|
376
|
+
"content; use it for images, logs and other attachments (GitHub user-attachments " +
|
|
377
|
+
"links from issue bodies, release assets, raw files). Give the file the extension " +
|
|
378
|
+
"matching the response's content_type: the Read tool decides image support by " +
|
|
379
|
+
"extension. Requests honour the proxy in ~/.pi/agent/proxy.json, so they reach hosts " +
|
|
380
|
+
"the shell sandbox blocks. SSRF-protected: refuses private/internal addresses.",
|
|
381
|
+
promptSnippet: "Fetch a web page, API response, or download a file",
|
|
382
|
+
parameters: Type.Object({
|
|
383
|
+
url: Type.String({ description: "The URL to fetch" }),
|
|
384
|
+
output_path: Type.Optional(
|
|
385
|
+
Type.String({
|
|
386
|
+
description:
|
|
387
|
+
"Save the response body to this path verbatim instead of returning it (absolute, or relative to the session cwd; ~ is expanded). Parent directories are created and an existing file is overwritten.",
|
|
388
|
+
}),
|
|
389
|
+
),
|
|
390
|
+
}),
|
|
391
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
392
|
+
const destination =
|
|
393
|
+
params.output_path === undefined
|
|
394
|
+
? undefined
|
|
395
|
+
: resolvePathArg(ctx.cwd, params.output_path);
|
|
396
|
+
// 落盘位置的审批与写文件工具同一套:工作区与 /tmp 自动放行,其余问用户。
|
|
397
|
+
// 放在 try 外面,拒绝的原因(user deny)不该被改写成「抓取失败」。
|
|
403
398
|
if (destination !== undefined) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
399
|
+
await guardWriteAccess(ctx, {
|
|
400
|
+
toolName: "web_fetch",
|
|
401
|
+
absolutePath: destination,
|
|
402
|
+
policy,
|
|
403
|
+
signal,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
if (destination !== undefined) {
|
|
409
|
+
const file = await saveUrlToFile(params.url, destination, signal);
|
|
410
|
+
const payload = {
|
|
411
|
+
url: file.url,
|
|
412
|
+
file_path: file.filePath,
|
|
413
|
+
content_type: file.contentType,
|
|
414
|
+
bytes: file.bytes,
|
|
415
|
+
};
|
|
416
|
+
return {
|
|
417
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
418
|
+
details: payload,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const page = await fetchPage(params.url, signal);
|
|
423
|
+
const { text, truncated } = truncateMarkdown(page.markdown);
|
|
424
|
+
const details: Record<string, unknown> = {
|
|
425
|
+
url: page.url,
|
|
426
|
+
title: page.title,
|
|
427
|
+
bytes: Buffer.byteLength(page.markdown, "utf8"),
|
|
428
|
+
truncated,
|
|
429
|
+
};
|
|
430
|
+
return {
|
|
431
|
+
content: [{ type: "text", text }],
|
|
432
|
+
details,
|
|
410
433
|
};
|
|
434
|
+
} catch (error) {
|
|
435
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
436
|
+
const details: Record<string, unknown> = { error: message, url: params.url };
|
|
411
437
|
return {
|
|
412
|
-
|
|
413
|
-
|
|
438
|
+
isError: true,
|
|
439
|
+
content: [{ type: "text", text: `抓取失败: ${message}` }],
|
|
440
|
+
details,
|
|
414
441
|
};
|
|
415
442
|
}
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
};
|
|
446
|
+
}
|
|
416
447
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
content: [{ type: "text", text }],
|
|
427
|
-
details,
|
|
428
|
-
};
|
|
429
|
-
} catch (error) {
|
|
430
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
431
|
-
const details: Record<string, unknown> = { error: message, url: params.url };
|
|
432
|
-
return {
|
|
433
|
-
isError: true,
|
|
434
|
-
content: [{ type: "text", text: `抓取失败: ${message}` }],
|
|
435
|
-
details,
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
});
|
|
448
|
+
/** 工具集入口形态:入口用它取注册函数。 */
|
|
449
|
+
export function createWebFetch(pi: ExtensionAPI): { register(bus: ToolBus): void } {
|
|
450
|
+
const registerTool = createWebFetchTool(pi);
|
|
451
|
+
return { register: registerTool };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** 独立扩展入口:在 session_start 里按本会话模型注册。 */
|
|
455
|
+
export default function webFetchTool(pi: ExtensionAPI): void {
|
|
456
|
+
registerToolsOnSessionStart(pi, createWebFetchTool(pi));
|
|
440
457
|
}
|
package/src/web/search.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { Type } from "typebox";
|
|
|
12
12
|
import { Value } from "typebox/value";
|
|
13
13
|
|
|
14
14
|
import { egress } from "../lib/egress.js";
|
|
15
|
+
import type { ToolBus } from "../lib/tool-bus.js";
|
|
16
|
+
import { registerToolsOnSessionStart } from "../lib/tool-registration.js";
|
|
15
17
|
import { loadSearch1ApiKey } from "./config.js";
|
|
16
18
|
|
|
17
19
|
const SEARCH_URL = "https://api.search1api.com/search";
|
|
@@ -169,8 +171,8 @@ export async function searchWeb(
|
|
|
169
171
|
return { query, hits: mapHits(parsed.results) };
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
export
|
|
173
|
-
|
|
174
|
+
export function registerWebSearchTool(bus: ToolBus): void {
|
|
175
|
+
bus.register({
|
|
174
176
|
name: "web_search",
|
|
175
177
|
label: "Web Search",
|
|
176
178
|
description:
|
|
@@ -285,3 +287,13 @@ export default function webSearchTool(pi: ExtensionAPI): void {
|
|
|
285
287
|
},
|
|
286
288
|
});
|
|
287
289
|
}
|
|
290
|
+
|
|
291
|
+
/** 工具集入口形态:入口用它取注册函数。 */
|
|
292
|
+
export function createWebSearchTool(): { register(bus: ToolBus): void } {
|
|
293
|
+
return { register: registerWebSearchTool };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** 独立扩展入口:在 session_start 里按本会话模型注册。 */
|
|
297
|
+
export default function webSearchTool(pi: ExtensionAPI): void {
|
|
298
|
+
registerToolsOnSessionStart(pi, registerWebSearchTool);
|
|
299
|
+
}
|
package/src/gh-readonly.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub Read-Only Tools Extension — extension entry point.
|
|
3
|
-
*
|
|
4
|
-
* The implementation lives in `src/gh/` (shared helpers in `base.ts`, one tool
|
|
5
|
-
* per file in `tools/`, registration in `index.ts`). This file only forwards
|
|
6
|
-
* the registration function and the public API consumed by tests.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export { default } from "./gh/index.js";
|
|
10
|
-
export * from "./gh/index.js";
|