@trim21/personal-pi-extensions 0.1.684 → 0.1.685

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.
@@ -1,64 +1,79 @@
1
+ /**
2
+ * `list-github-issues`:两条取数分支(关键词搜索走 octokit,浏览走 `gh issue list --json`)
3
+ * 归一成同一套 `SearchHit`,因此文本渲染与结构化载荷只有一份实现。
4
+ */
5
+
1
6
  import { Type } from "typebox";
2
7
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
8
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
9
  import {
10
+ browseList,
5
11
  type GhClient,
6
12
  type ListFilters,
7
- listGithub,
8
- searchList,
13
+ renderHitList,
14
+ type StructuredResultOf,
9
15
  subtitlePendant,
10
16
  type ToolCall,
11
- type ToolResult,
12
17
  toToolResult,
18
+ withStructuredResult,
13
19
  } from "../base.js";
20
+ import { ghHitListPayloadSchema } from "../schemas.js";
14
21
 
15
- async function listIssues(gh: GhClient, call: ToolCall<ListFilters>): Promise<ToolResult> {
22
+ async function listIssues(
23
+ gh: GhClient,
24
+ call: ToolCall<ListFilters>,
25
+ ): Promise<StructuredResultOf<typeof ghHitListPayloadSchema>> {
16
26
  const { params, ctx, signal } = call;
17
- const result = toToolResult(
18
- params.keywords
19
- ? await searchList("issue", params, gh.search)
20
- : await listGithub("issue", params, { cwd: ctx.cwd, signal, input: params }),
21
- params,
22
- );
27
+ const { repo, hits } = params.keywords
28
+ ? { repo: params.repo, hits: await gh.search.search("issue", params) }
29
+ : await browseList("issue", params, { cwd: ctx.cwd, signal, input: params });
30
+ const text = renderHitList("issue", hits, { repo, fields: params.fields });
31
+ const result = toToolResult(text, params);
23
32
  result.details.pendant = subtitlePendant(params);
24
- return result;
33
+ // 文本可能被行数/字节预算截断,载荷给全部命中
34
+ return withStructuredResult(result, { text, items: hits });
25
35
  }
26
36
 
27
37
  export function addListIssuesTool(gh: GhClient, bus: ToolBus) {
28
- bus.register({
29
- name: "list-github-issues",
30
- label: "GitHub Issues List",
31
- description:
32
- 'List GitHub issues with optional filters and keyword search. When repo is omitted, keyword search runs across GitHub. Keyword search defaults to open issues — pass state="all" to include closed ones. Set fields to choose the columns of each result row.',
33
- promptSnippet: "List or search GitHub issues",
34
- parameters: Type.Object({
35
- repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
36
- keywords: Type.Optional(Type.String({ description: "Search keywords (free text)" })),
37
- state: Type.Optional(
38
- Type.String({
39
- description:
40
- "open, closed, all (default: open; all applies to keyword search and covers closed too)",
41
- }),
42
- ),
43
- label: Type.Optional(Type.String({ description: "Filter by label" })),
44
- // `@me` 由 GitHub 的搜索 / 过滤 API 解析:不带 keywords 时 gh 自行展开(GraphQL 过滤分支)
45
- // 或按字面量转发(搜索分支),带 keywords 时我们直接把 `assignee:@me` 交给搜索 API。
46
- // 两条传输都支持,因此本仓库不要再实现一次展开。
47
- author: Type.Optional(Type.String({ description: "Filter by author ('@me' for yourself)" })),
48
- assignee: Type.Optional(
49
- Type.String({ description: "Filter by assignee ('@me' for yourself)" }),
50
- ),
51
- milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
52
- limit: Type.Optional(Type.Number({ description: "Max results (default 30, max 100)" })),
53
- fields: Type.Optional(
54
- Type.String({
55
- description:
56
- "Comma-separated columns for keyword-search rows (default: number,state,title,labels,updatedAt; adds repo when no repo given). Valid: number,state,title,url,author,labels,milestone,assignees,comments,repo,createdAt,updatedAt,closedAt",
57
- }),
58
- ),
38
+ bus.register(
39
+ defineStructuredTool({
40
+ name: "list-github-issues",
41
+ label: "GitHub Issues List",
42
+ description:
43
+ 'List GitHub issues with optional filters and keyword search. When repo is omitted, keyword search runs across GitHub. Keyword search defaults to open issues — pass state="all" to include closed ones. Set fields to choose the columns of each result row.',
44
+ promptSnippet: "List or search GitHub issues",
45
+ parameters: Type.Object({
46
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
47
+ keywords: Type.Optional(Type.String({ description: "Search keywords (free text)" })),
48
+ state: Type.Optional(
49
+ Type.String({
50
+ description:
51
+ "open, closed, all (default: open; all applies to keyword search and covers closed too)",
52
+ }),
53
+ ),
54
+ label: Type.Optional(Type.String({ description: "Filter by label" })),
55
+ // `@me` 由 GitHub 的搜索 / 过滤 API 解析:不带 keywords 时 gh 自行展开(GraphQL 过滤分支)
56
+ // 或按字面量转发(搜索分支),带 keywords 时我们直接把 `assignee:@me` 交给搜索 API。
57
+ // 两条传输都支持,因此本仓库不要再实现一次展开。
58
+ author: Type.Optional(
59
+ Type.String({ description: "Filter by author ('@me' for yourself)" }),
60
+ ),
61
+ assignee: Type.Optional(
62
+ Type.String({ description: "Filter by assignee ('@me' for yourself)" }),
63
+ ),
64
+ milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
65
+ limit: Type.Optional(Type.Number({ description: "Max results (default 30, max 100)" })),
66
+ fields: Type.Optional(
67
+ Type.String({
68
+ description:
69
+ "Comma-separated columns for the rows (default: number,state,title,labels,updatedAt; adds repo when no repo given). Valid: number,state,title,url,author,labels,milestone,assignees,comments,repo,createdAt,updatedAt,closedAt",
70
+ }),
71
+ ),
72
+ }),
73
+ structuredSchema: ghHitListPayloadSchema,
74
+ async execute(_id, params, signal, onUpdate, ctx) {
75
+ return listIssues(gh, { params, ctx, signal, onUpdate });
76
+ },
59
77
  }),
60
- async execute(_id, params, signal, onUpdate, ctx) {
61
- return listIssues(gh, { params, ctx, signal, onUpdate });
62
- },
63
- });
78
+ );
64
79
  }
@@ -1,64 +1,79 @@
1
+ /**
2
+ * `list-github-prs`:与 `list-github-issues` 同一套两条分支(关键词搜索走 octokit,
3
+ * 浏览走 `gh pr list --json`),归一成同一套 `SearchHit`。
4
+ */
5
+
1
6
  import { Type } from "typebox";
2
7
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
8
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
9
  import {
10
+ browseList,
5
11
  type GhClient,
6
12
  type ListFilters,
7
- listGithub,
8
- searchList,
13
+ renderHitList,
14
+ type StructuredResultOf,
9
15
  subtitlePendant,
10
16
  type ToolCall,
11
- type ToolResult,
12
17
  toToolResult,
18
+ withStructuredResult,
13
19
  } from "../base.js";
20
+ import { ghHitListPayloadSchema } from "../schemas.js";
14
21
 
15
- async function listPrs(gh: GhClient, call: ToolCall<ListFilters>): Promise<ToolResult> {
22
+ async function listPrs(
23
+ gh: GhClient,
24
+ call: ToolCall<ListFilters>,
25
+ ): Promise<StructuredResultOf<typeof ghHitListPayloadSchema>> {
16
26
  const { params, ctx, signal } = call;
17
- const result = toToolResult(
18
- params.keywords
19
- ? await searchList("pr", params, gh.search)
20
- : await listGithub("pr", params, { cwd: ctx.cwd, signal, input: params }),
21
- params,
22
- );
27
+ const { repo, hits } = params.keywords
28
+ ? { repo: params.repo, hits: await gh.search.search("pr", params) }
29
+ : await browseList("pr", params, { cwd: ctx.cwd, signal, input: params });
30
+ const text = renderHitList("pr", hits, { repo, fields: params.fields });
31
+ const result = toToolResult(text, params);
23
32
  result.details.pendant = subtitlePendant(params);
24
- return result;
33
+ // 文本可能被行数/字节预算截断,载荷给全部命中
34
+ return withStructuredResult(result, { text, items: hits });
25
35
  }
26
36
 
27
37
  export function addListPrsTool(gh: GhClient, bus: ToolBus) {
28
- bus.register({
29
- name: "list-github-prs",
30
- label: "GitHub PRs List",
31
- description:
32
- 'List GitHub pull requests with optional filters and keyword search. When repo is omitted, keyword search runs across GitHub. Keyword search defaults to open PRs — pass state="merged", state="closed" (merged excluded) or state="all" to broaden. Set fields to choose the columns of each result row.',
33
- promptSnippet: "List or search GitHub PRs",
34
- parameters: Type.Object({
35
- repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
36
- keywords: Type.Optional(Type.String({ description: "Search keywords (free text)" })),
37
- state: Type.Optional(
38
- Type.String({
39
- description:
40
- "open, closed, merged, all (default: open; all applies to keyword search and covers open + closed + merged)",
41
- }),
42
- ),
43
- label: Type.Optional(Type.String({ description: "Filter by label" })),
44
- // `@me` 由 GitHub 的搜索 / 过滤 API 解析:不带 keywords 时 gh 自行展开(GraphQL 过滤分支)
45
- // 或按字面量转发(搜索分支),带 keywords 时我们直接把 `assignee:@me` 交给搜索 API。
46
- // 两条传输都支持,因此本仓库不要再实现一次展开。
47
- author: Type.Optional(Type.String({ description: "Filter by author ('@me' for yourself)" })),
48
- assignee: Type.Optional(
49
- Type.String({ description: "Filter by assignee ('@me' for yourself)" }),
50
- ),
51
- milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
52
- limit: Type.Optional(Type.Number({ description: "Max results (default 30, max 100)" })),
53
- fields: Type.Optional(
54
- Type.String({
55
- description:
56
- "Comma-separated columns for keyword-search rows (default: number,state,title,labels,updatedAt; adds repo when no repo given). Valid: number,state,title,url,author,labels,milestone,assignees,comments,repo,createdAt,updatedAt,closedAt,mergedAt",
57
- }),
58
- ),
38
+ bus.register(
39
+ defineStructuredTool({
40
+ name: "list-github-prs",
41
+ label: "GitHub PRs List",
42
+ description:
43
+ 'List GitHub pull requests with optional filters and keyword search. When repo is omitted, keyword search runs across GitHub. Keyword search defaults to open PRs — pass state="merged", state="closed" (merged excluded) or state="all" to broaden. Set fields to choose the columns of each result row.',
44
+ promptSnippet: "List or search GitHub PRs",
45
+ parameters: Type.Object({
46
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
47
+ keywords: Type.Optional(Type.String({ description: "Search keywords (free text)" })),
48
+ state: Type.Optional(
49
+ Type.String({
50
+ description:
51
+ "open, closed, merged, all (default: open; all applies to keyword search and covers open + closed + merged)",
52
+ }),
53
+ ),
54
+ label: Type.Optional(Type.String({ description: "Filter by label" })),
55
+ // `@me` 由 GitHub 的搜索 / 过滤 API 解析:不带 keywords 时 gh 自行展开(GraphQL 过滤分支)
56
+ // 或按字面量转发(搜索分支),带 keywords 时我们直接把 `assignee:@me` 交给搜索 API。
57
+ // 两条传输都支持,因此本仓库不要再实现一次展开。
58
+ author: Type.Optional(
59
+ Type.String({ description: "Filter by author ('@me' for yourself)" }),
60
+ ),
61
+ assignee: Type.Optional(
62
+ Type.String({ description: "Filter by assignee ('@me' for yourself)" }),
63
+ ),
64
+ milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
65
+ limit: Type.Optional(Type.Number({ description: "Max results (default 30, max 100)" })),
66
+ fields: Type.Optional(
67
+ Type.String({
68
+ description:
69
+ "Comma-separated columns for the rows (default: number,state,title,labels,updatedAt; adds repo when no repo given). Valid: number,state,title,url,author,labels,milestone,assignees,comments,repo,createdAt,updatedAt,closedAt,mergedAt",
70
+ }),
71
+ ),
72
+ }),
73
+ structuredSchema: ghHitListPayloadSchema,
74
+ async execute(_id, params, signal, onUpdate, ctx) {
75
+ return listPrs(gh, { params, ctx, signal, onUpdate });
76
+ },
59
77
  }),
60
- async execute(_id, params, signal, onUpdate, ctx) {
61
- return listPrs(gh, { params, ctx, signal, onUpdate });
62
- },
63
- });
78
+ );
64
79
  }
@@ -1,30 +1,47 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
3
+ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
+ import {
6
+ type GhClient,
7
+ ghExec,
8
+ repoArgs,
9
+ subtitlePendant,
10
+ toToolResult,
11
+ withStructuredResult,
12
+ } from "../base.js";
13
+ import { renderReleaseList } from "../render.js";
14
+ import { ghReleaseListPayloadSchema, ghReleaseSummarySchema } from "../schemas.js";
15
+
16
+ /** 我们向 gh 要的 release 字段,与渲染和载荷一一对应。 */
17
+ const RELEASE_FIELDS = "tagName,name,isLatest,isPrerelease,isDraft,publishedAt,createdAt";
5
18
 
6
19
  export function addListReleasesTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "list-github-releases",
9
- label: "GitHub Releases List",
10
- description: "List GitHub releases.",
11
- promptSnippet: "List GitHub releases",
12
- parameters: Type.Object({
13
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
14
- limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
20
+ bus.register(
21
+ defineStructuredTool({
22
+ name: "list-github-releases",
23
+ label: "GitHub Releases List",
24
+ description:
25
+ "List GitHub releases. Each row is: tag, flags (latest/prerelease/draft), published date, name.",
26
+ promptSnippet: "List GitHub releases",
27
+ parameters: Type.Object({
28
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
29
+ limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
30
+ }),
31
+ structuredSchema: ghReleaseListPayloadSchema,
32
+ async execute(_id, params, signal, _onUpdate, ctx) {
33
+ const { repo, limit } = params;
34
+ const args = ["release", "list", ...repoArgs(repo), "--json", RELEASE_FIELDS];
35
+ if (limit) {
36
+ args.push("--limit", String(limit));
37
+ }
38
+ const stdout = await ghExec(args, { cwd: ctx.cwd, signal, input: params });
39
+ const releases = parseWithSchema(Type.Array(ghReleaseSummarySchema), JSON.parse(stdout));
40
+ const text = renderReleaseList(releases);
41
+ const result = toToolResult(text, params);
42
+ result.details.pendant = subtitlePendant(params);
43
+ return withStructuredResult(result, { text, releases });
44
+ },
15
45
  }),
16
- async execute(_id, params, signal, _onUpdate, ctx) {
17
- const { repo, limit } = params;
18
- const args = ["release", "list", ...repoArgs(repo)];
19
- if (limit) {
20
- args.push("--limit", String(limit));
21
- }
22
- const result = toToolResult(
23
- await ghExec(args, { cwd: ctx.cwd, signal, input: params }),
24
- params,
25
- );
26
- result.details.pendant = subtitlePendant(params);
27
- return result;
28
- },
29
- });
46
+ );
30
47
  }
@@ -1,40 +1,58 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
3
+ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
+ import {
6
+ type GhClient,
7
+ ghExec,
8
+ repoArgs,
9
+ subtitlePendant,
10
+ toToolResult,
11
+ withStructuredResult,
12
+ } from "../base.js";
13
+ import { renderRunList } from "../render.js";
14
+ import { ghRunListPayloadSchema, ghRunSummarySchema } from "../schemas.js";
15
+
16
+ /** 我们向 gh 要的运行字段,与渲染和载荷一一对应。 */
17
+ const RUN_FIELDS =
18
+ "databaseId,displayTitle,status,conclusion,workflowName,headBranch,event,createdAt,url";
5
19
 
6
20
  export function addListWorkflowRunsTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "list-github-workflow-runs",
9
- label: "GitHub Workflow Runs",
10
- description: "List GitHub Actions workflow runs.",
11
- promptSnippet: "List GitHub workflow runs",
12
- parameters: Type.Object({
13
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
14
- limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
15
- status: Type.Optional(
16
- Type.String({ description: "Filter by status: success, failure, cancelled, etc." }),
17
- ),
18
- workflow: Type.Optional(Type.String({ description: "Filter by workflow name or file" })),
21
+ bus.register(
22
+ defineStructuredTool({
23
+ name: "list-github-workflow-runs",
24
+ label: "GitHub Workflow Runs",
25
+ description:
26
+ "List GitHub Actions workflow runs. Each row is: id, status, conclusion, workflow, branch, event, created date, url.",
27
+ promptSnippet: "List GitHub workflow runs",
28
+ parameters: Type.Object({
29
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
30
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
31
+ status: Type.Optional(
32
+ Type.String({ description: "Filter by status: success, failure, cancelled, etc." }),
33
+ ),
34
+ workflow: Type.Optional(Type.String({ description: "Filter by workflow name or file" })),
35
+ }),
36
+ structuredSchema: ghRunListPayloadSchema,
37
+ async execute(_id, params, signal, _onUpdate, ctx) {
38
+ const { repo, limit, status, workflow } = params;
39
+ const args = ["run", "list", ...repoArgs(repo), "--json", RUN_FIELDS];
40
+ if (limit) {
41
+ args.push("--limit", String(limit));
42
+ }
43
+ if (status) {
44
+ args.push("--status", status);
45
+ }
46
+ if (workflow) {
47
+ args.push("--workflow", workflow);
48
+ }
49
+ const stdout = await ghExec(args, { cwd: ctx.cwd, signal, input: params });
50
+ const runs = parseWithSchema(Type.Array(ghRunSummarySchema), JSON.parse(stdout));
51
+ const text = renderRunList(runs);
52
+ const result = toToolResult(text, params);
53
+ result.details.pendant = subtitlePendant(params);
54
+ return withStructuredResult(result, { text, runs });
55
+ },
19
56
  }),
20
- async execute(_id, params, signal, _onUpdate, ctx) {
21
- const { repo, limit, status, workflow } = params;
22
- const args = ["run", "list", ...repoArgs(repo)];
23
- if (limit) {
24
- args.push("--limit", String(limit));
25
- }
26
- if (status) {
27
- args.push("--status", status);
28
- }
29
- if (workflow) {
30
- args.push("--workflow", workflow);
31
- }
32
- const result = toToolResult(
33
- await ghExec(args, { cwd: ctx.cwd, signal, input: params }),
34
- params,
35
- );
36
- result.details.pendant = subtitlePendant(params);
37
- return result;
38
- },
39
- });
57
+ );
40
58
  }
@@ -1,27 +1,38 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
3
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
+ import {
5
+ type GhClient,
6
+ ghExec,
7
+ repoArgs,
8
+ subtitlePendant,
9
+ toToolResult,
10
+ withStructuredResult,
11
+ } from "../base.js";
12
+ import { parseDiffStats } from "../render.js";
13
+ import { ghDiffPayloadSchema } from "../schemas.js";
5
14
 
6
15
  export function addReadPrDiffTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "read-github-pr-diff",
9
- label: "GitHub PR Diff",
10
- description: "Get the diff of a GitHub pull request.",
11
- promptSnippet: "Read a GitHub PR diff",
12
- parameters: Type.Object({
13
- number: Type.Union([Type.Number(), Type.String()], { description: "PR number" }),
14
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
16
+ bus.register(
17
+ defineStructuredTool({
18
+ name: "read-github-pr-diff",
19
+ label: "GitHub PR Diff",
20
+ description: "Get the diff of a GitHub pull request.",
21
+ promptSnippet: "Read a GitHub PR diff",
22
+ parameters: Type.Object({
23
+ number: Type.Union([Type.Number(), Type.String()], { description: "PR number" }),
24
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
25
+ }),
26
+ structuredSchema: ghDiffPayloadSchema,
27
+ async execute(_id, params, signal, _onUpdate, ctx) {
28
+ const { number, repo } = params;
29
+ const args = ["pr", "diff", String(number), ...repoArgs(repo)];
30
+ const stdout = await ghExec(args, { cwd: ctx.cwd, signal, input: params });
31
+ const result = toToolResult(stdout, params);
32
+ result.details.pendant = subtitlePendant(params, "number");
33
+ // 文本仍是原始 diff(`gh pr diff` 没有 --json),载荷从同一份 diff 解析出变更统计
34
+ return withStructuredResult(result, { text: stdout, ...parseDiffStats(stdout) });
35
+ },
15
36
  }),
16
- async execute(_id, params, signal, _onUpdate, ctx) {
17
- const { number, repo } = params;
18
- const args = ["pr", "diff", String(number), ...repoArgs(repo)];
19
- const result = toToolResult(
20
- await ghExec(args, { cwd: ctx.cwd, signal, input: params }),
21
- params,
22
- );
23
- result.details.pendant = subtitlePendant(params, "number");
24
- return result;
25
- },
26
- });
37
+ );
27
38
  }
@@ -1,30 +1,58 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
3
+ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
+ import {
6
+ type GhClient,
7
+ ghExec,
8
+ repoArgs,
9
+ subtitlePendant,
10
+ toToolResult,
11
+ withStructuredResult,
12
+ } from "../base.js";
13
+ import { renderReleaseView } from "../render.js";
14
+ import { ghReleaseViewPayloadSchema, ghReleaseViewSchema } from "../schemas.js";
15
+
16
+ /** 我们向 gh 要的 release 字段,与渲染和载荷一一对应。 */
17
+ const RELEASE_FIELDS = [
18
+ "tagName",
19
+ "name",
20
+ "body",
21
+ "url",
22
+ "isDraft",
23
+ "isPrerelease",
24
+ "createdAt",
25
+ "publishedAt",
26
+ "targetCommitish",
27
+ "author",
28
+ "assets",
29
+ ].join(",");
5
30
 
6
31
  export function addReadReleaseTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "read-github-release",
9
- label: "GitHub Release",
10
- description: "Get details of a specific GitHub release by tag.",
11
- promptSnippet: "Read a GitHub release",
12
- parameters: Type.Object({
13
- tag: Type.String({ description: "Release tag name" }),
14
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
32
+ bus.register(
33
+ defineStructuredTool({
34
+ name: "read-github-release",
35
+ label: "GitHub Release",
36
+ description:
37
+ "Get details of a specific GitHub release by tag: metadata, asset list and release notes.",
38
+ promptSnippet: "Read a GitHub release",
39
+ parameters: Type.Object({
40
+ tag: Type.String({ description: "Release tag name" }),
41
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
42
+ }),
43
+ structuredSchema: ghReleaseViewPayloadSchema,
44
+ async execute(_id, params, signal, _onUpdate, ctx) {
45
+ const { tag, repo } = params;
46
+ const stdout = await ghExec(
47
+ ["release", "view", tag, ...repoArgs(repo), "--json", RELEASE_FIELDS],
48
+ { cwd: ctx.cwd, signal, input: params },
49
+ );
50
+ const view = parseWithSchema(ghReleaseViewSchema, JSON.parse(stdout));
51
+ const text = renderReleaseView(view);
52
+ const result = toToolResult(text, params);
53
+ result.details.pendant = subtitlePendant(params, "tag");
54
+ return withStructuredResult(result, { text, release: view });
55
+ },
15
56
  }),
16
- async execute(_id, params, signal, _onUpdate, ctx) {
17
- const { tag, repo } = params;
18
- const result = toToolResult(
19
- await ghExec(["release", "view", tag, ...repoArgs(repo)], {
20
- cwd: ctx.cwd,
21
- signal,
22
- input: params,
23
- }),
24
- params,
25
- );
26
- result.details.pendant = subtitlePendant(params, "tag");
27
- return result;
28
- },
29
- });
57
+ );
30
58
  }