@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.1
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 +75 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +15 -5
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +18 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +4 -0
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-registry.ts +25 -2
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +20 -2
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/llm-bridge.ts +8 -3
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +9 -7
- package/src/memories/index.ts +12 -5
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +23 -0
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +124 -119
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +169 -94
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +18 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +11 -37
- package/src/session/agent-session.ts +82 -6
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +80 -78
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +81 -62
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
package/src/tui/status-line.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { formatStatusIcon } from "../tools/render-utils";
|
|
|
7
7
|
|
|
8
8
|
export interface StatusLineOptions {
|
|
9
9
|
icon?: ToolUIStatus;
|
|
10
|
+
/** Pre-rendered glyph that replaces the status icon (e.g. a magnifier for
|
|
11
|
+
* search-family tools). Takes precedence over `icon`. */
|
|
12
|
+
iconOverride?: string;
|
|
10
13
|
spinnerFrame?: number;
|
|
11
14
|
title: string;
|
|
12
15
|
titleColor?: ThemeColor;
|
|
@@ -27,7 +30,8 @@ function flattenForHeader(text: string): string {
|
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export function renderStatusLine(options: StatusLineOptions, theme: Theme): string {
|
|
30
|
-
const icon =
|
|
33
|
+
const icon =
|
|
34
|
+
options.iconOverride ?? (options.icon ? formatStatusIcon(options.icon, theme, options.spinnerFrame) : "");
|
|
31
35
|
const titleColor = options.titleColor ?? "accent";
|
|
32
36
|
const title = theme.fg(titleColor, flattenForHeader(options.title));
|
|
33
37
|
let line = icon ? `${icon} ${title}` : title;
|
|
@@ -6,6 +6,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
6
6
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
9
|
+
|
|
9
10
|
import type { ModelRegistry } from "../config/model-registry";
|
|
10
11
|
import { resolveModelRoleValue } from "../config/model-resolver";
|
|
11
12
|
import type { Settings } from "../config/settings";
|
|
@@ -110,7 +111,14 @@ export async function generateCommitMessage(
|
|
|
110
111
|
systemPrompt: [COMMIT_SYSTEM_PROMPT],
|
|
111
112
|
messages: [{ role: "user", content: userMessage, timestamp: Date.now() }],
|
|
112
113
|
},
|
|
113
|
-
{
|
|
114
|
+
{
|
|
115
|
+
apiKey: registry.resolver(candidate.model.provider, {
|
|
116
|
+
sessionId,
|
|
117
|
+
baseUrl: candidate.model.baseUrl,
|
|
118
|
+
}),
|
|
119
|
+
maxTokens,
|
|
120
|
+
reasoning: toReasoningEffort(candidate.thinkingLevel),
|
|
121
|
+
},
|
|
114
122
|
);
|
|
115
123
|
|
|
116
124
|
if (response.stopReason === "error") {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
|
+
|
|
3
|
+
const OSC5522_PREFIX = "\x1b]5522;";
|
|
4
|
+
const OSC_TERMINATOR_ST = "\x1b\\";
|
|
5
|
+
const OSC_TERMINATOR_BEL = "\x07";
|
|
6
|
+
const PASTE_EVENT_NAME_BASE64 = Buffer.from("Paste event", "utf8").toString("base64");
|
|
7
|
+
|
|
8
|
+
const IMAGE_MIME_PRIORITY = ["image/png", "image/jpeg", "image/webp", "image/gif"] as const;
|
|
9
|
+
const TEXT_MIME_TYPE = "text/plain";
|
|
10
|
+
|
|
11
|
+
type PasteReadKind = "image" | "text";
|
|
12
|
+
|
|
13
|
+
export interface Osc5522Packet {
|
|
14
|
+
metadata: Map<string, string>;
|
|
15
|
+
payload: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PasteListingState {
|
|
19
|
+
phase: "listing";
|
|
20
|
+
mimes: string[];
|
|
21
|
+
pw?: string;
|
|
22
|
+
loc?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface PasteReadState {
|
|
26
|
+
phase: "reading";
|
|
27
|
+
kind: PasteReadKind;
|
|
28
|
+
mimeType: string;
|
|
29
|
+
chunks: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type PasteState = PasteListingState | PasteReadState;
|
|
33
|
+
|
|
34
|
+
export interface EnhancedPasteHandlers {
|
|
35
|
+
write(data: string): void;
|
|
36
|
+
pasteText(text: string): void;
|
|
37
|
+
pasteImage(image: ImageContent): void | Promise<void>;
|
|
38
|
+
showStatus(message: string): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isOsc5522Packet(data: string): boolean {
|
|
42
|
+
return data.startsWith(OSC5522_PREFIX) && (data.endsWith(OSC_TERMINATOR_ST) || data.endsWith(OSC_TERMINATOR_BEL));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function decodeBase64Utf8(value: string): string | undefined {
|
|
46
|
+
try {
|
|
47
|
+
return Buffer.from(value, "base64").toString("utf8");
|
|
48
|
+
} catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseMetadata(raw: string): Map<string, string> {
|
|
54
|
+
const metadata = new Map<string, string>();
|
|
55
|
+
for (const part of raw.split(":")) {
|
|
56
|
+
const eq = part.indexOf("=");
|
|
57
|
+
if (eq <= 0) continue;
|
|
58
|
+
metadata.set(part.slice(0, eq), part.slice(eq + 1));
|
|
59
|
+
}
|
|
60
|
+
return metadata;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function parseOsc5522Packet(data: string): Osc5522Packet | undefined {
|
|
64
|
+
if (!isOsc5522Packet(data)) return undefined;
|
|
65
|
+
const bodyEnd = data.endsWith(OSC_TERMINATOR_BEL) ? data.length - 1 : data.length - OSC_TERMINATOR_ST.length;
|
|
66
|
+
const body = data.slice(OSC5522_PREFIX.length, bodyEnd);
|
|
67
|
+
const separator = body.indexOf(";");
|
|
68
|
+
const metadataRaw = separator === -1 ? body : body.slice(0, separator);
|
|
69
|
+
const payload = separator === -1 ? "" : body.slice(separator + 1);
|
|
70
|
+
return { metadata: parseMetadata(metadataRaw), payload };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function choosePasteMime(mimes: readonly string[]): { kind: PasteReadKind; mimeType: string } | undefined {
|
|
74
|
+
for (const mimeType of IMAGE_MIME_PRIORITY) {
|
|
75
|
+
if (mimes.includes(mimeType)) return { kind: "image", mimeType };
|
|
76
|
+
}
|
|
77
|
+
return mimes.includes(TEXT_MIME_TYPE) ? { kind: "text", mimeType: TEXT_MIME_TYPE } : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class EnhancedPasteController {
|
|
81
|
+
#state: PasteState | undefined;
|
|
82
|
+
#handlers: EnhancedPasteHandlers;
|
|
83
|
+
|
|
84
|
+
constructor(handlers: EnhancedPasteHandlers) {
|
|
85
|
+
this.#handlers = handlers;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
enable(): void {
|
|
89
|
+
this.#handlers.write("\x1b[?5522h");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
disable(): void {
|
|
93
|
+
this.#handlers.write("\x1b[?5522l");
|
|
94
|
+
this.#state = undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
handleInput(data: string): boolean {
|
|
98
|
+
const packet = parseOsc5522Packet(data);
|
|
99
|
+
if (!packet) return false;
|
|
100
|
+
void this.#handlePacket(packet);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async #handlePacket(packet: Osc5522Packet): Promise<void> {
|
|
105
|
+
const type = packet.metadata.get("type");
|
|
106
|
+
if (type !== "read") return;
|
|
107
|
+
|
|
108
|
+
const status = packet.metadata.get("status");
|
|
109
|
+
if (status === "OK") {
|
|
110
|
+
this.#handleOk(packet);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (status === "DATA") {
|
|
114
|
+
this.#handleData(packet);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (status === "DONE") {
|
|
118
|
+
await this.#handleDone();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (status) {
|
|
122
|
+
this.#state = undefined;
|
|
123
|
+
this.#handlers.showStatus(`Enhanced paste failed: ${status}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#handleOk(packet: Osc5522Packet): void {
|
|
128
|
+
if (this.#state?.phase === "reading") return;
|
|
129
|
+
const loc = packet.metadata.get("loc");
|
|
130
|
+
this.#state = {
|
|
131
|
+
phase: "listing",
|
|
132
|
+
mimes: [],
|
|
133
|
+
pw: packet.metadata.get("pw"),
|
|
134
|
+
loc: loc === "primary" ? loc : undefined,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#handleData(packet: Osc5522Packet): void {
|
|
139
|
+
const state = this.#state;
|
|
140
|
+
if (!state) return;
|
|
141
|
+
const encodedMime = packet.metadata.get("mime");
|
|
142
|
+
if (!encodedMime) return;
|
|
143
|
+
const mimeType = decodeBase64Utf8(encodedMime);
|
|
144
|
+
if (!mimeType) return;
|
|
145
|
+
|
|
146
|
+
if (state.phase === "listing") {
|
|
147
|
+
state.mimes.push(mimeType);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (state.mimeType === mimeType && packet.payload) {
|
|
152
|
+
state.chunks.push(packet.payload);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async #handleDone(): Promise<void> {
|
|
157
|
+
const state = this.#state;
|
|
158
|
+
if (!state) return;
|
|
159
|
+
if (state.phase === "listing") {
|
|
160
|
+
this.#finishListing(state);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
this.#state = undefined;
|
|
164
|
+
const bytes = Buffer.concat(state.chunks.map(chunk => Buffer.from(chunk, "base64")));
|
|
165
|
+
if (bytes.byteLength === 0) {
|
|
166
|
+
this.#handlers.showStatus("Clipboard paste was empty");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (state.kind === "text") {
|
|
170
|
+
this.#handlers.pasteText(bytes.toString("utf8"));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
await this.#handlers.pasteImage({
|
|
174
|
+
type: "image",
|
|
175
|
+
data: bytes.toString("base64"),
|
|
176
|
+
mimeType: state.mimeType,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#finishListing(state: PasteListingState): void {
|
|
181
|
+
const selected = choosePasteMime(state.mimes);
|
|
182
|
+
if (!selected) {
|
|
183
|
+
this.#state = undefined;
|
|
184
|
+
this.#handlers.showStatus("Clipboard paste has no supported text or image data");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.#state = {
|
|
189
|
+
phase: "reading",
|
|
190
|
+
kind: selected.kind,
|
|
191
|
+
mimeType: selected.mimeType,
|
|
192
|
+
chunks: [],
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const metadata = [`type=read`, `mime=${Buffer.from(selected.mimeType, "utf8").toString("base64")}`];
|
|
196
|
+
if (state.loc) metadata.push(`loc=${state.loc}`);
|
|
197
|
+
if (state.pw) {
|
|
198
|
+
metadata.push(`pw=${state.pw}`, `name=${PASTE_EVENT_NAME_BASE64}`);
|
|
199
|
+
}
|
|
200
|
+
this.#handlers.write(`${OSC5522_PREFIX}${metadata.join(":")}${OSC_TERMINATOR_ST}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -6,6 +6,7 @@ import * as path from "node:path";
|
|
|
6
6
|
import { type Api, type AssistantMessage, completeSimple, type Model, type Tool } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import type { ModelRegistry } from "../config/model-registry";
|
|
9
|
+
|
|
9
10
|
import { resolveRoleSelection } from "../config/model-resolver";
|
|
10
11
|
import type { Settings } from "../config/settings";
|
|
11
12
|
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
@@ -238,7 +239,7 @@ export async function generateTitleOnline(
|
|
|
238
239
|
tools: [setTitleTool],
|
|
239
240
|
},
|
|
240
241
|
{
|
|
241
|
-
apiKey,
|
|
242
|
+
apiKey: registry.resolver(model.provider, { sessionId, baseUrl: model.baseUrl }),
|
|
242
243
|
maxTokens,
|
|
243
244
|
disableReasoning: true,
|
|
244
245
|
toolChoice: { type: "tool", name: SET_TITLE_TOOL_NAME },
|
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
import {
|
|
8
8
|
type AnthropicAuthConfig,
|
|
9
9
|
type AnthropicSystemBlock,
|
|
10
|
+
type ApiKey,
|
|
10
11
|
type AuthStorage,
|
|
11
12
|
buildAnthropicAuthConfig,
|
|
12
13
|
buildAnthropicSearchHeaders,
|
|
13
14
|
buildAnthropicSystemBlocks,
|
|
14
15
|
buildAnthropicUrl,
|
|
15
16
|
stripClaudeToolPrefix,
|
|
17
|
+
withAuth,
|
|
16
18
|
} from "@oh-my-pi/pi-ai";
|
|
17
19
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
18
20
|
import type {
|
|
@@ -247,18 +249,13 @@ export async function searchAnthropic(
|
|
|
247
249
|
): Promise<SearchResponse> {
|
|
248
250
|
const searchApiKey = $env.ANTHROPIC_SEARCH_API_KEY;
|
|
249
251
|
const searchBaseUrl = $env.ANTHROPIC_SEARCH_BASE_URL;
|
|
250
|
-
|
|
252
|
+
const keyOrResolver: ApiKey | undefined = searchApiKey
|
|
253
|
+
? searchApiKey
|
|
254
|
+
: "authStorage" in params
|
|
255
|
+
? params.authStorage.resolver("anthropic", { sessionId: params.sessionId })
|
|
256
|
+
: undefined;
|
|
251
257
|
|
|
252
|
-
if (
|
|
253
|
-
auth = buildAnthropicAuthConfig(searchApiKey, searchBaseUrl);
|
|
254
|
-
} else if ("authStorage" in params) {
|
|
255
|
-
const apiKey = await params.authStorage.getApiKey("anthropic", params.sessionId, {
|
|
256
|
-
signal: params.signal,
|
|
257
|
-
});
|
|
258
|
-
if (apiKey) auth = buildAnthropicAuthConfig(apiKey, searchBaseUrl);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (!auth) {
|
|
258
|
+
if (!keyOrResolver) {
|
|
262
259
|
throw new Error(
|
|
263
260
|
"No Anthropic credentials found. Set ANTHROPIC_SEARCH_API_KEY or ANTHROPIC_API_KEY, or configure Anthropic OAuth.",
|
|
264
261
|
);
|
|
@@ -267,14 +264,23 @@ export async function searchAnthropic(
|
|
|
267
264
|
const model = getModel();
|
|
268
265
|
const systemPrompt = "authStorage" in params ? params.systemPrompt : params.system_prompt;
|
|
269
266
|
const maxTokens = "authStorage" in params ? params.maxOutputTokens : params.max_tokens;
|
|
270
|
-
const response = await
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
267
|
+
const response = await withAuth(
|
|
268
|
+
keyOrResolver,
|
|
269
|
+
key =>
|
|
270
|
+
callSearch(
|
|
271
|
+
buildAnthropicAuthConfig(key, searchBaseUrl),
|
|
272
|
+
model,
|
|
273
|
+
params.query,
|
|
274
|
+
systemPrompt,
|
|
275
|
+
maxTokens,
|
|
276
|
+
params.temperature,
|
|
277
|
+
params.signal,
|
|
278
|
+
),
|
|
279
|
+
{
|
|
280
|
+
signal: params.signal,
|
|
281
|
+
missingKeyMessage:
|
|
282
|
+
"No Anthropic credentials found. Set ANTHROPIC_SEARCH_API_KEY or ANTHROPIC_API_KEY, or configure Anthropic OAuth.",
|
|
283
|
+
},
|
|
278
284
|
);
|
|
279
285
|
|
|
280
286
|
const result = parseResponse(response);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Requests per-result summaries via `contents.summary` and synthesizes
|
|
7
7
|
* them into a combined `answer` string on the SearchResponse.
|
|
8
8
|
*/
|
|
9
|
-
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
9
|
+
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import { settings } from "../../../config/settings";
|
|
11
11
|
import { callExaTool, findApiKey, isSearchResponse } from "../../../exa/mcp-client";
|
|
12
12
|
|
|
@@ -228,11 +228,19 @@ async function callExaMcpSearch(params: ExaSearchParams): Promise<ExaSearchRespo
|
|
|
228
228
|
|
|
229
229
|
/** Execute Exa web search */
|
|
230
230
|
export async function searchExa(params: ExaSearchParams): Promise<SearchResponse> {
|
|
231
|
+
// AuthStorage-backed key takes precedence (existing behavior); probe it once
|
|
232
|
+
// so the env-key and keyless-MCP fallbacks below stay intact, then drive the
|
|
233
|
+
// authStorage path through the central force-refresh/rotate retry policy.
|
|
231
234
|
const storedKey = params.authStorage
|
|
232
235
|
? await params.authStorage.getApiKey("exa", params.sessionId, { signal: params.signal })
|
|
233
236
|
: undefined;
|
|
234
|
-
const
|
|
235
|
-
|
|
237
|
+
const keyOrResolver: ApiKey | undefined =
|
|
238
|
+
storedKey && params.authStorage
|
|
239
|
+
? params.authStorage.resolver("exa", { sessionId: params.sessionId })
|
|
240
|
+
: getEnvApiKey("exa");
|
|
241
|
+
const response = keyOrResolver
|
|
242
|
+
? await withAuth(keyOrResolver, key => callExaSearch(key, params), { signal: params.signal })
|
|
243
|
+
: await callExaMcpSearch(params);
|
|
236
244
|
|
|
237
245
|
// Convert to unified SearchResponse
|
|
238
246
|
const sources: SearchSource[] = [];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Uses Moonshot Kimi Code search API to retrieve web results.
|
|
5
5
|
* Endpoint: POST https://api.kimi.com/coding/v1/search
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type ApiKey, type AuthStorage, withAuth } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
9
9
|
|
|
10
10
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
@@ -54,20 +54,26 @@ function resolveBaseUrl(): string {
|
|
|
54
54
|
return asTrimmed($env.MOONSHOT_SEARCH_BASE_URL) ?? asTrimmed($env.KIMI_SEARCH_BASE_URL) ?? KIMI_SEARCH_URL;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
/**
|
|
58
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the Kimi search credential. Highest precedence is the static env key;
|
|
59
|
+
* otherwise an AuthStorage-backed resolver for whichever stored provider id
|
|
60
|
+
* holds a key (`moonshot` first, then `kimi-code`), so a stale token triggers
|
|
61
|
+
* the central force-refresh / sibling-rotate retry. Returns `undefined` when
|
|
62
|
+
* neither is configured.
|
|
63
|
+
*/
|
|
64
|
+
async function resolveKey(
|
|
59
65
|
authStorage: AuthStorage,
|
|
60
66
|
sessionId: string | undefined,
|
|
61
67
|
signal: AbortSignal | undefined,
|
|
62
|
-
): Promise<
|
|
68
|
+
): Promise<ApiKey | undefined> {
|
|
63
69
|
const envKey = asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ?? asTrimmed($env.KIMI_SEARCH_API_KEY);
|
|
64
70
|
if (envKey) return envKey;
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
for (const provider of ["moonshot", "kimi-code"] as const) {
|
|
73
|
+
const stored = await authStorage.getApiKey(provider, sessionId, { signal });
|
|
74
|
+
if (stored) return authStorage.resolver(provider, { sessionId });
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
async function callKimiSearch(
|
|
@@ -108,20 +114,25 @@ async function callKimiSearch(
|
|
|
108
114
|
|
|
109
115
|
/** Execute Kimi web search. */
|
|
110
116
|
export async function searchKimi(params: KimiSearchParams): Promise<SearchResponse> {
|
|
111
|
-
const
|
|
112
|
-
if (!
|
|
117
|
+
const keyOrResolver = await resolveKey(params.authStorage, params.sessionId, params.signal);
|
|
118
|
+
if (!keyOrResolver) {
|
|
113
119
|
throw new Error(
|
|
114
120
|
"Kimi search credentials not found. Set MOONSHOT_SEARCH_API_KEY, KIMI_SEARCH_API_KEY, MOONSHOT_API_KEY, or login with 'omp /login moonshot'.",
|
|
115
121
|
);
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
const limit = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
119
|
-
const { response, requestId } = await
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
const { response, requestId } = await withAuth(
|
|
126
|
+
keyOrResolver,
|
|
127
|
+
key =>
|
|
128
|
+
callKimiSearch(key, {
|
|
129
|
+
query: params.query,
|
|
130
|
+
limit,
|
|
131
|
+
includeContent: params.include_content ?? false,
|
|
132
|
+
signal: params.signal,
|
|
133
|
+
}),
|
|
134
|
+
{ signal: params.signal },
|
|
135
|
+
);
|
|
125
136
|
const sources: SearchSource[] = [];
|
|
126
137
|
|
|
127
138
|
for (const result of response.search_results ?? []) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { SearchResponse } from "../../../web/search/types";
|
|
3
3
|
import { SearchProviderError } from "../../../web/search/types";
|
|
4
4
|
import { ParallelApiError, type ParallelSearchResult, type ParallelSearchSource } from "../../parallel";
|
|
@@ -123,30 +123,41 @@ async function searchWithAuthStorage(
|
|
|
123
123
|
);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
126
|
+
// Drive the (already-present) credential through the central force-refresh /
|
|
127
|
+
// sibling-rotate retry policy. The `ParallelApiError` thrown below carries a
|
|
128
|
+
// `statusCode`, which `withAuth`'s default classifier reads to detect a
|
|
129
|
+
// retryable 401 / usage-limit.
|
|
130
|
+
const keyOrResolver: ApiKey = authStorage.resolver("parallel", { sessionId });
|
|
131
|
+
return withAuth(
|
|
132
|
+
keyOrResolver,
|
|
133
|
+
async key => {
|
|
134
|
+
const response = await fetch(PARALLEL_SEARCH_URL, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: {
|
|
137
|
+
Accept: "application/json",
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
"x-api-key": key,
|
|
140
|
+
"parallel-beta": PARALLEL_BETA_HEADER,
|
|
141
|
+
},
|
|
142
|
+
body: JSON.stringify({
|
|
143
|
+
objective,
|
|
144
|
+
search_queries: queries,
|
|
145
|
+
mode: "fast",
|
|
146
|
+
excerpts: {
|
|
147
|
+
max_chars_per_result: 10_000,
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
signal: withHardTimeout(params.signal),
|
|
151
|
+
});
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw parseParallelErrorResponse(response.status, await response.text());
|
|
154
|
+
}
|
|
147
155
|
|
|
148
|
-
|
|
149
|
-
|
|
156
|
+
const payload: unknown = await response.json();
|
|
157
|
+
return parseSearchPayload(payload);
|
|
158
|
+
},
|
|
159
|
+
{ signal: params.signal },
|
|
160
|
+
);
|
|
150
161
|
}
|
|
151
162
|
|
|
152
163
|
export async function searchParallel(
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Endpoint: POST https://api.synthetic.new/v2/search
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
10
10
|
import { SearchProviderError } from "../../../web/search/types";
|
|
11
11
|
import type { SearchParams } from "./base";
|
|
@@ -66,12 +66,14 @@ async function callSyntheticSearch(
|
|
|
66
66
|
|
|
67
67
|
/** Execute Synthetic web search. */
|
|
68
68
|
export async function searchSynthetic(params: SearchParams): Promise<SearchResponse> {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
69
|
+
const keyOrResolver: ApiKey = params.authStorage.resolver("synthetic", {
|
|
70
|
+
sessionId: params.sessionId,
|
|
71
|
+
});
|
|
73
72
|
|
|
74
|
-
const data = await callSyntheticSearch(
|
|
73
|
+
const data = await withAuth(keyOrResolver, key => callSyntheticSearch(key, params.query, params.signal), {
|
|
74
|
+
signal: params.signal,
|
|
75
|
+
missingKeyMessage: "Synthetic credentials not found. Set SYNTHETIC_API_KEY or login with 'omp /login synthetic'.",
|
|
76
|
+
});
|
|
75
77
|
const sources: SearchSource[] = [];
|
|
76
78
|
|
|
77
79
|
for (const result of data.results ?? []) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Uses Tavily's agent-focused search API to return structured results with an
|
|
5
5
|
* optional synthesized answer.
|
|
6
6
|
*/
|
|
7
|
-
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
9
9
|
import { SearchProviderError } from "../../../web/search/types";
|
|
10
10
|
import { clampNumResults, dateToAgeSeconds } from "../utils";
|
|
@@ -127,15 +127,16 @@ export async function searchTavily(params: SearchParams): Promise<SearchResponse
|
|
|
127
127
|
recency: params.recency,
|
|
128
128
|
signal: params.signal,
|
|
129
129
|
};
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
'Tavily credentials not found. Set TAVILY_API_KEY or configure an API key for provider "tavily".',
|
|
134
|
-
);
|
|
135
|
-
}
|
|
130
|
+
const keyOrResolver: ApiKey = params.authStorage.resolver("tavily", {
|
|
131
|
+
sessionId: params.sessionId,
|
|
132
|
+
});
|
|
136
133
|
|
|
137
134
|
const numResults = clampNumResults(tavilyParams.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
138
|
-
const response = await callTavilySearch(
|
|
135
|
+
const response = await withAuth(keyOrResolver, key => callTavilySearch(key, tavilyParams), {
|
|
136
|
+
signal: params.signal,
|
|
137
|
+
missingKeyMessage:
|
|
138
|
+
'Tavily credentials not found. Set TAVILY_API_KEY or configure an API key for provider "tavily".',
|
|
139
|
+
});
|
|
139
140
|
const sources: SearchSource[] = [];
|
|
140
141
|
|
|
141
142
|
for (const result of response.results ?? []) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Calls Z.AI's remote MCP server (`webSearchPrime`) and adapts results into
|
|
5
5
|
* the unified SearchResponse shape used by the web search tool.
|
|
6
6
|
*/
|
|
7
|
-
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { asRecord, asString } from "../../../web/scrapers/utils";
|
|
9
9
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
10
10
|
import { SearchProviderError } from "../../../web/search/types";
|
|
@@ -278,12 +278,14 @@ function toSources(results: ZaiSearchResult[]): SearchSource[] {
|
|
|
278
278
|
|
|
279
279
|
/** Execute Z.AI web search via remote MCP endpoint. */
|
|
280
280
|
export async function searchZai(params: ZaiSearchParams): Promise<SearchResponse> {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
281
|
+
const keyOrResolver: ApiKey = params.authStorage.resolver("zai", {
|
|
282
|
+
sessionId: params.sessionId,
|
|
283
|
+
});
|
|
285
284
|
|
|
286
|
-
const rawResult = await callZaiSearch(
|
|
285
|
+
const rawResult = await withAuth(keyOrResolver, key => callZaiSearch(key, params), {
|
|
286
|
+
signal: params.signal,
|
|
287
|
+
missingKeyMessage: "Z.AI credentials not found. Set ZAI_API_KEY or login with 'omp /login zai'.",
|
|
288
|
+
});
|
|
287
289
|
const payload = parseSearchPayload(rawResult);
|
|
288
290
|
let sources = toSources(payload.results);
|
|
289
291
|
|