@nghyane/arcane 0.1.13 → 0.1.15
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 +28 -0
- package/package.json +21 -70
- package/scripts/format-prompts.ts +1 -3
- package/src/cli/args.ts +2 -7
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli.ts +0 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/grep.ts +1 -1
- package/src/commands/jupyter.ts +1 -1
- package/src/commands/plugin.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/shell.ts +1 -1
- package/src/commands/ssh.ts +1 -1
- package/src/commands/stats.ts +1 -1
- package/src/commands/update.ts +1 -1
- package/src/config/model-registry.ts +3 -4
- package/src/config/model-resolver.ts +36 -9
- package/src/config/prompt-templates.ts +1 -9
- package/src/config/settings-schema.ts +32 -88
- package/src/config/settings.ts +3 -4
- package/src/debug/index.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +2 -2
- package/src/discovery/helpers.ts +13 -3
- package/src/exa/index.ts +1 -35
- package/src/exa/render.ts +30 -190
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +5 -1
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/extensions/wrapper.ts +7 -15
- package/src/extensibility/hooks/runner.ts +1 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +1 -1
- package/src/index.ts +13 -13
- package/src/lsp/index.ts +77 -24
- package/src/lsp/render.ts +34 -583
- package/src/lsp/types.ts +3 -3
- package/src/lsp/utils.ts +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/tool-bridge.ts +1 -24
- package/src/modes/components/assistant-message.ts +7 -7
- package/src/modes/components/bash-execution.ts +50 -112
- package/src/modes/components/bordered-loader.ts +1 -1
- package/src/modes/components/branch-summary-message.ts +16 -10
- package/src/modes/components/compaction-summary-message.ts +20 -12
- package/src/modes/components/context-group.ts +106 -0
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/dynamic-border.ts +1 -1
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +2 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-input.ts +1 -1
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/index.ts +0 -2
- package/src/modes/components/keybinding-hints.ts +1 -1
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/oauth-selector.ts +1 -1
- package/src/modes/components/plugin-settings.ts +1 -1
- package/src/modes/components/python-execution.ts +51 -91
- package/src/modes/components/queue-mode-selector.ts +1 -1
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +5 -10
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +1 -1
- package/src/modes/components/skill-message.ts +4 -4
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/status-line/separators.ts +1 -1
- package/src/modes/components/status-line-segment-editor.ts +1 -1
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/components/theme-selector.ts +1 -1
- package/src/modes/components/thinking-selector.ts +1 -1
- package/src/modes/components/todo-display.ts +2 -4
- package/src/modes/components/todo-reminder.ts +4 -4
- package/src/modes/components/tool-execution.ts +118 -440
- package/src/modes/components/tool-image-display.ts +107 -0
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/components/ttsr-notification.ts +4 -17
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +9 -10
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +58 -187
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +3 -1
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +3 -26
- package/src/modes/controllers/ssh-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +3 -7
- package/src/modes/print-mode.ts +5 -5
- package/src/modes/rpc/rpc-mode.ts +1 -1
- package/src/modes/types.ts +1 -2
- package/src/modes/utils/ui-helpers.ts +34 -32
- package/src/patch/edit-tool.ts +742 -0
- package/src/patch/index.ts +32 -898
- package/src/patch/schemas.ts +208 -0
- package/src/patch/shared.ts +83 -151
- package/src/prompts/agents/explore.md +22 -37
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/agents/librarian.md +29 -20
- package/src/prompts/agents/oracle.md +9 -2
- package/src/prompts/agents/reviewer.md +14 -48
- package/src/prompts/agents/task.md +16 -8
- package/src/prompts/compaction/branch-summary.md +4 -1
- package/src/prompts/compaction/compaction-summary.md +4 -1
- package/src/prompts/system/subagent-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +162 -178
- package/src/prompts/system/verification-reminder.md +6 -0
- package/src/sdk.ts +0 -9
- package/src/session/agent-session.ts +244 -1459
- package/src/session/model-controller.ts +406 -0
- package/src/session/retry-utils.ts +71 -0
- package/src/session/session-manager.ts +22 -186
- package/src/session/session-types.ts +312 -0
- package/src/session/stats.ts +387 -0
- package/src/session/streaming-edit.ts +258 -0
- package/src/session/ttsr.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +0 -8
- package/src/stt/recorder.ts +2 -2
- package/src/system-prompt.ts +1 -14
- package/src/task/agents.ts +7 -33
- package/src/task/executor.ts +50 -438
- package/src/task/index.ts +104 -71
- package/src/task/progress-tracker.ts +390 -0
- package/src/task/render.ts +371 -187
- package/src/task/subprocess-tool-registry.ts +1 -1
- package/src/task/types.ts +14 -47
- package/src/tools/ask.ts +31 -42
- package/src/tools/bash-interactive.ts +2 -2
- package/src/tools/bash-interceptor.ts +2 -2
- package/src/tools/bash-normalize.ts +1 -1
- package/src/tools/bash-skill-urls.ts +2 -2
- package/src/tools/bash.ts +87 -136
- package/src/tools/browser.ts +54 -84
- package/src/tools/create-tools.ts +186 -0
- package/src/tools/default-renderer.ts +104 -0
- package/src/tools/explore.ts +11 -10
- package/src/tools/fetch.ts +24 -114
- package/src/tools/find.ts +48 -132
- package/src/tools/gemini-image.ts +5 -15
- package/src/tools/github.ts +450 -0
- package/src/tools/grep.ts +43 -179
- package/src/tools/index.ts +35 -198
- package/src/tools/json-tree.ts +3 -3
- package/src/tools/librarian.ts +18 -18
- package/src/tools/list-limit.ts +2 -2
- package/src/tools/notebook.ts +35 -87
- package/src/tools/oracle.ts +25 -25
- package/src/tools/output-meta.ts +89 -4
- package/src/tools/output-utils.ts +2 -2
- package/src/tools/python.ts +86 -637
- package/src/tools/read.ts +36 -119
- package/src/tools/reviewer-tool.ts +19 -21
- package/src/tools/search-code.ts +128 -0
- package/src/tools/ssh.ts +67 -126
- package/src/tools/subagent-tool.ts +197 -123
- package/src/tools/todo-write.ts +15 -31
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/undo-edit.ts +30 -67
- package/src/tools/write.ts +78 -127
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/file-list.ts +2 -2
- package/src/tui/output-block.ts +1 -1
- package/src/tui/status-line.ts +1 -1
- package/src/tui/tree-list.ts +2 -2
- package/src/tui/types.ts +1 -1
- package/src/tui/utils.ts +1 -1
- package/src/{tools → ui}/render-utils.ts +87 -126
- package/src/utils/external-editor.ts +4 -4
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/index.ts +30 -0
- package/src/utils/tools-manager.ts +9 -19
- package/src/web/github-client.ts +290 -0
- package/src/web/scrapers/github.ts +11 -62
- package/src/web/search/auth.ts +1 -3
- package/src/web/search/index.ts +82 -46
- package/src/web/search/provider.ts +11 -16
- package/src/web/search/providers/grep.ts +160 -0
- package/src/web/search/render.ts +48 -235
- package/src/web/search/types.ts +1 -1
- package/src/commands/commit.ts +0 -36
- package/src/commit/agentic/agent.ts +0 -311
- package/src/commit/agentic/fallback.ts +0 -96
- package/src/commit/agentic/index.ts +0 -359
- package/src/commit/agentic/prompts/analyze-file.md +0 -22
- package/src/commit/agentic/prompts/session-user.md +0 -25
- package/src/commit/agentic/prompts/split-confirm.md +0 -1
- package/src/commit/agentic/prompts/system.md +0 -38
- package/src/commit/agentic/state.ts +0 -69
- package/src/commit/agentic/tools/analyze-file.ts +0 -118
- package/src/commit/agentic/tools/git-file-diff.ts +0 -194
- package/src/commit/agentic/tools/git-hunk.ts +0 -50
- package/src/commit/agentic/tools/git-overview.ts +0 -84
- package/src/commit/agentic/tools/index.ts +0 -56
- package/src/commit/agentic/tools/propose-changelog.ts +0 -128
- package/src/commit/agentic/tools/propose-commit.ts +0 -154
- package/src/commit/agentic/tools/recent-commits.ts +0 -81
- package/src/commit/agentic/tools/split-commit.ts +0 -280
- package/src/commit/agentic/topo-sort.ts +0 -44
- package/src/commit/agentic/trivial.ts +0 -51
- package/src/commit/agentic/validation.ts +0 -200
- package/src/commit/analysis/conventional.ts +0 -165
- package/src/commit/analysis/index.ts +0 -4
- package/src/commit/analysis/scope.ts +0 -242
- package/src/commit/analysis/summary.ts +0 -112
- package/src/commit/analysis/validation.ts +0 -66
- package/src/commit/changelog/detect.ts +0 -37
- package/src/commit/changelog/generate.ts +0 -110
- package/src/commit/changelog/index.ts +0 -234
- package/src/commit/changelog/parse.ts +0 -44
- package/src/commit/cli.ts +0 -93
- package/src/commit/git/diff.ts +0 -148
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -211
- package/src/commit/git/operations.ts +0 -54
- package/src/commit/index.ts +0 -5
- package/src/commit/map-reduce/index.ts +0 -64
- package/src/commit/map-reduce/map-phase.ts +0 -178
- package/src/commit/map-reduce/reduce-phase.ts +0 -145
- package/src/commit/map-reduce/utils.ts +0 -9
- package/src/commit/message.ts +0 -11
- package/src/commit/model-selection.ts +0 -69
- package/src/commit/pipeline.ts +0 -243
- package/src/commit/prompts/analysis-system.md +0 -148
- package/src/commit/prompts/analysis-user.md +0 -38
- package/src/commit/prompts/changelog-system.md +0 -50
- package/src/commit/prompts/changelog-user.md +0 -18
- package/src/commit/prompts/file-observer-system.md +0 -24
- package/src/commit/prompts/file-observer-user.md +0 -8
- package/src/commit/prompts/reduce-system.md +0 -50
- package/src/commit/prompts/reduce-user.md +0 -17
- package/src/commit/prompts/summary-retry.md +0 -3
- package/src/commit/prompts/summary-system.md +0 -38
- package/src/commit/prompts/summary-user.md +0 -13
- package/src/commit/prompts/types-description.md +0 -2
- package/src/commit/types.ts +0 -109
- package/src/commit/utils/exclusions.ts +0 -42
- package/src/mcp/render.ts +0 -123
- package/src/modes/components/agent-dashboard.ts +0 -1130
- package/src/modes/components/codemode-group.ts +0 -369
- package/src/modes/components/read-tool-group.ts +0 -119
- package/src/modes/components/visual-truncate.ts +0 -63
- package/src/prompts/system/subagent-user-prompt.md +0 -8
- package/src/prompts/tools/ask.md +0 -44
- package/src/prompts/tools/bash.md +0 -24
- package/src/prompts/tools/browser.md +0 -33
- package/src/prompts/tools/calculator.md +0 -12
- package/src/prompts/tools/explore.md +0 -29
- package/src/prompts/tools/fetch.md +0 -16
- package/src/prompts/tools/find.md +0 -18
- package/src/prompts/tools/gemini-image.md +0 -23
- package/src/prompts/tools/grep.md +0 -28
- package/src/prompts/tools/hashline.md +0 -232
- package/src/prompts/tools/librarian.md +0 -24
- package/src/prompts/tools/lsp.md +0 -28
- package/src/prompts/tools/oracle.md +0 -26
- package/src/prompts/tools/patch.md +0 -74
- package/src/prompts/tools/python.md +0 -66
- package/src/prompts/tools/read.md +0 -36
- package/src/prompts/tools/replace.md +0 -38
- package/src/prompts/tools/reviewer.md +0 -41
- package/src/prompts/tools/ssh.md +0 -51
- package/src/prompts/tools/task-summary.md +0 -28
- package/src/prompts/tools/task.md +0 -146
- package/src/prompts/tools/todo-write.md +0 -65
- package/src/prompts/tools/undo-edit.md +0 -7
- package/src/prompts/tools/web-search.md +0 -19
- package/src/prompts/tools/write.md +0 -18
- package/src/task/batch.ts +0 -102
- package/src/task/discovery.ts +0 -126
- package/src/task/parallel.ts +0 -84
- package/src/task/template.ts +0 -32
- package/src/tools/calculator.ts +0 -537
- package/src/tools/jtd-to-typescript.ts +0 -198
- package/src/tools/renderers.ts +0 -60
- package/src/tools/tool-result.ts +0 -86
- /package/src/{modes/theme → theme}/dark.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-dracula.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-gruvbox.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-solarized.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-tokyo-night.json +0 -0
- /package/src/{modes/theme → theme}/defaults/index.ts +0 -0
- /package/src/{modes/theme → theme}/defaults/light-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-github.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-solarized.json +0 -0
- /package/src/{modes/theme → theme}/light.json +0 -0
- /package/src/{modes/theme → theme}/mermaid-cache.ts +0 -0
- /package/src/{modes/theme → theme}/theme-schema.json +0 -0
- /package/src/{modes/theme → theme}/theme.ts +0 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolResult } from "@nghyane/arcane-agent";
|
|
2
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
3
|
+
import type { Theme } from "../theme/theme";
|
|
4
|
+
import { type GitHubResponse, githubClient } from "../web/github-client";
|
|
5
|
+
import type { ToolSession } from ".";
|
|
6
|
+
import { type OutputMeta, toolResult } from "./output-meta";
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Schema
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
const ActionEnum = Type.Union([
|
|
13
|
+
Type.Literal("get_repo"),
|
|
14
|
+
Type.Literal("get_file"),
|
|
15
|
+
Type.Literal("get_tree"),
|
|
16
|
+
Type.Literal("search_repos"),
|
|
17
|
+
Type.Literal("get_issue"),
|
|
18
|
+
Type.Literal("list_issues"),
|
|
19
|
+
Type.Literal("get_pull"),
|
|
20
|
+
Type.Literal("list_pulls"),
|
|
21
|
+
Type.Literal("list_commits"),
|
|
22
|
+
Type.Literal("get_commit"),
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const schema = Type.Object({
|
|
26
|
+
action: ActionEnum,
|
|
27
|
+
owner: Type.String({ description: "Repository owner (user or org)" }),
|
|
28
|
+
repo: Type.String({ description: "Repository name" }),
|
|
29
|
+
path: Type.Optional(Type.String({ description: "File or directory path within the repo" })),
|
|
30
|
+
ref: Type.Optional(Type.String({ description: "Branch, tag, or commit SHA" })),
|
|
31
|
+
number: Type.Optional(Type.Number({ description: "Issue or PR number" })),
|
|
32
|
+
query: Type.Optional(Type.String({ description: "Search query or SSR pattern" })),
|
|
33
|
+
state: Type.Optional(Type.String({ description: "Filter by state (open, closed, all)" })),
|
|
34
|
+
labels: Type.Optional(Type.String({ description: "Comma-separated label filter" })),
|
|
35
|
+
sha: Type.Optional(Type.String({ description: "Commit SHA" })),
|
|
36
|
+
include_diff: Type.Optional(Type.Boolean({ description: "Include diff in commit details" })),
|
|
37
|
+
recursive: Type.Optional(Type.Boolean({ description: "Recursively list tree contents" })),
|
|
38
|
+
limit: Type.Optional(Type.Number({ description: "Max number of results" })),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
type GitHubInput = Static<typeof schema>;
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Details
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
export interface GitHubToolDetails {
|
|
48
|
+
action: string;
|
|
49
|
+
owner: string;
|
|
50
|
+
repo: string;
|
|
51
|
+
meta?: OutputMeta;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Response Formatters
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
function formatRepo(data: any): string {
|
|
59
|
+
return [
|
|
60
|
+
`# ${data.full_name}`,
|
|
61
|
+
data.description ? `${data.description}` : "",
|
|
62
|
+
"",
|
|
63
|
+
`- Default branch: ${data.default_branch}`,
|
|
64
|
+
`- Language: ${data.language ?? "N/A"}`,
|
|
65
|
+
`- Stars: ${data.stargazers_count} | Forks: ${data.forks_count}`,
|
|
66
|
+
`- Topics: ${data.topics?.length ? data.topics.join(", ") : "none"}`,
|
|
67
|
+
`- License: ${data.license?.spdx_id ?? "N/A"}`,
|
|
68
|
+
`- Created: ${data.created_at} | Updated: ${data.updated_at}`,
|
|
69
|
+
data.homepage ? `- Homepage: ${data.homepage}` : "",
|
|
70
|
+
]
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.join("\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function formatTreeEntry(entry: any): string {
|
|
76
|
+
const icon = entry.type === "dir" || entry.type === "tree" ? "dir" : "file";
|
|
77
|
+
const size = entry.size ? ` (${formatSize(entry.size)})` : "";
|
|
78
|
+
const name = entry.path ?? entry.name;
|
|
79
|
+
return `[${icon}] ${name}${size}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatSize(bytes: number): string {
|
|
83
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
84
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
|
|
85
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatIssue(data: any, comments: any[] = []): string {
|
|
89
|
+
const lines = [
|
|
90
|
+
`# #${data.number}: ${data.title}`,
|
|
91
|
+
`State: ${data.state} | Author: @${data.user?.login} | Created: ${data.created_at}`,
|
|
92
|
+
data.labels?.length ? `Labels: ${data.labels.map((l: any) => l.name).join(", ")}` : "",
|
|
93
|
+
data.assignees?.length ? `Assignees: ${data.assignees.map((a: any) => `@${a.login}`).join(", ")}` : "",
|
|
94
|
+
"",
|
|
95
|
+
data.body ?? "(no description)",
|
|
96
|
+
].filter(l => l !== "");
|
|
97
|
+
|
|
98
|
+
for (const comment of comments) {
|
|
99
|
+
lines.push("", `---`, `**@${comment.user?.login}** on ${comment.created_at}:`, "", comment.body ?? "");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatIssueMinimal(issue: any): string {
|
|
106
|
+
const labels = issue.labels?.length ? ` [${issue.labels.map((l: any) => l.name).join(", ")}]` : "";
|
|
107
|
+
return `#${issue.number} [${issue.state}] ${issue.title}${labels} (@${issue.user?.login}, ${issue.created_at})`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatPR(data: any, diff?: string): string {
|
|
111
|
+
const lines = [
|
|
112
|
+
`# PR #${data.number}: ${data.title}`,
|
|
113
|
+
`State: ${data.state}${data.merged ? " (merged)" : ""} | Author: @${data.user?.login}`,
|
|
114
|
+
`Base: ${data.base?.ref} <- Head: ${data.head?.ref}`,
|
|
115
|
+
`Changed files: ${data.changed_files ?? "?"} | +${data.additions ?? 0} -${data.deletions ?? 0}`,
|
|
116
|
+
"",
|
|
117
|
+
data.body ?? "(no description)",
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
if (diff) {
|
|
121
|
+
lines.push("", "## Diff", "", `\`\`\`diff`, diff, `\`\`\``);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function formatPRMinimal(pr: any): string {
|
|
128
|
+
const merged = pr.merged_at ? " (merged)" : "";
|
|
129
|
+
return `#${pr.number} [${pr.state}${merged}] ${pr.title} (@${pr.user?.login}, ${pr.base?.ref} <- ${pr.head?.ref})`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function formatCommit(data: any, diff?: string): string {
|
|
133
|
+
const lines = [
|
|
134
|
+
`Commit: ${data.sha}`,
|
|
135
|
+
`Author: ${data.commit?.author?.name} <${data.commit?.author?.email}>`,
|
|
136
|
+
`Date: ${data.commit?.author?.date}`,
|
|
137
|
+
"",
|
|
138
|
+
data.commit?.message ?? "",
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
if (data.stats) {
|
|
142
|
+
lines.push("", `Files changed: ${data.stats.total} | +${data.stats.additions} -${data.stats.deletions}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (data.files?.length && !diff) {
|
|
146
|
+
lines.push("", "Files:");
|
|
147
|
+
for (const f of data.files) {
|
|
148
|
+
lines.push(` ${f.status} ${f.filename} (+${f.additions} -${f.deletions})`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (diff) {
|
|
153
|
+
lines.push("", "## Diff", "", `\`\`\`diff`, diff, `\`\`\``);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return lines.join("\n");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function formatCommitMinimal(c: any): string {
|
|
160
|
+
const sha = (c.sha ?? "").slice(0, 7);
|
|
161
|
+
const msg = (c.commit?.message ?? "").split("\n")[0];
|
|
162
|
+
const author = c.commit?.author?.name ?? c.author?.login ?? "?";
|
|
163
|
+
const date = c.commit?.author?.date ?? "";
|
|
164
|
+
return `${sha} ${msg} (${author}, ${date})`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function formatSearchReposResult(data: any): string {
|
|
168
|
+
const items = data.items ?? [];
|
|
169
|
+
const lines = [`Found ${data.total_count} repositories (showing ${items.length}):`, ""];
|
|
170
|
+
for (const item of items) {
|
|
171
|
+
lines.push(`${item.full_name} (${item.stargazers_count}*) - ${item.description ?? "no description"}`);
|
|
172
|
+
}
|
|
173
|
+
return lines.join("\n");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// =============================================================================
|
|
177
|
+
// Action Handlers
|
|
178
|
+
// =============================================================================
|
|
179
|
+
|
|
180
|
+
const MAX_FILE_LINES = 500;
|
|
181
|
+
const MAX_DIFF_CHARS = 50_000;
|
|
182
|
+
const MAX_COMMENTS_PAGES = 5;
|
|
183
|
+
|
|
184
|
+
async function handleAction(input: GitHubInput, signal?: AbortSignal): Promise<{ text: string; url?: string }> {
|
|
185
|
+
const { action, owner, repo } = input;
|
|
186
|
+
const opts = { signal };
|
|
187
|
+
const base = `/repos/${owner}/${repo}`;
|
|
188
|
+
|
|
189
|
+
switch (action) {
|
|
190
|
+
case "get_repo": {
|
|
191
|
+
const res = await githubClient.request(base, opts);
|
|
192
|
+
if (!res.ok) return error(res, "repository");
|
|
193
|
+
return { text: formatRepo(res.data), url: `https://github.com/${owner}/${repo}` };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
case "get_file": {
|
|
197
|
+
const filePath = input.path ?? "README.md";
|
|
198
|
+
const ref = input.ref ? `?ref=${input.ref}` : "";
|
|
199
|
+
let res = await githubClient.request<string>(`${base}/contents/${filePath}${ref}`, {
|
|
200
|
+
...opts,
|
|
201
|
+
mediaType: "application/vnd.github.v3.raw",
|
|
202
|
+
});
|
|
203
|
+
// Fallback to Blob API for files >1MB (raw mediaType returns 403)
|
|
204
|
+
if (!res.ok && res.status === 403) {
|
|
205
|
+
const metaRes = await githubClient.request<{ sha: string; size: number }>(
|
|
206
|
+
`${base}/contents/${filePath}${ref}`,
|
|
207
|
+
opts,
|
|
208
|
+
);
|
|
209
|
+
if (metaRes.ok && metaRes.data?.sha) {
|
|
210
|
+
const blobRes = await githubClient.request<{ content: string; encoding: string }>(
|
|
211
|
+
`${base}/git/blobs/${metaRes.data.sha}`,
|
|
212
|
+
opts,
|
|
213
|
+
);
|
|
214
|
+
if (blobRes.ok && blobRes.data?.content) {
|
|
215
|
+
const decoded =
|
|
216
|
+
blobRes.data.encoding === "base64"
|
|
217
|
+
? Buffer.from(blobRes.data.content, "base64").toString("utf-8")
|
|
218
|
+
: blobRes.data.content;
|
|
219
|
+
res = { data: decoded as any, ok: true, status: 200 };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!res.ok) return error(res, `file ${filePath}`);
|
|
224
|
+
const content = String(res.data);
|
|
225
|
+
const lines = content.split("\n");
|
|
226
|
+
const truncated = lines.length > MAX_FILE_LINES;
|
|
227
|
+
const output = truncated ? lines.slice(0, MAX_FILE_LINES).join("\n") : content;
|
|
228
|
+
const note = truncated ? `\n\n[Truncated: showing ${MAX_FILE_LINES}/${lines.length} lines]` : "";
|
|
229
|
+
return {
|
|
230
|
+
text: `# ${owner}/${repo}:${filePath}${input.ref ? ` @${input.ref}` : ""}\n\n${output}${note}`,
|
|
231
|
+
url: `https://github.com/${owner}/${repo}/blob/${input.ref ?? "HEAD"}/${filePath}`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
case "get_tree": {
|
|
236
|
+
const treePath = input.path ?? "";
|
|
237
|
+
if (input.recursive) {
|
|
238
|
+
const ref = input.ref ?? "HEAD";
|
|
239
|
+
const res = await githubClient.request<any>(`${base}/git/trees/${ref}?recursive=1`, opts);
|
|
240
|
+
if (!res.ok) return error(res, "tree");
|
|
241
|
+
const entries = (res.data.tree ?? [])
|
|
242
|
+
.filter((e: any) => !treePath || e.path.startsWith(treePath))
|
|
243
|
+
.slice(0, 500);
|
|
244
|
+
return {
|
|
245
|
+
text: `# Tree: ${owner}/${repo}${treePath ? `/${treePath}` : ""} (recursive)\n\n${entries.map(formatTreeEntry).join("\n")}`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const ref = input.ref ? `?ref=${input.ref}` : "";
|
|
249
|
+
const endpoint = treePath ? `${base}/contents/${treePath}${ref}` : `${base}/contents${ref}`;
|
|
250
|
+
const res = await githubClient.request<any[]>(endpoint, opts);
|
|
251
|
+
if (!res.ok) return error(res, "directory");
|
|
252
|
+
const entries = Array.isArray(res.data) ? res.data : [res.data];
|
|
253
|
+
return {
|
|
254
|
+
text: `# ${owner}/${repo}/${treePath}\n\n${entries.map(formatTreeEntry).join("\n")}`,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case "search_repos": {
|
|
259
|
+
const q = input.query ?? `${owner}/${repo}`;
|
|
260
|
+
const perPage = Math.min(input.limit ?? 30, 100);
|
|
261
|
+
const res = await githubClient.request<any>(
|
|
262
|
+
`/search/repositories?q=${encodeURIComponent(q)}&per_page=${perPage}`,
|
|
263
|
+
opts,
|
|
264
|
+
);
|
|
265
|
+
if (!res.ok) return error(res, "repository search");
|
|
266
|
+
return { text: formatSearchReposResult(res.data) };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
case "get_issue": {
|
|
270
|
+
const num = input.number;
|
|
271
|
+
if (!num) return { text: "Error: 'number' is required for get_issue" };
|
|
272
|
+
const [issueRes, commentsRes] = await Promise.all([
|
|
273
|
+
githubClient.request<any>(`${base}/issues/${num}`, opts),
|
|
274
|
+
githubClient.requestPaginated<any>(`${base}/issues/${num}/comments`, {
|
|
275
|
+
...opts,
|
|
276
|
+
perPage: 100,
|
|
277
|
+
maxPages: MAX_COMMENTS_PAGES,
|
|
278
|
+
}),
|
|
279
|
+
]);
|
|
280
|
+
if (!issueRes.ok) return error(issueRes, `issue #${num}`);
|
|
281
|
+
return {
|
|
282
|
+
text: formatIssue(issueRes.data, commentsRes.ok ? commentsRes.data : []),
|
|
283
|
+
url: `https://github.com/${owner}/${repo}/issues/${num}`,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case "list_issues": {
|
|
288
|
+
const params = new URLSearchParams();
|
|
289
|
+
if (input.state) params.set("state", input.state);
|
|
290
|
+
if (input.labels) params.set("labels", input.labels);
|
|
291
|
+
const limit = Math.min(input.limit ?? 100, 500);
|
|
292
|
+
const perPage = Math.min(limit, 100);
|
|
293
|
+
const maxPages = Math.ceil(limit / perPage);
|
|
294
|
+
const res = await githubClient.requestPaginated<any>(`${base}/issues?${params}`, {
|
|
295
|
+
...opts,
|
|
296
|
+
perPage,
|
|
297
|
+
maxPages,
|
|
298
|
+
});
|
|
299
|
+
if (!res.ok) return error(res, "issues");
|
|
300
|
+
const issues = (res.data ?? []).filter((i: any) => !i.pull_request).slice(0, limit);
|
|
301
|
+
const header = `${issues.length} issue(s)${issues.length >= limit ? " (limit reached, increase limit for more)" : ""}`;
|
|
302
|
+
return {
|
|
303
|
+
text: issues.length ? `${header}\n${issues.map(formatIssueMinimal).join("\n")}` : "No issues found.",
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
case "get_pull": {
|
|
308
|
+
const num = input.number;
|
|
309
|
+
if (!num) return { text: "Error: 'number' is required for get_pull" };
|
|
310
|
+
const prRes = await githubClient.request<any>(`${base}/pulls/${num}`, opts);
|
|
311
|
+
if (!prRes.ok) return error(prRes, `PR #${num}`);
|
|
312
|
+
|
|
313
|
+
let diff: string | undefined;
|
|
314
|
+
if (input.include_diff) {
|
|
315
|
+
const diffRes = await githubClient.request<string>(`${base}/pulls/${num}`, {
|
|
316
|
+
...opts,
|
|
317
|
+
mediaType: "application/vnd.github.v3.diff",
|
|
318
|
+
});
|
|
319
|
+
if (diffRes.ok) {
|
|
320
|
+
diff = String(diffRes.data);
|
|
321
|
+
if (diff.length > MAX_DIFF_CHARS) {
|
|
322
|
+
diff = `${diff.slice(0, MAX_DIFF_CHARS)}\n[Truncated at ${MAX_DIFF_CHARS} chars]`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
text: formatPR(prRes.data, diff),
|
|
329
|
+
url: `https://github.com/${owner}/${repo}/pull/${num}`,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
case "list_pulls": {
|
|
334
|
+
const params = new URLSearchParams();
|
|
335
|
+
if (input.state) params.set("state", input.state);
|
|
336
|
+
const limit = Math.min(input.limit ?? 100, 500);
|
|
337
|
+
const perPage = Math.min(limit, 100);
|
|
338
|
+
const maxPages = Math.ceil(limit / perPage);
|
|
339
|
+
const res = await githubClient.requestPaginated<any>(`${base}/pulls?${params}`, {
|
|
340
|
+
...opts,
|
|
341
|
+
perPage,
|
|
342
|
+
maxPages,
|
|
343
|
+
});
|
|
344
|
+
if (!res.ok) return error(res, "pull requests");
|
|
345
|
+
const pulls = (res.data ?? []).slice(0, limit);
|
|
346
|
+
const header = `${pulls.length} PR(s)${pulls.length >= limit ? " (limit reached, increase limit for more)" : ""}`;
|
|
347
|
+
return {
|
|
348
|
+
text: pulls.length ? `${header}\n${pulls.map(formatPRMinimal).join("\n")}` : "No pull requests found.",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
case "list_commits": {
|
|
353
|
+
const params = new URLSearchParams();
|
|
354
|
+
if (input.sha) params.set("sha", input.sha);
|
|
355
|
+
if (input.path) params.set("path", input.path);
|
|
356
|
+
const limit = Math.min(input.limit ?? 100, 500);
|
|
357
|
+
const perPage = Math.min(limit, 100);
|
|
358
|
+
const maxPages = Math.ceil(limit / perPage);
|
|
359
|
+
const res = await githubClient.requestPaginated<any>(`${base}/commits?${params}`, {
|
|
360
|
+
...opts,
|
|
361
|
+
perPage,
|
|
362
|
+
maxPages,
|
|
363
|
+
});
|
|
364
|
+
if (!res.ok) return error(res, "commits");
|
|
365
|
+
const commits = (res.data ?? []).slice(0, limit);
|
|
366
|
+
const header = `${commits.length} commit(s)${commits.length >= limit ? " (limit reached, increase limit for more)" : ""}`;
|
|
367
|
+
return {
|
|
368
|
+
text: commits.length ? `${header}\n${commits.map(formatCommitMinimal).join("\n")}` : "No commits found.",
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
case "get_commit": {
|
|
373
|
+
const sha = input.sha;
|
|
374
|
+
if (!sha) return { text: "Error: 'sha' is required for get_commit" };
|
|
375
|
+
const res = await githubClient.request<any>(`${base}/commits/${sha}`, opts);
|
|
376
|
+
if (!res.ok) return error(res, `commit ${sha}`);
|
|
377
|
+
|
|
378
|
+
let diff: string | undefined;
|
|
379
|
+
if (input.include_diff) {
|
|
380
|
+
const diffRes = await githubClient.request<string>(`${base}/commits/${sha}`, {
|
|
381
|
+
...opts,
|
|
382
|
+
mediaType: "application/vnd.github.v3.diff",
|
|
383
|
+
});
|
|
384
|
+
if (diffRes.ok) {
|
|
385
|
+
diff = String(diffRes.data);
|
|
386
|
+
if (diff.length > MAX_DIFF_CHARS) {
|
|
387
|
+
diff = `${diff.slice(0, MAX_DIFF_CHARS)}\n[Truncated at ${MAX_DIFF_CHARS} chars]`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
text: formatCommit(res.data, diff),
|
|
394
|
+
url: `https://github.com/${owner}/${repo}/commit/${sha}`,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
default:
|
|
399
|
+
return { text: `Unknown action: ${action}` };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function error(res: GitHubResponse, resource: string): { text: string } {
|
|
404
|
+
const status = res.status;
|
|
405
|
+
if (status === 404) return { text: `Error: ${resource} not found (404)` };
|
|
406
|
+
if (status === 403) {
|
|
407
|
+
const rl = res.rateLimit;
|
|
408
|
+
if (rl && rl.remaining === 0) {
|
|
409
|
+
return { text: `Error: GitHub API rate limit exceeded. Resets at ${new Date(rl.reset * 1000).toISOString()}` };
|
|
410
|
+
}
|
|
411
|
+
return { text: `Error: Access denied to ${resource} (403). Check token permissions.` };
|
|
412
|
+
}
|
|
413
|
+
if (status === 401)
|
|
414
|
+
return { text: `Error: Authentication failed (401). Check GITHUB_TOKEN or run 'gh auth login'.` };
|
|
415
|
+
return { text: `Error: Failed to fetch ${resource} (HTTP ${status})` };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// =============================================================================
|
|
419
|
+
// Tool Class
|
|
420
|
+
// =============================================================================
|
|
421
|
+
|
|
422
|
+
export class GitHubTool implements AgentTool<typeof schema, GitHubToolDetails, Theme> {
|
|
423
|
+
readonly name = "github";
|
|
424
|
+
readonly label = "GitHub";
|
|
425
|
+
readonly parameters = schema;
|
|
426
|
+
description = "Interact with GitHub API: repos, issues, PRs, commits";
|
|
427
|
+
|
|
428
|
+
constructor(readonly _session: ToolSession) {}
|
|
429
|
+
|
|
430
|
+
async execute(
|
|
431
|
+
_toolCallId: string,
|
|
432
|
+
params: Record<string, unknown>,
|
|
433
|
+
signal?: AbortSignal,
|
|
434
|
+
): Promise<AgentToolResult<GitHubToolDetails>> {
|
|
435
|
+
const input = params as unknown as GitHubInput;
|
|
436
|
+
const details: GitHubToolDetails = {
|
|
437
|
+
action: input.action,
|
|
438
|
+
owner: input.owner,
|
|
439
|
+
repo: input.repo,
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const result = await handleAction(input, signal);
|
|
443
|
+
|
|
444
|
+
const builder = toolResult(details).text(result.text);
|
|
445
|
+
if (result.url) {
|
|
446
|
+
builder.sourceUrl(result.url);
|
|
447
|
+
}
|
|
448
|
+
return builder.done();
|
|
449
|
+
}
|
|
450
|
+
}
|