@oh-my-pi/pi-coding-agent 13.9.11 → 13.9.13

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +7 -7
  3. package/src/cli/args.ts +18 -16
  4. package/src/config/keybindings.ts +6 -0
  5. package/src/config/model-registry.ts +4 -4
  6. package/src/config/settings-schema.ts +10 -9
  7. package/src/debug/log-viewer.ts +11 -7
  8. package/src/exec/bash-executor.ts +15 -1
  9. package/src/internal-urls/docs-index.generated.ts +1 -1
  10. package/src/modes/components/agent-dashboard.ts +11 -8
  11. package/src/modes/components/extensions/extension-list.ts +16 -8
  12. package/src/modes/components/settings-defs.ts +2 -2
  13. package/src/modes/components/status-line.ts +5 -9
  14. package/src/modes/components/tree-selector.ts +4 -6
  15. package/src/modes/components/welcome.ts +1 -0
  16. package/src/modes/controllers/command-controller.ts +47 -42
  17. package/src/modes/controllers/event-controller.ts +12 -9
  18. package/src/modes/controllers/input-controller.ts +54 -1
  19. package/src/modes/interactive-mode.ts +4 -10
  20. package/src/modes/prompt-action-autocomplete.ts +201 -0
  21. package/src/modes/types.ts +1 -0
  22. package/src/modes/utils/ui-helpers.ts +12 -0
  23. package/src/patch/index.ts +1 -1
  24. package/src/prompts/system/system-prompt.md +97 -107
  25. package/src/prompts/tools/ast-edit.md +5 -2
  26. package/src/prompts/tools/ast-grep.md +5 -2
  27. package/src/prompts/tools/inspect-image-system.md +20 -0
  28. package/src/prompts/tools/inspect-image.md +32 -0
  29. package/src/session/agent-session.ts +33 -36
  30. package/src/session/compaction/compaction.ts +26 -29
  31. package/src/session/session-manager.ts +15 -7
  32. package/src/tools/bash-interactive.ts +8 -3
  33. package/src/tools/fetch.ts +5 -27
  34. package/src/tools/index.ts +4 -0
  35. package/src/tools/inspect-image-renderer.ts +103 -0
  36. package/src/tools/inspect-image.ts +168 -0
  37. package/src/tools/read.ts +62 -49
  38. package/src/tools/renderers.ts +2 -0
  39. package/src/utils/image-input.ts +264 -0
  40. package/src/web/kagi.ts +0 -42
  41. package/src/web/scrapers/youtube.ts +0 -17
  42. package/src/web/search/index.ts +3 -1
  43. package/src/web/search/provider.ts +4 -1
  44. package/src/web/search/providers/exa.ts +8 -0
  45. package/src/web/search/providers/tavily.ts +162 -0
  46. package/src/web/search/types.ts +1 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Tavily Web Search Provider
