@oh-my-pi/pi-coding-agent 0.1.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 +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Search TUI Rendering
|
|
3
|
+
*
|
|
4
|
+
* Tree-based rendering with collapsed/expanded states for web search results.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
9
|
+
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
10
|
+
import type { RenderResultOptions } from "../../custom-tools/types";
|
|
11
|
+
import type { WebSearchResponse } from "./types";
|
|
12
|
+
|
|
13
|
+
/** Truncate text to max length with ellipsis */
|
|
14
|
+
export function truncate(text: string, maxLen: number, ellipsis: string): string {
|
|
15
|
+
if (text.length <= maxLen) return text;
|
|
16
|
+
const sliceLen = Math.max(0, maxLen - ellipsis.length);
|
|
17
|
+
return `${text.slice(0, sliceLen)}${ellipsis}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Extract domain from URL */
|
|
21
|
+
export function getDomain(url: string): string {
|
|
22
|
+
try {
|
|
23
|
+
const u = new URL(url);
|
|
24
|
+
return u.hostname.replace(/^www\./, "");
|
|
25
|
+
} catch {
|
|
26
|
+
return url;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Format age string from seconds */
|
|
31
|
+
export function formatAge(ageSeconds: number | null | undefined): string {
|
|
32
|
+
if (!ageSeconds) return "";
|
|
33
|
+
const mins = Math.floor(ageSeconds / 60);
|
|
34
|
+
const hours = Math.floor(mins / 60);
|
|
35
|
+
const days = Math.floor(hours / 24);
|
|
36
|
+
const weeks = Math.floor(days / 7);
|
|
37
|
+
const months = Math.floor(days / 30);
|
|
38
|
+
|
|
39
|
+
if (months > 0) return `${months}mo ago`;
|
|
40
|
+
if (weeks > 0) return `${weeks}w ago`;
|
|
41
|
+
if (days > 0) return `${days}d ago`;
|
|
42
|
+
if (hours > 0) return `${hours}h ago`;
|
|
43
|
+
if (mins > 0) return `${mins}m ago`;
|
|
44
|
+
return "just now";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Get first N lines of text as preview */
|
|
48
|
+
export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis: string): string[] {
|
|
49
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
50
|
+
return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen, ellipsis));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const MAX_COLLAPSED_ANSWER_LINES = 3;
|
|
54
|
+
const MAX_EXPANDED_ANSWER_LINES = 12;
|
|
55
|
+
const MAX_ANSWER_LINE_LEN = 110;
|
|
56
|
+
const MAX_SNIPPET_LINES = 2;
|
|
57
|
+
const MAX_SNIPPET_LINE_LEN = 110;
|
|
58
|
+
const MAX_RELATED_QUESTIONS = 6;
|
|
59
|
+
const MAX_QUERY_PREVIEW = 2;
|
|
60
|
+
const MAX_QUERY_LEN = 90;
|
|
61
|
+
const MAX_REQUEST_ID_LEN = 36;
|
|
62
|
+
|
|
63
|
+
function formatCount(label: string, count: number): string {
|
|
64
|
+
const safeCount = Number.isFinite(count) ? count : 0;
|
|
65
|
+
return `${safeCount} ${label}${safeCount === 1 ? "" : "s"}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderFallbackText(contentText: string, expanded: boolean, theme: Theme): Component {
|
|
69
|
+
const lines = contentText.split("\n").filter((line) => line.trim());
|
|
70
|
+
const maxLines = expanded ? lines.length : 6;
|
|
71
|
+
const displayLines = lines.slice(0, maxLines).map((line) => truncate(line.trim(), 110, theme.format.ellipsis));
|
|
72
|
+
const remaining = lines.length - displayLines.length;
|
|
73
|
+
|
|
74
|
+
const headerIcon = theme.fg("warning", theme.status.warning);
|
|
75
|
+
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
76
|
+
let text = `${headerIcon} ${theme.fg("toolTitle", "Web Search")}${expandHint}`;
|
|
77
|
+
|
|
78
|
+
if (displayLines.length === 0) {
|
|
79
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", "No response data")}`;
|
|
80
|
+
return new Text(text, 0, 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
84
|
+
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
85
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
86
|
+
text += `\n ${theme.fg("dim", branch)} ${theme.fg("dim", displayLines[i])}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!expanded && remaining > 0) {
|
|
90
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
91
|
+
"muted",
|
|
92
|
+
`${theme.format.ellipsis} ${remaining} more line${remaining === 1 ? "" : "s"}`,
|
|
93
|
+
)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return new Text(text, 0, 0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface WebSearchRenderDetails {
|
|
100
|
+
response: WebSearchResponse;
|
|
101
|
+
error?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Render web search result with tree-based layout */
|
|
105
|
+
export function renderWebSearchResult(
|
|
106
|
+
result: { content: Array<{ type: string; text?: string }>; details?: WebSearchRenderDetails },
|
|
107
|
+
options: RenderResultOptions,
|
|
108
|
+
theme: Theme,
|
|
109
|
+
): Component {
|
|
110
|
+
const { expanded } = options;
|
|
111
|
+
const details = result.details;
|
|
112
|
+
|
|
113
|
+
// Handle error case
|
|
114
|
+
if (details?.error) {
|
|
115
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const rawText = result.content?.find((block) => block.type === "text")?.text?.trim() ?? "";
|
|
119
|
+
const response = details?.response;
|
|
120
|
+
if (!response) {
|
|
121
|
+
return renderFallbackText(rawText, expanded, theme);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const sources = Array.isArray(response.sources) ? response.sources : [];
|
|
125
|
+
const sourceCount = sources.length;
|
|
126
|
+
const citations = Array.isArray(response.citations) ? response.citations : [];
|
|
127
|
+
const citationCount = citations.length;
|
|
128
|
+
const related = Array.isArray(response.relatedQuestions)
|
|
129
|
+
? response.relatedQuestions.filter((item) => typeof item === "string")
|
|
130
|
+
: [];
|
|
131
|
+
const relatedCount = related.length;
|
|
132
|
+
const searchQueries = Array.isArray(response.searchQueries)
|
|
133
|
+
? response.searchQueries.filter((item) => typeof item === "string")
|
|
134
|
+
: [];
|
|
135
|
+
const provider = response.provider;
|
|
136
|
+
|
|
137
|
+
// Build header: status icon Web Search (provider) · counts
|
|
138
|
+
const providerLabel =
|
|
139
|
+
provider === "anthropic"
|
|
140
|
+
? "Anthropic"
|
|
141
|
+
: provider === "perplexity"
|
|
142
|
+
? "Perplexity"
|
|
143
|
+
: provider === "exa"
|
|
144
|
+
? "Exa"
|
|
145
|
+
: "Unknown";
|
|
146
|
+
const headerIcon =
|
|
147
|
+
sourceCount > 0 ? theme.fg("success", theme.status.success) : theme.fg("warning", theme.status.warning);
|
|
148
|
+
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
149
|
+
let text = `${headerIcon} ${theme.fg("toolTitle", "Web Search")} ${theme.fg("dim", `(${providerLabel})`)}${theme.sep.dot}${theme.fg(
|
|
150
|
+
"dim",
|
|
151
|
+
formatCount("source", sourceCount),
|
|
152
|
+
)}${expandHint}`;
|
|
153
|
+
|
|
154
|
+
// Get answer text
|
|
155
|
+
const answerText = typeof response.answer === "string" ? response.answer.trim() : "";
|
|
156
|
+
const contentText = answerText || rawText;
|
|
157
|
+
const totalAnswerLines = contentText ? contentText.split("\n").filter((l) => l.trim()).length : 0;
|
|
158
|
+
const answerLimit = expanded ? MAX_EXPANDED_ANSWER_LINES : MAX_COLLAPSED_ANSWER_LINES;
|
|
159
|
+
const answerPreview = contentText
|
|
160
|
+
? getPreviewLines(contentText, answerLimit, MAX_ANSWER_LINE_LEN, theme.format.ellipsis)
|
|
161
|
+
: [];
|
|
162
|
+
|
|
163
|
+
if (!expanded) {
|
|
164
|
+
const answerTitle = `${theme.fg("accent", theme.status.info)} ${theme.fg("accent", "Answer")}`;
|
|
165
|
+
text += `\n ${theme.fg("dim", theme.tree.vertical)} ${answerTitle}`;
|
|
166
|
+
|
|
167
|
+
if (answerPreview.length === 0) {
|
|
168
|
+
text += `\n ${theme.fg("dim", theme.tree.vertical)} ${theme.fg("dim", `${theme.tree.hook} `)}${theme.fg(
|
|
169
|
+
"muted",
|
|
170
|
+
"No answer text returned",
|
|
171
|
+
)}`;
|
|
172
|
+
} else {
|
|
173
|
+
for (const line of answerPreview) {
|
|
174
|
+
text += `\n ${theme.fg("dim", theme.tree.vertical)} ${theme.fg("dim", `${theme.tree.hook} `)}${theme.fg(
|
|
175
|
+
"dim",
|
|
176
|
+
line,
|
|
177
|
+
)}`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const remaining = totalAnswerLines - answerPreview.length;
|
|
182
|
+
if (remaining > 0) {
|
|
183
|
+
text += `\n ${theme.fg("dim", theme.tree.vertical)} ${theme.fg("dim", `${theme.tree.hook} `)}${theme.fg(
|
|
184
|
+
"muted",
|
|
185
|
+
`${theme.format.ellipsis} ${remaining} more line${remaining === 1 ? "" : "s"}`,
|
|
186
|
+
)}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const summary = [
|
|
190
|
+
formatCount("source", sourceCount),
|
|
191
|
+
formatCount("citation", citationCount),
|
|
192
|
+
formatCount("related", relatedCount),
|
|
193
|
+
].join(theme.sep.dot);
|
|
194
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", summary)}`;
|
|
195
|
+
return new Text(text, 0, 0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const answerLines = answerPreview.length > 0 ? answerPreview : ["No answer text returned"];
|
|
199
|
+
const answerSectionLines = answerLines.map((line) =>
|
|
200
|
+
line === "No answer text returned" ? theme.fg("muted", line) : theme.fg("text", line),
|
|
201
|
+
);
|
|
202
|
+
const remainingAnswer = totalAnswerLines - answerPreview.length;
|
|
203
|
+
if (remainingAnswer > 0) {
|
|
204
|
+
answerSectionLines.push(
|
|
205
|
+
theme.fg("muted", `${theme.format.ellipsis} ${remainingAnswer} more line${remainingAnswer === 1 ? "" : "s"}`),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const sourceLines: string[] = [];
|
|
210
|
+
if (sourceCount === 0) {
|
|
211
|
+
sourceLines.push(theme.fg("muted", "No sources returned"));
|
|
212
|
+
} else {
|
|
213
|
+
for (const src of sources) {
|
|
214
|
+
const titleText =
|
|
215
|
+
typeof src.title === "string" && src.title.trim()
|
|
216
|
+
? src.title
|
|
217
|
+
: typeof src.url === "string" && src.url.trim()
|
|
218
|
+
? src.url
|
|
219
|
+
: "Untitled";
|
|
220
|
+
const title = truncate(titleText, 70, theme.format.ellipsis);
|
|
221
|
+
const url = typeof src.url === "string" ? src.url : "";
|
|
222
|
+
const domain = url ? getDomain(url) : "";
|
|
223
|
+
const age = formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
|
|
224
|
+
const metaParts: string[] = [];
|
|
225
|
+
if (domain) {
|
|
226
|
+
metaParts.push(theme.fg("dim", `(${domain})`));
|
|
227
|
+
}
|
|
228
|
+
if (typeof src.author === "string" && src.author.trim()) {
|
|
229
|
+
metaParts.push(theme.fg("muted", src.author));
|
|
230
|
+
}
|
|
231
|
+
if (age) {
|
|
232
|
+
metaParts.push(theme.fg("muted", age));
|
|
233
|
+
}
|
|
234
|
+
const metaSep = theme.fg("dim", theme.sep.dot);
|
|
235
|
+
const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
|
|
236
|
+
sourceLines.push(`${theme.fg("accent", title)}${metaSuffix}`);
|
|
237
|
+
|
|
238
|
+
const snippetText = typeof src.snippet === "string" ? src.snippet : "";
|
|
239
|
+
if (snippetText.trim()) {
|
|
240
|
+
const snippetLines = getPreviewLines(
|
|
241
|
+
snippetText,
|
|
242
|
+
MAX_SNIPPET_LINES,
|
|
243
|
+
MAX_SNIPPET_LINE_LEN,
|
|
244
|
+
theme.format.ellipsis,
|
|
245
|
+
);
|
|
246
|
+
for (const snippetLine of snippetLines) {
|
|
247
|
+
sourceLines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (url) {
|
|
252
|
+
sourceLines.push(theme.fg("mdLinkUrl", url));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const relatedLines: string[] = [];
|
|
258
|
+
if (relatedCount === 0) {
|
|
259
|
+
relatedLines.push(theme.fg("muted", "No related questions"));
|
|
260
|
+
} else {
|
|
261
|
+
const maxRelated = Math.min(MAX_RELATED_QUESTIONS, related.length);
|
|
262
|
+
for (let i = 0; i < maxRelated; i++) {
|
|
263
|
+
relatedLines.push(theme.fg("muted", `${theme.format.dash} ${related[i]}`));
|
|
264
|
+
}
|
|
265
|
+
if (relatedCount > maxRelated) {
|
|
266
|
+
relatedLines.push(
|
|
267
|
+
theme.fg(
|
|
268
|
+
"muted",
|
|
269
|
+
`${theme.format.ellipsis} ${relatedCount - maxRelated} more question${
|
|
270
|
+
relatedCount - maxRelated === 1 ? "" : "s"
|
|
271
|
+
}`,
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const metaLines: string[] = [];
|
|
278
|
+
metaLines.push(`${theme.fg("muted", "Provider:")} ${theme.fg("text", providerLabel)}`);
|
|
279
|
+
if (response.model) {
|
|
280
|
+
metaLines.push(`${theme.fg("muted", "Model:")} ${theme.fg("text", response.model)}`);
|
|
281
|
+
}
|
|
282
|
+
metaLines.push(`${theme.fg("muted", "Sources:")} ${theme.fg("text", String(sourceCount))}`);
|
|
283
|
+
if (citationCount > 0) {
|
|
284
|
+
metaLines.push(`${theme.fg("muted", "Citations:")} ${theme.fg("text", String(citationCount))}`);
|
|
285
|
+
}
|
|
286
|
+
if (relatedCount > 0) {
|
|
287
|
+
metaLines.push(`${theme.fg("muted", "Related:")} ${theme.fg("text", String(relatedCount))}`);
|
|
288
|
+
}
|
|
289
|
+
if (response.usage) {
|
|
290
|
+
const usageParts: string[] = [];
|
|
291
|
+
if (response.usage.inputTokens !== undefined) usageParts.push(`in ${response.usage.inputTokens}`);
|
|
292
|
+
if (response.usage.outputTokens !== undefined) usageParts.push(`out ${response.usage.outputTokens}`);
|
|
293
|
+
if (response.usage.totalTokens !== undefined) usageParts.push(`total ${response.usage.totalTokens}`);
|
|
294
|
+
if (response.usage.searchRequests !== undefined) usageParts.push(`search ${response.usage.searchRequests}`);
|
|
295
|
+
if (usageParts.length > 0) {
|
|
296
|
+
metaLines.push(`${theme.fg("muted", "Usage:")} ${theme.fg("text", usageParts.join(theme.sep.dot))}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (response.requestId) {
|
|
300
|
+
metaLines.push(
|
|
301
|
+
`${theme.fg("muted", "Request:")} ${theme.fg(
|
|
302
|
+
"text",
|
|
303
|
+
truncate(response.requestId, MAX_REQUEST_ID_LEN, theme.format.ellipsis),
|
|
304
|
+
)}`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (searchQueries.length > 0) {
|
|
308
|
+
metaLines.push(`${theme.fg("muted", "Search queries:")} ${theme.fg("text", String(searchQueries.length))}`);
|
|
309
|
+
const queryPreview = searchQueries.slice(0, MAX_QUERY_PREVIEW);
|
|
310
|
+
for (const q of queryPreview) {
|
|
311
|
+
metaLines.push(theme.fg("muted", `${theme.format.dash} ${truncate(q, MAX_QUERY_LEN, theme.format.ellipsis)}`));
|
|
312
|
+
}
|
|
313
|
+
if (searchQueries.length > MAX_QUERY_PREVIEW) {
|
|
314
|
+
metaLines.push(
|
|
315
|
+
theme.fg(
|
|
316
|
+
"muted",
|
|
317
|
+
`${theme.format.ellipsis} ${searchQueries.length - MAX_QUERY_PREVIEW} more query${
|
|
318
|
+
searchQueries.length - MAX_QUERY_PREVIEW === 1 ? "" : "s"
|
|
319
|
+
}`,
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const sections: Array<{ title: string; icon: string; lines: string[] }> = [
|
|
326
|
+
{
|
|
327
|
+
title: "Answer",
|
|
328
|
+
icon: theme.fg("accent", theme.status.info),
|
|
329
|
+
lines: answerSectionLines,
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
title: "Sources",
|
|
333
|
+
icon: sourceCount > 0 ? theme.fg("success", theme.status.success) : theme.fg("warning", theme.status.warning),
|
|
334
|
+
lines: sourceLines,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
title: "Related",
|
|
338
|
+
icon: relatedCount > 0 ? theme.fg("accent", theme.status.info) : theme.fg("warning", theme.status.warning),
|
|
339
|
+
lines: relatedLines,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
title: "Meta",
|
|
343
|
+
icon: theme.fg("accent", theme.status.info),
|
|
344
|
+
lines: metaLines,
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
for (let i = 0; i < sections.length; i++) {
|
|
349
|
+
const section = sections[i];
|
|
350
|
+
const isLast = i === sections.length - 1;
|
|
351
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
352
|
+
const indent = isLast ? " " : theme.tree.vertical;
|
|
353
|
+
|
|
354
|
+
text += `\n ${theme.fg("dim", branch)} ${section.icon} ${theme.fg("accent", section.title)}`;
|
|
355
|
+
for (const line of section.lines) {
|
|
356
|
+
text += `\n ${theme.fg("dim", indent)} ${theme.fg("dim", `${theme.tree.hook} `)}${line}`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return new Text(text, 0, 0);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Render web search call (query preview) */
|
|
364
|
+
export function renderWebSearchCall(
|
|
365
|
+
args: { query: string; provider?: string; [key: string]: unknown },
|
|
366
|
+
theme: Theme,
|
|
367
|
+
): Component {
|
|
368
|
+
const provider = args.provider ?? "auto";
|
|
369
|
+
const query = truncate(args.query, 80, theme.format.ellipsis);
|
|
370
|
+
const text = `${theme.fg("toolTitle", "Web Search")} ${theme.fg("dim", `(${provider})`)} ${theme.fg("muted", query)}`;
|
|
371
|
+
return new Text(text, 0, 0);
|
|
372
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Search Types
|
|
3
|
+
*
|
|
4
|
+
* Unified types for web search responses across Anthropic and Perplexity providers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Supported web search providers */
|
|
8
|
+
export type WebSearchProvider = "exa" | "anthropic" | "perplexity";
|
|
9
|
+
|
|
10
|
+
/** Source returned by search (all providers) */
|
|
11
|
+
export interface WebSearchSource {
|
|
12
|
+
title: string;
|
|
13
|
+
url: string;
|
|
14
|
+
snippet?: string;
|
|
15
|
+
/** ISO date string or relative ("2d ago") */
|
|
16
|
+
publishedDate?: string;
|
|
17
|
+
/** Age in seconds for consistent formatting */
|
|
18
|
+
ageSeconds?: number;
|
|
19
|
+
author?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Citation with text reference (anthropic, perplexity) */
|
|
23
|
+
export interface WebSearchCitation {
|
|
24
|
+
url: string;
|
|
25
|
+
title: string;
|
|
26
|
+
citedText?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Usage metrics */
|
|
30
|
+
export interface WebSearchUsage {
|
|
31
|
+
inputTokens?: number;
|
|
32
|
+
outputTokens?: number;
|
|
33
|
+
/** Anthropic: number of web search requests made */
|
|
34
|
+
searchRequests?: number;
|
|
35
|
+
/** Perplexity: combined token count */
|
|
36
|
+
totalTokens?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Unified response across providers */
|
|
40
|
+
export interface WebSearchResponse {
|
|
41
|
+
provider: WebSearchProvider;
|
|
42
|
+
/** Synthesized answer text (anthropic, perplexity) */
|
|
43
|
+
answer?: string;
|
|
44
|
+
/** Search result sources */
|
|
45
|
+
sources: WebSearchSource[];
|
|
46
|
+
/** Text citations with context */
|
|
47
|
+
citations?: WebSearchCitation[];
|
|
48
|
+
/** Follow-up questions (perplexity) */
|
|
49
|
+
relatedQuestions?: string[];
|
|
50
|
+
/** Intermediate search queries (anthropic) */
|
|
51
|
+
searchQueries?: string[];
|
|
52
|
+
/** Token usage metrics */
|
|
53
|
+
usage?: WebSearchUsage;
|
|
54
|
+
/** Model used */
|
|
55
|
+
model?: string;
|
|
56
|
+
/** Request ID for debugging */
|
|
57
|
+
requestId?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Auth configuration for Anthropic */
|
|
61
|
+
export interface AnthropicAuthConfig {
|
|
62
|
+
apiKey: string;
|
|
63
|
+
baseUrl: string;
|
|
64
|
+
isOAuth: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** models.json structure for provider resolution */
|
|
68
|
+
export interface ModelsJson {
|
|
69
|
+
providers?: Record<
|
|
70
|
+
string,
|
|
71
|
+
{
|
|
72
|
+
baseUrl?: string;
|
|
73
|
+
apiKey?: string;
|
|
74
|
+
api?: string;
|
|
75
|
+
}
|
|
76
|
+
>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** auth.json structure for OAuth credentials */
|
|
80
|
+
export interface AuthJson {
|
|
81
|
+
anthropic?: {
|
|
82
|
+
type: "oauth";
|
|
83
|
+
access: string;
|
|
84
|
+
refresh?: string;
|
|
85
|
+
/** Expiry timestamp in milliseconds */
|
|
86
|
+
expires: number;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Anthropic API response types */
|
|
91
|
+
export interface AnthropicWebSearchResult {
|
|
92
|
+
type: "web_search_result";
|
|
93
|
+
title: string;
|
|
94
|
+
url: string;
|
|
95
|
+
encrypted_content: string;
|
|
96
|
+
page_age: string | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface AnthropicCitation {
|
|
100
|
+
type: "web_search_result_location";
|
|
101
|
+
url: string;
|
|
102
|
+
title: string;
|
|
103
|
+
cited_text: string;
|
|
104
|
+
encrypted_index: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface AnthropicContentBlock {
|
|
108
|
+
type: string;
|
|
109
|
+
/** Text content (for type="text") */
|
|
110
|
+
text?: string;
|
|
111
|
+
/** Citations in text block */
|
|
112
|
+
citations?: AnthropicCitation[];
|
|
113
|
+
/** Tool name (for type="server_tool_use") */
|
|
114
|
+
name?: string;
|
|
115
|
+
/** Tool input (for type="server_tool_use") */
|
|
116
|
+
input?: { query: string };
|
|
117
|
+
/** Search results (for type="web_search_tool_result") */
|
|
118
|
+
content?: AnthropicWebSearchResult[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface AnthropicApiResponse {
|
|
122
|
+
id: string;
|
|
123
|
+
model: string;
|
|
124
|
+
content: AnthropicContentBlock[];
|
|
125
|
+
usage: {
|
|
126
|
+
input_tokens: number;
|
|
127
|
+
output_tokens: number;
|
|
128
|
+
cache_read_input_tokens?: number;
|
|
129
|
+
cache_creation_input_tokens?: number;
|
|
130
|
+
server_tool_use?: { web_search_requests: number };
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Perplexity API types */
|
|
135
|
+
export interface PerplexityMessage {
|
|
136
|
+
role: "system" | "user" | "assistant";
|
|
137
|
+
content: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface PerplexityRequest {
|
|
141
|
+
model: string;
|
|
142
|
+
messages: PerplexityMessage[];
|
|
143
|
+
temperature?: number;
|
|
144
|
+
max_tokens?: number;
|
|
145
|
+
search_domain_filter?: string[];
|
|
146
|
+
search_recency_filter?: "day" | "week" | "month" | "year";
|
|
147
|
+
return_images?: boolean;
|
|
148
|
+
return_related_questions?: boolean;
|
|
149
|
+
search_context_size?: "low" | "medium" | "high";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface PerplexitySearchResult {
|
|
153
|
+
title: string;
|
|
154
|
+
url: string;
|
|
155
|
+
date?: string;
|
|
156
|
+
snippet?: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface PerplexityResponse {
|
|
160
|
+
id: string;
|
|
161
|
+
model: string;
|
|
162
|
+
created: number;
|
|
163
|
+
usage: {
|
|
164
|
+
prompt_tokens: number;
|
|
165
|
+
completion_tokens: number;
|
|
166
|
+
total_tokens: number;
|
|
167
|
+
search_context_size?: string;
|
|
168
|
+
};
|
|
169
|
+
citations?: string[];
|
|
170
|
+
search_results?: PerplexitySearchResult[];
|
|
171
|
+
related_questions?: string[];
|
|
172
|
+
choices: Array<{
|
|
173
|
+
index: number;
|
|
174
|
+
finish_reason: string;
|
|
175
|
+
message: {
|
|
176
|
+
role: string;
|
|
177
|
+
content: string;
|
|
178
|
+
};
|
|
179
|
+
}>;
|
|
180
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import writeDescription from "../../prompts/tools/write.md" with { type: "text" };
|
|
4
|
+
import { type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "./lsp/index";
|
|
5
|
+
import { resolveToCwd } from "./path-utils";
|
|
6
|
+
|
|
7
|
+
const writeSchema = Type.Object({
|
|
8
|
+
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
9
|
+
content: Type.String({ description: "Content to write to the file" }),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/** Options for creating the write tool */
|
|
13
|
+
export interface WriteToolOptions {
|
|
14
|
+
writethrough?: WritethroughCallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Details returned by the write tool for TUI rendering */
|
|
18
|
+
export interface WriteToolDetails {
|
|
19
|
+
diagnostics?: FileDiagnosticsResult;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createWriteTool(
|
|
23
|
+
cwd: string,
|
|
24
|
+
options: WriteToolOptions = {},
|
|
25
|
+
): AgentTool<typeof writeSchema, WriteToolDetails> {
|
|
26
|
+
const writethrough = options.writethrough ?? writethroughNoop;
|
|
27
|
+
return {
|
|
28
|
+
name: "write",
|
|
29
|
+
label: "Write",
|
|
30
|
+
description: writeDescription,
|
|
31
|
+
parameters: writeSchema,
|
|
32
|
+
execute: async (
|
|
33
|
+
_toolCallId: string,
|
|
34
|
+
{ path, content }: { path: string; content: string },
|
|
35
|
+
signal?: AbortSignal,
|
|
36
|
+
) => {
|
|
37
|
+
const absolutePath = resolveToCwd(path, cwd);
|
|
38
|
+
|
|
39
|
+
const diagnostics = await writethrough(absolutePath, content, signal);
|
|
40
|
+
|
|
41
|
+
let resultText = `Successfully wrote ${content.length} bytes to ${path}`;
|
|
42
|
+
if (!diagnostics) {
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: resultText }],
|
|
45
|
+
details: {},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const messages = diagnostics?.messages;
|
|
50
|
+
if (messages && messages.length > 0) {
|
|
51
|
+
resultText += `\n\nLSP Diagnostics (${diagnostics.summary}):\n`;
|
|
52
|
+
resultText += messages.map((d) => ` ${d}`).join("\n");
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: resultText }],
|
|
56
|
+
details: { diagnostics },
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Default write tool using process.cwd() - for backwards compatibility */
|
|
63
|
+
export const writeTool = createWriteTool(process.cwd());
|