@oh-my-pi/pi-coding-agent 16.1.23 → 16.2.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 +70 -0
- package/dist/cli.js +3817 -3735
- package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
- package/dist/types/advisor/emission-guard.d.ts +73 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/runtime.d.ts +7 -0
- package/dist/types/advisor/watchdog.d.ts +2 -0
- package/dist/types/cli/flag-tables.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
- package/dist/types/collab/display-name.d.ts +3 -0
- package/dist/types/collab/host.d.ts +0 -2
- package/dist/types/commands/worktree.d.ts +0 -3
- package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
- package/dist/types/config/model-discovery.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +6 -2
- package/dist/types/config/model-resolver.d.ts +12 -0
- package/dist/types/config/models-config-schema.d.ts +29 -2
- package/dist/types/config/models-config.d.ts +22 -1
- package/dist/types/config/settings-schema.d.ts +91 -21
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/dap/config.d.ts +1 -1
- package/dist/types/edit/hashline/filesystem.d.ts +4 -2
- package/dist/types/edit/renderer.d.ts +4 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -17
- package/dist/types/extensibility/hooks/types.d.ts +10 -10
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
- package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
- package/dist/types/internal-urls/router.d.ts +1 -1
- package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
- package/dist/types/internal-urls/types.d.ts +19 -2
- package/dist/types/irc/bus.d.ts +6 -0
- package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/move-overlay.d.ts +23 -0
- package/dist/types/modes/components/plugin-selector.d.ts +2 -1
- package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
- package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +4 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/show-images-selector.d.ts +2 -1
- package/dist/types/modes/components/status-line/component.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/theme-selector.d.ts +2 -1
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/modes/controllers/command-controller.d.ts +10 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -1
- package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
- package/dist/types/modes/running-subagent-badge.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +9 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +2 -1
- package/dist/types/session/session-manager.d.ts +13 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
- package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
- package/dist/types/ssh/connection-manager.d.ts +2 -0
- package/dist/types/ssh/file-transfer.d.ts +79 -0
- package/dist/types/ssh/utils.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +3 -0
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tiny/text.d.ts +1 -1
- package/dist/types/tools/builtin-names.d.ts +5 -1
- package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
- package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tools/path-utils.d.ts +29 -0
- package/dist/types/tools/read.d.ts +2 -2
- package/dist/types/tools/render-utils.d.ts +8 -0
- package/dist/types/tools/renderers.d.ts +11 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo.d.ts +0 -16
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/active-repo-context.d.ts +8 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/utils/markit-cache.d.ts +23 -0
- package/dist/types/utils/markit.d.ts +5 -1
- package/dist/types/utils/prompt-path.d.ts +1 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
- package/dist/types/web/search/providers/xai.d.ts +13 -0
- package/dist/types/web/search/types.d.ts +18 -2
- package/package.json +30 -15
- package/scripts/build-binary.ts +9 -9
- package/scripts/bundle-dist.ts +2 -2
- package/src/advisor/__tests__/advisor.test.ts +40 -4
- package/src/advisor/__tests__/emission-guard.test.ts +147 -0
- package/src/advisor/advise-tool.ts +1 -1
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +11 -0
- package/src/advisor/watchdog.ts +12 -1
- package/src/cli/args.ts +5 -2
- package/src/cli/auth-broker-cli.ts +17 -0
- package/src/cli/config-cli.ts +4 -0
- package/src/cli/flag-tables.ts +7 -4
- package/src/cli/gallery-cli.ts +14 -2
- package/src/cli/gallery-fixtures/edit.ts +60 -0
- package/src/cli/gallery-fixtures/fs.ts +17 -17
- package/src/cli/gallery-fixtures/search.ts +4 -4
- package/src/cli/usage-cli.ts +20 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +5 -1
- package/src/collab/host.ts +2 -13
- package/src/commands/worktree.ts +6 -0
- package/src/config/inline-tool-descriptors-mode.ts +27 -0
- package/src/config/model-discovery.ts +77 -8
- package/src/config/model-registry.ts +89 -11
- package/src/config/model-resolver.ts +39 -1
- package/src/config/models-config-schema.ts +41 -5
- package/src/config/models-config.ts +4 -1
- package/src/config/settings-schema.ts +85 -27
- package/src/config/settings.ts +172 -1
- package/src/cursor.ts +1 -1
- package/src/dap/config.ts +152 -8
- package/src/dap/session.ts +1 -1
- package/src/discovery/helpers.ts +3 -1
- package/src/edit/hashline/diff.ts +14 -3
- package/src/edit/hashline/execute.ts +42 -6
- package/src/edit/hashline/filesystem.ts +38 -2
- package/src/edit/index.ts +1 -0
- package/src/edit/modes/patch.ts +11 -1
- package/src/edit/renderer.ts +140 -13
- package/src/eval/__tests__/agent-bridge.test.ts +101 -0
- package/src/eval/agent-bridge.ts +26 -3
- package/src/eval/js/tool-bridge.ts +2 -2
- package/src/export/html/index.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/export/html/tool-views.generated.js +20 -20
- package/src/extensibility/extensions/types.ts +22 -22
- package/src/extensibility/hooks/types.ts +11 -11
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +8 -3
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +25 -10
- package/src/extensibility/plugins/marketplace/manager.ts +22 -0
- package/src/extensibility/plugins/marketplace/types.ts +1 -0
- package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
- package/src/internal-urls/docs-index.generated.txt +2 -2
- package/src/internal-urls/docs-index.ts +2 -3
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/registry-helpers.ts +19 -4
- package/src/internal-urls/router.ts +5 -3
- package/src/internal-urls/ssh-protocol.ts +367 -0
- package/src/internal-urls/types.ts +19 -2
- package/src/irc/bus.ts +11 -3
- package/src/mcp/tool-bridge.ts +32 -2
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +2 -2
- package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
- package/src/modes/components/agent-hub.ts +3 -0
- package/src/modes/components/agent-transcript-viewer.ts +9 -7
- package/src/modes/components/custom-editor.ts +90 -5
- package/src/modes/components/move-overlay.ts +282 -0
- package/src/modes/components/plan-review-overlay.ts +35 -35
- package/src/modes/components/plugin-selector.ts +6 -1
- package/src/modes/components/queue-mode-selector.ts +6 -1
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/session-selector.ts +11 -10
- package/src/modes/components/settings-defs.ts +14 -1
- package/src/modes/components/settings-selector.ts +196 -29
- package/src/modes/components/show-images-selector.ts +6 -1
- package/src/modes/components/status-line/component.ts +108 -28
- package/src/modes/components/status-line/segments.ts +5 -1
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/theme-selector.ts +6 -1
- package/src/modes/components/thinking-selector.ts +6 -1
- package/src/modes/components/tool-execution.ts +25 -9
- package/src/modes/components/tree-selector.ts +5 -5
- package/src/modes/controllers/command-controller.ts +140 -47
- package/src/modes/controllers/event-controller.ts +59 -3
- package/src/modes/controllers/input-controller.ts +68 -2
- package/src/modes/controllers/selector-controller.ts +5 -2
- package/src/modes/controllers/streaming-reveal.ts +17 -0
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +116 -50
- package/src/modes/internal-url-autocomplete.ts +17 -2
- package/src/modes/prompt-action-autocomplete.ts +3 -1
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
- package/src/modes/setup-wizard/scenes/providers.ts +2 -1
- package/src/modes/setup-wizard/scenes/theme.ts +6 -12
- package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
- package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
- package/src/modes/theme/theme.ts +9 -1
- package/src/modes/types.ts +9 -1
- package/src/modes/utils/interactive-context-helpers.ts +1 -1
- package/src/priority.json +15 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/system.md +21 -7
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/plan-mode-active.md +3 -3
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -6
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/{find.md → glob.md} +1 -1
- package/src/prompts/tools/{search.md → grep.md} +3 -3
- package/src/prompts/tools/read.md +4 -2
- package/src/sdk.ts +55 -25
- package/src/session/agent-session.ts +508 -132
- package/src/session/session-history-format.ts +2 -2
- package/src/session/session-manager.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +138 -20
- package/src/slash-commands/helpers/usage-report.ts +23 -2
- package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
- package/src/ssh/connection-manager.ts +12 -15
- package/src/ssh/file-transfer.ts +209 -0
- package/src/ssh/utils.ts +24 -0
- package/src/system-prompt.ts +56 -28
- package/src/task/executor.ts +96 -36
- package/src/task/index.ts +3 -3
- package/src/task/render.ts +14 -13
- package/src/task/worktree.ts +38 -1
- package/src/tiny/text.ts +49 -4
- package/src/tiny/worker.ts +3 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/builtin-names.ts +26 -2
- package/src/tools/{find.ts → glob.ts} +48 -42
- package/src/tools/{search.ts → grep.ts} +311 -129
- package/src/tools/index.ts +13 -14
- package/src/tools/irc.ts +6 -2
- package/src/tools/path-utils.ts +64 -1
- package/src/tools/read.ts +35 -6
- package/src/tools/render-utils.ts +14 -0
- package/src/tools/renderers.ts +15 -4
- package/src/tools/ssh.ts +17 -2
- package/src/tools/todo.ts +4 -26
- package/src/tools/write.ts +25 -9
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/image-resize.ts +88 -7
- package/src/utils/lang-from-path.ts +3 -3
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +86 -18
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/title-generator.ts +1 -1
- package/src/web/search/provider.ts +54 -34
- package/src/web/search/providers/duckduckgo.ts +140 -0
- package/src/web/search/providers/firecrawl.ts +144 -0
- package/src/web/search/providers/tinyfish.ts +159 -0
- package/src/web/search/providers/xai.ts +292 -0
- package/src/web/search/providers/zai.ts +142 -56
- package/src/web/search/types.ts +6 -2
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { type ApiKey, type AuthStorage, withAuth } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { SearchCitation, SearchResponse, SearchSource, SearchUsage } from "../../../web/search/types";
|
|
3
|
+
import { SearchProviderError } from "../../../web/search/types";
|
|
4
|
+
import { clampNumResults } from "../utils";
|
|
5
|
+
import type { SearchParams } from "./base";
|
|
6
|
+
import { SearchProvider } from "./base";
|
|
7
|
+
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
8
|
+
|
|
9
|
+
const XAI_RESPONSES_URL = "https://api.x.ai/v1/responses";
|
|
10
|
+
const XAI_WEB_SEARCH_MODEL = "grok-4.3";
|
|
11
|
+
const DEFAULT_NUM_RESULTS = 10;
|
|
12
|
+
const MAX_NUM_RESULTS = 30;
|
|
13
|
+
const RECENCY_DAYS: Record<NonNullable<SearchParams["recency"]>, number> = {
|
|
14
|
+
day: 1,
|
|
15
|
+
week: 7,
|
|
16
|
+
month: 30,
|
|
17
|
+
year: 365,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function formatUtcDate(date: Date): string {
|
|
21
|
+
return date.toISOString().slice(0, 10);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildRecencyDateBounds(
|
|
25
|
+
recency: NonNullable<SearchParams["recency"]>,
|
|
26
|
+
now = new Date(),
|
|
27
|
+
): {
|
|
28
|
+
from_date: string;
|
|
29
|
+
to_date: string;
|
|
30
|
+
} {
|
|
31
|
+
const toDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
32
|
+
const fromDate = new Date(toDate);
|
|
33
|
+
fromDate.setUTCDate(fromDate.getUTCDate() - RECENCY_DAYS[recency]);
|
|
34
|
+
return {
|
|
35
|
+
from_date: formatUtcDate(fromDate),
|
|
36
|
+
to_date: formatUtcDate(toDate),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface XAIUrlCitationAnnotation {
|
|
41
|
+
type?: string;
|
|
42
|
+
url?: string | null;
|
|
43
|
+
title?: string | null;
|
|
44
|
+
text?: string | null;
|
|
45
|
+
cited_text?: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface XAIResponseContentPart {
|
|
49
|
+
type?: string;
|
|
50
|
+
text?: string | null;
|
|
51
|
+
output_text?: string | null;
|
|
52
|
+
annotations?: XAIUrlCitationAnnotation[] | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface XAIResponseOutputItem {
|
|
56
|
+
content?: XAIResponseContentPart[] | null;
|
|
57
|
+
annotations?: XAIUrlCitationAnnotation[] | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface XAIResponsesUsage {
|
|
61
|
+
input_tokens?: number;
|
|
62
|
+
output_tokens?: number;
|
|
63
|
+
total_tokens?: number;
|
|
64
|
+
inputTokens?: number;
|
|
65
|
+
outputTokens?: number;
|
|
66
|
+
totalTokens?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface XAIResponsesResponse {
|
|
70
|
+
id?: string;
|
|
71
|
+
model?: string;
|
|
72
|
+
output_text?: string | null;
|
|
73
|
+
output?: XAIResponseOutputItem[] | null;
|
|
74
|
+
annotations?: XAIUrlCitationAnnotation[] | null;
|
|
75
|
+
citations?: string[] | null;
|
|
76
|
+
usage?: XAIResponsesUsage | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildRequestBody(params: SearchParams): Record<string, unknown> {
|
|
80
|
+
const body: Record<string, unknown> = {
|
|
81
|
+
model: XAI_WEB_SEARCH_MODEL,
|
|
82
|
+
input: [
|
|
83
|
+
{ role: "system", content: params.systemPrompt },
|
|
84
|
+
{ role: "user", content: params.query },
|
|
85
|
+
],
|
|
86
|
+
tools: [{ type: "web_search" }],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const requestedSearchResults = params.numSearchResults ?? params.limit;
|
|
90
|
+
const searchParameters: Record<string, unknown> = {};
|
|
91
|
+
if (requestedSearchResults !== undefined) {
|
|
92
|
+
searchParameters.max_search_results = clampNumResults(
|
|
93
|
+
requestedSearchResults,
|
|
94
|
+
DEFAULT_NUM_RESULTS,
|
|
95
|
+
MAX_NUM_RESULTS,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (params.recency) {
|
|
99
|
+
Object.assign(searchParameters, buildRecencyDateBounds(params.recency));
|
|
100
|
+
}
|
|
101
|
+
if (Object.keys(searchParameters).length > 0) {
|
|
102
|
+
body.search_parameters = searchParameters;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (params.maxOutputTokens !== undefined) {
|
|
106
|
+
body.max_output_tokens = params.maxOutputTokens;
|
|
107
|
+
}
|
|
108
|
+
if (params.temperature !== undefined) {
|
|
109
|
+
body.temperature = params.temperature;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return body;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function postXAIResponses(
|
|
116
|
+
apiKey: string,
|
|
117
|
+
params: SearchParams,
|
|
118
|
+
body: Record<string, unknown>,
|
|
119
|
+
): Promise<Response> {
|
|
120
|
+
return (params.fetch ?? fetch)(XAI_RESPONSES_URL, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
Authorization: `Bearer ${apiKey}`,
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify(body),
|
|
127
|
+
signal: withHardTimeout(params.signal),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function throwXAIResponsesError(status: number, errorText: string): never {
|
|
132
|
+
const classified = classifyProviderHttpError("xai", status, errorText);
|
|
133
|
+
if (classified) throw classified;
|
|
134
|
+
throw new SearchProviderError("xai", `xAI Responses API error (${status}): ${errorText}`, status);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function callXAIResponses(apiKey: string, params: SearchParams): Promise<XAIResponsesResponse> {
|
|
138
|
+
const requestBody = buildRequestBody(params);
|
|
139
|
+
const response = await postXAIResponses(apiKey, params, requestBody);
|
|
140
|
+
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
throwXAIResponsesError(response.status, await response.text());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (await response.json()) as XAIResponsesResponse;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function addCitationSource(
|
|
149
|
+
sources: SearchSource[],
|
|
150
|
+
citations: SearchCitation[],
|
|
151
|
+
seenUrls: Set<string>,
|
|
152
|
+
url: string,
|
|
153
|
+
title?: string | null,
|
|
154
|
+
citedText?: string | null,
|
|
155
|
+
): void {
|
|
156
|
+
const trimmedUrl = url.trim();
|
|
157
|
+
if (!trimmedUrl || seenUrls.has(trimmedUrl)) return;
|
|
158
|
+
seenUrls.add(trimmedUrl);
|
|
159
|
+
const sourceTitle = title?.trim() || trimmedUrl;
|
|
160
|
+
const sourceSnippet = citedText?.trim() || undefined;
|
|
161
|
+
|
|
162
|
+
sources.push({
|
|
163
|
+
title: sourceTitle,
|
|
164
|
+
url: trimmedUrl,
|
|
165
|
+
snippet: sourceSnippet,
|
|
166
|
+
});
|
|
167
|
+
citations.push({
|
|
168
|
+
title: sourceTitle,
|
|
169
|
+
url: trimmedUrl,
|
|
170
|
+
citedText: sourceSnippet,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function collectAnnotationSources(
|
|
175
|
+
annotations: readonly XAIUrlCitationAnnotation[] | null | undefined,
|
|
176
|
+
sources: SearchSource[],
|
|
177
|
+
citations: SearchCitation[],
|
|
178
|
+
seenUrls: Set<string>,
|
|
179
|
+
): void {
|
|
180
|
+
if (!annotations) return;
|
|
181
|
+
for (const annotation of annotations) {
|
|
182
|
+
if (annotation.type !== "url_citation" || !annotation.url) continue;
|
|
183
|
+
addCitationSource(
|
|
184
|
+
sources,
|
|
185
|
+
citations,
|
|
186
|
+
seenUrls,
|
|
187
|
+
annotation.url,
|
|
188
|
+
annotation.title,
|
|
189
|
+
annotation.cited_text ?? annotation.text,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function parseAnswer(response: XAIResponsesResponse): string | undefined {
|
|
195
|
+
const topLevelText = response.output_text?.trim();
|
|
196
|
+
if (topLevelText) return topLevelText;
|
|
197
|
+
|
|
198
|
+
const answerParts: string[] = [];
|
|
199
|
+
for (const item of response.output ?? []) {
|
|
200
|
+
for (const part of item.content ?? []) {
|
|
201
|
+
const text = part.output_text ?? part.text;
|
|
202
|
+
if ((part.type === "output_text" || part.type === "text") && text?.trim()) {
|
|
203
|
+
answerParts.push(text.trim());
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const answer = answerParts.join("\n").trim();
|
|
209
|
+
return answer ? answer : undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function parseUsage(usage: XAIResponsesUsage | null | undefined): SearchUsage | undefined {
|
|
213
|
+
if (!usage) return undefined;
|
|
214
|
+
const parsed: SearchUsage = {};
|
|
215
|
+
const inputTokens = usage.input_tokens ?? usage.inputTokens;
|
|
216
|
+
const outputTokens = usage.output_tokens ?? usage.outputTokens;
|
|
217
|
+
const totalTokens = usage.total_tokens ?? usage.totalTokens;
|
|
218
|
+
|
|
219
|
+
if (typeof inputTokens === "number") parsed.inputTokens = inputTokens;
|
|
220
|
+
if (typeof outputTokens === "number") parsed.outputTokens = outputTokens;
|
|
221
|
+
if (typeof totalTokens === "number") parsed.totalTokens = totalTokens;
|
|
222
|
+
|
|
223
|
+
return Object.keys(parsed).length > 0 ? parsed : undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function applyResultCap(
|
|
227
|
+
sources: SearchSource[],
|
|
228
|
+
citations: SearchCitation[],
|
|
229
|
+
resultCap: number,
|
|
230
|
+
): { sources: SearchSource[]; citations: SearchCitation[] } {
|
|
231
|
+
return {
|
|
232
|
+
sources: sources.slice(0, resultCap),
|
|
233
|
+
citations: citations.slice(0, resultCap),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function parseResponse(response: XAIResponsesResponse, resultCap: number): SearchResponse {
|
|
238
|
+
const sources: SearchSource[] = [];
|
|
239
|
+
const citations: SearchCitation[] = [];
|
|
240
|
+
const seenUrls = new Set<string>();
|
|
241
|
+
|
|
242
|
+
collectAnnotationSources(response.annotations, sources, citations, seenUrls);
|
|
243
|
+
for (const item of response.output ?? []) {
|
|
244
|
+
collectAnnotationSources(item.annotations, sources, citations, seenUrls);
|
|
245
|
+
for (const part of item.content ?? []) {
|
|
246
|
+
collectAnnotationSources(part.annotations, sources, citations, seenUrls);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
for (const url of response.citations ?? []) {
|
|
250
|
+
addCitationSource(sources, citations, seenUrls, url);
|
|
251
|
+
}
|
|
252
|
+
const limited = applyResultCap(sources, citations, resultCap);
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
provider: "xai",
|
|
256
|
+
answer: parseAnswer(response),
|
|
257
|
+
sources: limited.sources,
|
|
258
|
+
citations: limited.citations.length > 0 ? limited.citations : undefined,
|
|
259
|
+
usage: parseUsage(response.usage),
|
|
260
|
+
model: response.model,
|
|
261
|
+
requestId: response.id,
|
|
262
|
+
authMode: "api_key",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Execute xAI Responses API web search. */
|
|
267
|
+
export async function searchXAI(params: SearchParams): Promise<SearchResponse> {
|
|
268
|
+
const keyOrResolver: ApiKey = params.authStorage.resolver("xai", {
|
|
269
|
+
sessionId: params.sessionId,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const resultCap = clampNumResults(params.numSearchResults ?? params.limit, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
273
|
+
const response = await withAuth(keyOrResolver, (key: string) => callXAIResponses(key, params), {
|
|
274
|
+
signal: params.signal,
|
|
275
|
+
missingKeyMessage: 'xAI credentials not found. Set XAI_API_KEY or configure an API key for provider "xai".',
|
|
276
|
+
});
|
|
277
|
+
return parseResponse(response, resultCap);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Search provider for xAI web search. */
|
|
281
|
+
export class XAIProvider extends SearchProvider {
|
|
282
|
+
readonly id = "xai";
|
|
283
|
+
readonly label = "xAI";
|
|
284
|
+
|
|
285
|
+
isAvailable(authStorage: AuthStorage): boolean {
|
|
286
|
+
return authStorage.hasAuth("xai");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
search(params: SearchParams): Promise<SearchResponse> {
|
|
290
|
+
return searchXAI(params);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* the unified SearchResponse shape used by the web search tool.
|
|
6
6
|
*/
|
|
7
7
|
import { type ApiKey, type AuthStorage, type FetchImpl, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
8
|
-
import {
|
|
8
|
+
import { isRecord } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
10
10
|
import { SearchProviderError } from "../../../web/search/types";
|
|
11
11
|
import { dateToAgeSeconds } from "../utils";
|
|
@@ -54,49 +54,24 @@ interface JsonRpcPayload {
|
|
|
54
54
|
error?: JsonRpcError;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
sessionId?: string,
|
|
61
|
-
signal?: AbortSignal,
|
|
62
|
-
): Promise<string | null> {
|
|
63
|
-
return (await authStorage.getApiKey("zai", sessionId, { signal })) ?? null;
|
|
57
|
+
interface ZaiMcpPostResult {
|
|
58
|
+
parsed?: unknown;
|
|
59
|
+
sessionId?: string;
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
): Promise<unknown> {
|
|
72
|
-
const response = await fetchImpl(ZAI_MCP_URL, {
|
|
73
|
-
method: "POST",
|
|
74
|
-
headers: {
|
|
75
|
-
Authorization: `Bearer ${apiKey}`,
|
|
76
|
-
"Content-Type": "application/json",
|
|
77
|
-
Accept: "application/json, text/event-stream",
|
|
78
|
-
},
|
|
79
|
-
body: JSON.stringify({
|
|
80
|
-
jsonrpc: "2.0",
|
|
81
|
-
id: crypto.randomUUID(),
|
|
82
|
-
method: "tools/call",
|
|
83
|
-
params: {
|
|
84
|
-
name: ZAI_TOOL_NAME,
|
|
85
|
-
arguments: args,
|
|
86
|
-
},
|
|
87
|
-
}),
|
|
88
|
-
signal: withHardTimeout(signal),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
const errorText = await response.text();
|
|
93
|
-
const classified = classifyProviderHttpError("zai", response.status, errorText);
|
|
94
|
-
if (classified) throw classified;
|
|
95
|
-
throw new SearchProviderError("zai", `Z.AI MCP error (${response.status}): ${errorText}`, response.status);
|
|
96
|
-
}
|
|
62
|
+
const ZAI_MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
63
|
+
const ZAI_MCP_CLIENT_INFO = {
|
|
64
|
+
name: "omp-coding-agent",
|
|
65
|
+
version: "1.0.0",
|
|
66
|
+
};
|
|
97
67
|
|
|
98
|
-
|
|
68
|
+
function asString(value: unknown): string | null {
|
|
69
|
+
if (typeof value !== "string") return null;
|
|
70
|
+
const trimmed = value.trim();
|
|
71
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
72
|
+
}
|
|
99
73
|
|
|
74
|
+
function parseZaiMcpResponse(rawText: string): unknown {
|
|
100
75
|
const parsedMessages: unknown[] = [];
|
|
101
76
|
for (const line of rawText.split("\n")) {
|
|
102
77
|
const trimmed = line.trim();
|
|
@@ -118,8 +93,64 @@ async function callZaiTool(
|
|
|
118
93
|
}
|
|
119
94
|
}
|
|
120
95
|
|
|
121
|
-
|
|
122
|
-
|
|
96
|
+
return parsedMessages[parsedMessages.length - 1];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function postZaiMcp(
|
|
100
|
+
apiKey: string,
|
|
101
|
+
method: string,
|
|
102
|
+
params: Record<string, unknown>,
|
|
103
|
+
sessionId: string | undefined,
|
|
104
|
+
signal: AbortSignal | undefined,
|
|
105
|
+
fetchImpl: FetchImpl,
|
|
106
|
+
expectResponse: boolean,
|
|
107
|
+
): Promise<ZaiMcpPostResult> {
|
|
108
|
+
const headers: Record<string, string> = {
|
|
109
|
+
Authorization: `Bearer ${apiKey}`,
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
Accept: "application/json, text/event-stream",
|
|
112
|
+
};
|
|
113
|
+
if (sessionId) {
|
|
114
|
+
headers["Mcp-Session-Id"] = sessionId;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const body: Record<string, unknown> = {
|
|
118
|
+
jsonrpc: "2.0",
|
|
119
|
+
method,
|
|
120
|
+
params,
|
|
121
|
+
};
|
|
122
|
+
if (expectResponse) {
|
|
123
|
+
body.id = crypto.randomUUID();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const response = await fetchImpl(ZAI_MCP_URL, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers,
|
|
129
|
+
body: JSON.stringify(body),
|
|
130
|
+
signal: withHardTimeout(signal),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
const errorText = await response.text();
|
|
135
|
+
const classified = classifyProviderHttpError("zai", response.status, errorText);
|
|
136
|
+
if (classified) throw classified;
|
|
137
|
+
throw new SearchProviderError("zai", `Z.AI MCP error (${response.status}): ${errorText}`, response.status);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const nextSessionId = response.headers.get("Mcp-Session-Id") ?? sessionId;
|
|
141
|
+
if (!expectResponse) {
|
|
142
|
+
await response.body?.cancel();
|
|
143
|
+
return { sessionId: nextSessionId };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
parsed: parseZaiMcpResponse(await response.text()),
|
|
148
|
+
sessionId: nextSessionId,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readJsonRpcPayload(parsed: unknown): JsonRpcPayload {
|
|
153
|
+
const parsedRecord = isRecord(parsed) ? parsed : null;
|
|
123
154
|
const directErrorCode = typeof parsedRecord?.code === "number" ? parsedRecord.code : undefined;
|
|
124
155
|
const directErrorSuccess = parsedRecord?.success;
|
|
125
156
|
const directErrorMessage =
|
|
@@ -132,6 +163,10 @@ async function callZaiTool(
|
|
|
132
163
|
);
|
|
133
164
|
}
|
|
134
165
|
|
|
166
|
+
if (!isRecord(parsed)) {
|
|
167
|
+
throw new SearchProviderError("zai", "Failed to parse Z.AI MCP response", 500);
|
|
168
|
+
}
|
|
169
|
+
|
|
135
170
|
const payload = parsed as JsonRpcPayload;
|
|
136
171
|
if (payload.error) {
|
|
137
172
|
const status = typeof payload.error.code === "number" ? payload.error.code : 400;
|
|
@@ -142,11 +177,64 @@ async function callZaiTool(
|
|
|
142
177
|
);
|
|
143
178
|
}
|
|
144
179
|
|
|
145
|
-
|
|
180
|
+
return payload;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Resolve Z.AI API credentials through the unified auth storage pipeline. */
|
|
184
|
+
export async function findApiKey(
|
|
185
|
+
authStorage: AuthStorage,
|
|
186
|
+
sessionId?: string,
|
|
187
|
+
signal?: AbortSignal,
|
|
188
|
+
): Promise<string | null> {
|
|
189
|
+
return (await authStorage.getApiKey("zai", sessionId, { signal })) ?? null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function callZaiTool(
|
|
193
|
+
apiKey: string,
|
|
194
|
+
args: Record<string, unknown>,
|
|
195
|
+
signal: AbortSignal | undefined,
|
|
196
|
+
fetchImpl: FetchImpl,
|
|
197
|
+
): Promise<unknown> {
|
|
198
|
+
const initialized = await postZaiMcp(
|
|
199
|
+
apiKey,
|
|
200
|
+
"initialize",
|
|
201
|
+
{
|
|
202
|
+
protocolVersion: ZAI_MCP_PROTOCOL_VERSION,
|
|
203
|
+
capabilities: {},
|
|
204
|
+
clientInfo: ZAI_MCP_CLIENT_INFO,
|
|
205
|
+
},
|
|
206
|
+
undefined,
|
|
207
|
+
signal,
|
|
208
|
+
fetchImpl,
|
|
209
|
+
true,
|
|
210
|
+
);
|
|
211
|
+
if (initialized.parsed !== undefined) {
|
|
212
|
+
readJsonRpcPayload(initialized.parsed);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await postZaiMcp(apiKey, "notifications/initialized", {}, initialized.sessionId, signal, fetchImpl, false);
|
|
216
|
+
|
|
217
|
+
const toolCall = await postZaiMcp(
|
|
218
|
+
apiKey,
|
|
219
|
+
"tools/call",
|
|
220
|
+
{
|
|
221
|
+
name: ZAI_TOOL_NAME,
|
|
222
|
+
arguments: args,
|
|
223
|
+
},
|
|
224
|
+
initialized.sessionId,
|
|
225
|
+
signal,
|
|
226
|
+
fetchImpl,
|
|
227
|
+
true,
|
|
228
|
+
);
|
|
229
|
+
const payload = readJsonRpcPayload(toolCall.parsed);
|
|
230
|
+
const resultRecord = isRecord(payload.result) ? payload.result : null;
|
|
146
231
|
if (resultRecord?.isError === true) {
|
|
147
232
|
const content = Array.isArray(resultRecord.content) ? resultRecord.content : [];
|
|
148
233
|
const errorText = content
|
|
149
|
-
.map(item =>
|
|
234
|
+
.map(item => {
|
|
235
|
+
if (!isRecord(item)) return null;
|
|
236
|
+
return asString(item.text);
|
|
237
|
+
})
|
|
150
238
|
.filter((text): text is string => text != null)
|
|
151
239
|
.join("\n")
|
|
152
240
|
.trim();
|
|
@@ -159,7 +247,7 @@ async function callZaiTool(
|
|
|
159
247
|
return payload.result;
|
|
160
248
|
}
|
|
161
249
|
|
|
162
|
-
return parsed;
|
|
250
|
+
return toolCall.parsed;
|
|
163
251
|
}
|
|
164
252
|
|
|
165
253
|
async function callZaiSearch(apiKey: string, params: ZaiSearchParams): Promise<unknown> {
|
|
@@ -204,8 +292,8 @@ function getSearchResults(value: unknown): ZaiSearchResult[] {
|
|
|
204
292
|
if (Array.isArray(value)) {
|
|
205
293
|
return value as ZaiSearchResult[];
|
|
206
294
|
}
|
|
207
|
-
|
|
208
|
-
|
|
295
|
+
if (!isRecord(value)) return [];
|
|
296
|
+
const obj = value;
|
|
209
297
|
|
|
210
298
|
const searchResult = obj.search_result;
|
|
211
299
|
if (Array.isArray(searchResult)) return searchResult as ZaiSearchResult[];
|
|
@@ -224,17 +312,15 @@ function parseSearchPayload(rawResult: unknown): {
|
|
|
224
312
|
const candidates: unknown[] = [rawResult];
|
|
225
313
|
const textParts: string[] = [];
|
|
226
314
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
if (
|
|
231
|
-
if (root.result) candidates.push(root.result);
|
|
315
|
+
if (isRecord(rawResult)) {
|
|
316
|
+
if (rawResult.structuredContent) candidates.push(rawResult.structuredContent);
|
|
317
|
+
if (rawResult.data) candidates.push(rawResult.data);
|
|
318
|
+
if (rawResult.result) candidates.push(rawResult.result);
|
|
232
319
|
|
|
233
|
-
const content =
|
|
320
|
+
const content = rawResult.content;
|
|
234
321
|
if (Array.isArray(content)) {
|
|
235
322
|
for (const part of content) {
|
|
236
|
-
const
|
|
237
|
-
const text = asString(partObj?.text);
|
|
323
|
+
const text = isRecord(part) ? asString(part.text) : null;
|
|
238
324
|
if (!text) continue;
|
|
239
325
|
textParts.push(text);
|
|
240
326
|
try {
|
|
@@ -249,7 +335,7 @@ function parseSearchPayload(rawResult: unknown): {
|
|
|
249
335
|
for (const candidate of candidates) {
|
|
250
336
|
const results = getSearchResults(candidate);
|
|
251
337
|
if (results.length > 0) {
|
|
252
|
-
const obj =
|
|
338
|
+
const obj = isRecord(candidate) ? (candidate as ZaiWebSearchResponse) : null;
|
|
253
339
|
return {
|
|
254
340
|
results,
|
|
255
341
|
answer: textParts.length > 0 ? textParts.join("\n\n") : undefined,
|
package/src/web/search/types.ts
CHANGED
|
@@ -30,16 +30,20 @@ export const SEARCH_PROVIDER_OPTIONS = [
|
|
|
30
30
|
label: "OpenAI",
|
|
31
31
|
description: "OpenAI's native web_search (uses ChatGPT OAuth via /login openai-codex)",
|
|
32
32
|
},
|
|
33
|
+
{ value: "xai", label: "xAI", description: "Grok web search via xAI Responses API (requires XAI_API_KEY)" },
|
|
33
34
|
{ value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" },
|
|
34
35
|
{ value: "exa", label: "Exa", description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP" },
|
|
36
|
+
{ value: "tinyfish", label: "TinyFish", description: "Requires TINYFISH_API_KEY" },
|
|
35
37
|
{ value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
|
|
36
38
|
{ value: "kagi", label: "Kagi", description: "Requires KAGI_API_KEY and Kagi Search API beta access" },
|
|
37
39
|
{ value: "tavily", label: "Tavily", description: "Requires TAVILY_API_KEY" },
|
|
40
|
+
{ value: "firecrawl", label: "Firecrawl", description: "Requires FIRECRAWL_API_KEY" },
|
|
38
41
|
{ value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
|
|
39
42
|
{ value: "kimi", label: "Kimi", description: "Requires MOONSHOT_SEARCH_API_KEY or MOONSHOT_API_KEY" },
|
|
40
43
|
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
41
44
|
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
42
45
|
{ value: "searxng", label: "SearXNG", description: "Requires SEARXNG_ENDPOINT or searxng.endpoint" },
|
|
46
|
+
{ value: "duckduckgo", label: "DuckDuckGo", description: "Uses DuckDuckGo Instant Answer API (no API key)" },
|
|
43
47
|
] as const;
|
|
44
48
|
|
|
45
49
|
/** Supported web search providers (every option except `auto`). */
|
|
@@ -81,7 +85,7 @@ export interface SearchSource {
|
|
|
81
85
|
author?: string;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
/** Citation with text reference (
|
|
88
|
+
/** Citation with text reference (LLM-mediated providers) */
|
|
85
89
|
export interface SearchCitation {
|
|
86
90
|
url: string;
|
|
87
91
|
title: string;
|
|
@@ -101,7 +105,7 @@ export interface SearchUsage {
|
|
|
101
105
|
/** Unified response across providers */
|
|
102
106
|
export interface SearchResponse {
|
|
103
107
|
provider: SearchProviderId | "none";
|
|
104
|
-
/** Synthesized answer text (
|
|
108
|
+
/** Synthesized answer text (LLM-mediated providers) */
|
|
105
109
|
answer?: string;
|
|
106
110
|
/** Search result sources */
|
|
107
111
|
sources: SearchSource[];
|