3
+ *
4
+ * Uses Tavily's agent-focused search API to return structured results with an
5
+ * optional synthesized answer.
6
+ */
7
+ import { getEnvApiKey } from "@oh-my-pi/pi-ai";
8
+ import type { SearchResponse, SearchSource } from "../../../web/search/types";
9
+ import { SearchProviderError } from "../../../web/search/types";
10
+ import { clampNumResults, dateToAgeSeconds } from "../utils";
11
+ import type { SearchParams } from "./base";
12
+ import { SearchProvider } from "./base";
13
+ import { findCredential } from "./utils";
14
+
15
+ const TAVILY_SEARCH_URL = "https://api.tavily.com/search";
16
+ const DEFAULT_NUM_RESULTS = 5;
17
+ const MAX_NUM_RESULTS = 20;
18
+
19
+ export interface TavilySearchParams {
20
+ query: string;
21
+ num_results?: number;
22
+ recency?: "day" | "week" | "month" | "year";
23
+ signal?: AbortSignal;
24
+ }
25
+
26
+ interface TavilySearchResult {
27
+ title?: string | null;
28
+ url?: string | null;
29
+ content?: string | null;
30
+ published_date?: string | null;
31
+ }
32
+
33
+ interface TavilySearchResponse {
34
+ answer?: string | null;
35
+ results?: TavilySearchResult[];
36
+ request_id?: string | null;
37
+ }
38
+
39
+ function asRecord(value: unknown): Record<string, unknown> | null {
40
+ if (typeof value !== "object" || value === null) return null;
41
+ return value as Record<string, unknown>;
42
+ }
43
+
44
+ function getErrorMessage(value: unknown): string | null {
45
+ if (typeof value === "string") {
46
+ const trimmed = value.trim();
47
+ return trimmed.length > 0 ? trimmed : null;
48
+ }
49
+
50
+ const record = asRecord(value);
51
+ if (!record) return null;
52
+
53
+ for (const key of ["detail", "error", "message"]) {
54
+ const message = getErrorMessage(record[key]);
55
+ if (message) return message;
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ /** Find Tavily API key from environment or agent.db credentials. */
62
+ export async function findApiKey(): Promise<string | null> {
63
+ return findCredential(getEnvApiKey("tavily"), "tavily");
64
+ }
65
+
66
+ function buildRequestBody(params: TavilySearchParams): Record<string, unknown> {
67
+ const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
68
+ return {
69
+ query: params.query,
70
+ search_depth: "basic",
71
+ topic: params.recency ? "news" : "general",
72
+ time_range: params.recency,
73
+ max_results: numResults,
74
+ include_answer: "advanced",
75
+ include_raw_content: false,
76
+ };
77
+ }
78
+
79
+ async function callTavilySearch(apiKey: string, params: TavilySearchParams): Promise<TavilySearchResponse> {
80
+ const response = await fetch(TAVILY_SEARCH_URL, {
81
+ method: "POST",
82
+ headers: {
83
+ "Content-Type": "application/json",
84
+ Authorization: `Bearer ${apiKey}`,
85
+ },
86
+ body: JSON.stringify(buildRequestBody(params)),
87
+ signal: params.signal,
88
+ });
89
+
90
+ if (!response.ok) {
91
+ const errorText = await response.text();
92
+ let message = errorText.trim();
93
+ if (message.length === 0) {
94
+ message = response.statusText;
95
+ } else {
96
+ try {
97
+ message = getErrorMessage(JSON.parse(errorText)) ?? message;
98
+ } catch {
99
+ // Keep raw text fallback.
100
+ }
101
+ }
102
+ throw new SearchProviderError("tavily", `Tavily API error (${response.status}): ${message}`, response.status);
103
+ }
104
+
105
+ return (await response.json()) as TavilySearchResponse;
106
+ }
107
+
108
+ /** Execute Tavily web search. */
109
+ export async function searchTavily(params: TavilySearchParams): Promise<SearchResponse> {
110
+ const apiKey = await findApiKey();
111
+ if (!apiKey) {
112
+ throw new Error(
113
+ 'Tavily credentials not found. Set TAVILY_API_KEY or store an API key for provider "tavily" in agent.db.',
114
+ );
115
+ }
116
+
117
+ const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
118
+ const response = await callTavilySearch(apiKey, params);
119
+ const sources: SearchSource[] = [];
120
+
121
+ for (const result of response.results ?? []) {
122
+ if (!result.url) continue;
123
+ sources.push({
124
+ title: result.title ?? result.url,
125
+ url: result.url,
126
+ snippet: result.content ?? undefined,
127
+ publishedDate: result.published_date ?? undefined,
128
+ ageSeconds: dateToAgeSeconds(result.published_date ?? undefined),
129
+ });
130
+ }
131
+
132
+ return {
133
+ provider: "tavily",
134
+ answer: response.answer?.trim() || undefined,
135
+ sources: sources.slice(0, numResults),
136
+ requestId: response.request_id ?? undefined,
137
+ authMode: "api_key",
138
+ };
139
+ }
140
+
141
+ /** Search provider for Tavily web search. */
142
+ export class TavilyProvider extends SearchProvider {
143
+ readonly id = "tavily";
144
+ readonly label = "Tavily";
145
+
146
+ async isAvailable(): Promise<boolean> {
147
+ try {
148
+ return !!(await findApiKey());
149
+ } catch {
150
+ return false;
151
+ }
152
+ }
153
+
154
+ search(params: SearchParams): Promise<SearchResponse> {
155
+ return searchTavily({
156
+ query: params.query,
157
+ num_results: params.numSearchResults ?? params.limit,
158
+ recency: params.recency,
159
+ signal: params.signal,
160
+ });
161
+ }
162
+ }
@@ -15,6 +15,7 @@ export type SearchProviderId =
15
15
  | "perplexity"
16
16
  | "gemini"
17
17
  | "codex"
18
+ | "tavily"
18
19
  | "kagi"
19
20
  | "synthetic";
20
21