@oh-my-pi/pi-coding-agent 13.5.4 → 13.5.5
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 +6 -0
- package/package.json +7 -7
- package/src/config/settings-schema.ts +1 -0
- package/src/modes/components/settings-defs.ts +1 -0
- package/src/web/search/index.ts +15 -1
- package/src/web/search/provider.ts +3 -0
- package/src/web/search/providers/kagi.ts +148 -0
- package/src/web/search/types.ts +1 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.5.
|
|
4
|
+
"version": "13.5.5",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "^0.6",
|
|
44
|
-
"@oh-my-pi/omp-stats": "13.5.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.5.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.5.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.5.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.5.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.5.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.5.5",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.5.5",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.5.5",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.5.5",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.5.5",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.5.5",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
|
@@ -194,6 +194,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
194
194
|
{ value: "perplexity", label: "Perplexity", description: "Requires PERPLEXITY_COOKIES or PERPLEXITY_API_KEY" },
|
|
195
195
|
{ value: "anthropic", label: "Anthropic", description: "Uses Anthropic web search" },
|
|
196
196
|
{ value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" },
|
|
197
|
+
{ value: "kagi", label: "Kagi", description: "Requires KAGI_API_KEY" },
|
|
197
198
|
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
198
199
|
],
|
|
199
200
|
"providers.image": [
|
package/src/web/search/index.ts
CHANGED
|
@@ -34,7 +34,20 @@ export const webSearchSchema = Type.Object({
|
|
|
34
34
|
query: Type.String({ description: "Search query" }),
|
|
35
35
|
provider: Type.Optional(
|
|
36
36
|
StringEnum(
|
|
37
|
-
[
|
|
37
|
+
[
|
|
38
|
+
"auto",
|
|
39
|
+
"exa",
|
|
40
|
+
"brave",
|
|
41
|
+
"jina",
|
|
42
|
+
"kimi",
|
|
43
|
+
"zai",
|
|
44
|
+
"anthropic",
|
|
45
|
+
"perplexity",
|
|
46
|
+
"gemini",
|
|
47
|
+
"codex",
|
|
48
|
+
"kagi",
|
|
49
|
+
"synthetic",
|
|
50
|
+
],
|
|
38
51
|
{
|
|
39
52
|
description: "Search provider (default: auto)",
|
|
40
53
|
},
|
|
@@ -64,6 +77,7 @@ export type SearchParams = {
|
|
|
64
77
|
| "perplexity"
|
|
65
78
|
| "gemini"
|
|
66
79
|
| "codex"
|
|
80
|
+
| "kagi"
|
|
67
81
|
| "synthetic";
|
|
68
82
|
recency?: "day" | "week" | "month" | "year";
|
|
69
83
|
limit?: number;
|
|
@@ -5,6 +5,7 @@ import { CodexProvider } from "./providers/codex";
|
|
|
5
5
|
import { ExaProvider } from "./providers/exa";
|
|
6
6
|
import { GeminiProvider } from "./providers/gemini";
|
|
7
7
|
import { JinaProvider } from "./providers/jina";
|
|
8
|
+
import { KagiProvider } from "./providers/kagi";
|
|
8
9
|
import { KimiProvider } from "./providers/kimi";
|
|
9
10
|
import { PerplexityProvider } from "./providers/perplexity";
|
|
10
11
|
import { SyntheticProvider } from "./providers/synthetic";
|
|
@@ -24,6 +25,7 @@ const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
|
|
|
24
25
|
anthropic: new AnthropicProvider(),
|
|
25
26
|
gemini: new GeminiProvider(),
|
|
26
27
|
codex: new CodexProvider(),
|
|
28
|
+
kagi: new KagiProvider(),
|
|
27
29
|
synthetic: new SyntheticProvider(),
|
|
28
30
|
} as const;
|
|
29
31
|
|
|
@@ -37,6 +39,7 @@ export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
|
|
|
37
39
|
"codex",
|
|
38
40
|
"zai",
|
|
39
41
|
"exa",
|
|
42
|
+
"kagi",
|
|
40
43
|
"synthetic",
|
|
41
44
|
];
|
|
42
45
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kagi Web Search Provider
|
|
3
|
+
*
|
|
4
|
+
* Calls Kagi's Search API (v0) and maps results into the unified
|
|
5
|
+
* SearchResponse shape used by the web search tool.
|
|
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
|
+
|
|
14
|
+
const KAGI_SEARCH_URL = "https://kagi.com/api/v0/search";
|
|
15
|
+
const DEFAULT_NUM_RESULTS = 10;
|
|
16
|
+
const MAX_NUM_RESULTS = 40;
|
|
17
|
+
|
|
18
|
+
interface KagiSearchResult {
|
|
19
|
+
t: 0;
|
|
20
|
+
url: string;
|
|
21
|
+
title: string;
|
|
22
|
+
snippet?: string;
|
|
23
|
+
published?: string;
|
|
24
|
+
thumbnail?: {
|
|
25
|
+
url: string;
|
|
26
|
+
width?: number | null;
|
|
27
|
+
height?: number | null;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface KagiRelatedSearches {
|
|
32
|
+
t: 1;
|
|
33
|
+
list: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type KagiSearchObject = KagiSearchResult | KagiRelatedSearches;
|
|
37
|
+
|
|
38
|
+
interface KagiMeta {
|
|
39
|
+
id: string;
|
|
40
|
+
node: string;
|
|
41
|
+
ms: number;
|
|
42
|
+
api_balance?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface KagiSearchResponse {
|
|
46
|
+
meta: KagiMeta;
|
|
47
|
+
data: KagiSearchObject[];
|
|
48
|
+
error?: Array<{ code: number; msg: string; ref?: unknown }>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Find KAGI_API_KEY from environment or .env files. */
|
|
52
|
+
export function findApiKey(): string | null {
|
|
53
|
+
return getEnvApiKey("kagi") ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function callKagiSearch(
|
|
57
|
+
apiKey: string,
|
|
58
|
+
query: string,
|
|
59
|
+
limit: number,
|
|
60
|
+
signal?: AbortSignal,
|
|
61
|
+
): Promise<KagiSearchResponse> {
|
|
62
|
+
const url = new URL(KAGI_SEARCH_URL);
|
|
63
|
+
url.searchParams.set("q", query);
|
|
64
|
+
url.searchParams.set("limit", String(limit));
|
|
65
|
+
|
|
66
|
+
const response = await fetch(url, {
|
|
67
|
+
headers: {
|
|
68
|
+
Authorization: `Bot ${apiKey}`,
|
|
69
|
+
Accept: "application/json",
|
|
70
|
+
},
|
|
71
|
+
signal,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const errorText = await response.text();
|
|
76
|
+
throw new SearchProviderError("kagi", `Kagi API error (${response.status}): ${errorText}`, response.status);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const data = (await response.json()) as KagiSearchResponse;
|
|
80
|
+
|
|
81
|
+
if (data.error && data.error.length > 0) {
|
|
82
|
+
const firstError = data.error[0];
|
|
83
|
+
throw new SearchProviderError("kagi", `Kagi API error: ${firstError.msg}`, firstError.code);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Execute Kagi web search. */
|
|
90
|
+
export async function searchKagi(params: {
|
|
91
|
+
query: string;
|
|
92
|
+
num_results?: number;
|
|
93
|
+
signal?: AbortSignal;
|
|
94
|
+
}): Promise<SearchResponse> {
|
|
95
|
+
const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
96
|
+
const apiKey = findApiKey();
|
|
97
|
+
if (!apiKey) {
|
|
98
|
+
throw new Error("KAGI_API_KEY not found. Set it in environment or .env file.");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const data = await callKagiSearch(apiKey, params.query, numResults, params.signal);
|
|
102
|
+
|
|
103
|
+
const sources: SearchSource[] = [];
|
|
104
|
+
const relatedQuestions: string[] = [];
|
|
105
|
+
|
|
106
|
+
for (const item of data.data) {
|
|
107
|
+
if (item.t === 0) {
|
|
108
|
+
sources.push({
|
|
109
|
+
title: item.title,
|
|
110
|
+
url: item.url,
|
|
111
|
+
snippet: item.snippet,
|
|
112
|
+
publishedDate: item.published ?? undefined,
|
|
113
|
+
ageSeconds: dateToAgeSeconds(item.published),
|
|
114
|
+
});
|
|
115
|
+
} else if (item.t === 1) {
|
|
116
|
+
relatedQuestions.push(...item.list);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
provider: "kagi",
|
|
122
|
+
sources: sources.slice(0, numResults),
|
|
123
|
+
relatedQuestions: relatedQuestions.length > 0 ? relatedQuestions : undefined,
|
|
124
|
+
requestId: data.meta.id,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Search provider for Kagi web search. */
|
|
129
|
+
export class KagiProvider extends SearchProvider {
|
|
130
|
+
readonly id = "kagi";
|
|
131
|
+
readonly label = "Kagi";
|
|
132
|
+
|
|
133
|
+
isAvailable() {
|
|
134
|
+
try {
|
|
135
|
+
return !!findApiKey();
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
search(params: SearchParams): Promise<SearchResponse> {
|
|
142
|
+
return searchKagi({
|
|
143
|
+
query: params.query,
|
|
144
|
+
num_results: params.numSearchResults ?? params.limit,
|
|
145
|
+
signal: params.signal,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|