@oh-my-pi/pi-coding-agent 15.8.3 → 15.9.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 +74 -31
- package/dist/types/capability/skill.d.ts +7 -0
- package/dist/types/config/settings-schema.d.ts +36 -22
- package/dist/types/debug/protocol-probe.d.ts +38 -0
- package/dist/types/debug/terminal-info.d.ts +34 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mcp/transports/stdio.d.ts +16 -7
- package/dist/types/modes/components/assistant-message.d.ts +3 -2
- package/dist/types/modes/components/hook-selector.d.ts +11 -0
- package/dist/types/modes/components/todo-reminder.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +36 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +20 -1
- package/dist/types/plan-mode/plan-handoff.d.ts +20 -0
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/indexed-session-storage.d.ts +59 -0
- package/dist/types/session/redis-session-storage.d.ts +12 -85
- package/dist/types/session/session-manager.d.ts +20 -0
- package/dist/types/session/session-storage.d.ts +5 -7
- package/dist/types/session/sql-session-storage.d.ts +16 -85
- package/dist/types/task/executor.d.ts +9 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/telemetry-export.d.ts +19 -0
- package/dist/types/tiny/compiled-runtime.d.ts +35 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/index.d.ts +4 -2
- package/dist/types/tools/path-utils.d.ts +1 -1
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/{todo-write.d.ts → todo.d.ts} +18 -18
- package/dist/types/utils/session-color.d.ts +7 -2
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +2 -2
- package/dist/types/web/search/types.d.ts +65 -1
- package/package.json +15 -9
- package/scripts/build-binary.ts +12 -0
- package/src/capability/skill.ts +7 -0
- package/src/cli/args.ts +1 -1
- package/src/config/settings-schema.ts +22 -55
- package/src/debug/index.ts +67 -1
- package/src/debug/protocol-probe.ts +267 -0
- package/src/debug/terminal-info.ts +127 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +3 -3
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +3 -3
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/main.ts +12 -0
- package/src/mcp/transports/stdio.ts +37 -12
- package/src/modes/acp/acp-event-mapper.ts +7 -7
- package/src/modes/components/assistant-message.ts +3 -2
- package/src/modes/components/hook-selector.ts +149 -14
- package/src/modes/components/session-selector.ts +36 -5
- package/src/modes/components/status-line/segments.ts +2 -1
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/todo-reminder.ts +1 -1
- package/src/modes/components/tool-execution.ts +9 -4
- package/src/modes/components/transcript-container.ts +91 -0
- package/src/modes/controllers/event-controller.ts +12 -7
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/todo-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +18 -6
- package/src/modes/print-mode.ts +5 -0
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/modes/theme/theme.ts +48 -8
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/plan-mode/plan-handoff.ts +37 -0
- package/src/priority.json +4 -0
- package/src/prompts/goals/goal-continuation.md +1 -1
- package/src/prompts/system/eager-todo.md +3 -3
- package/src/prompts/system/orchestrate-notice.md +5 -5
- package/src/prompts/system/plan-mode-approved.md +14 -17
- package/src/prompts/system/plan-mode-reference.md +3 -6
- package/src/prompts/system/subagent-system-prompt.md +11 -0
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/browser.md +5 -2
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +3 -1
- package/src/prompts/tools/{todo-write.md → todo.md} +5 -5
- package/src/sdk.ts +1 -0
- package/src/session/agent-session.ts +14 -14
- package/src/session/indexed-session-storage.ts +430 -0
- package/src/session/redis-session-storage.ts +66 -377
- package/src/session/session-manager.ts +102 -22
- package/src/session/session-storage.ts +148 -68
- package/src/session/sql-session-storage.ts +131 -382
- package/src/slash-commands/helpers/todo.ts +2 -2
- package/src/task/executor.ts +9 -1
- package/src/task/index.ts +51 -1
- package/src/telemetry-export.ts +126 -0
- package/src/tiny/compiled-runtime.ts +179 -0
- package/src/tiny/worker.ts +24 -2
- package/src/tools/ask.ts +133 -87
- package/src/tools/fetch.ts +17 -4
- package/src/tools/find.ts +2 -2
- package/src/tools/index.ts +6 -4
- package/src/tools/path-utils.ts +16 -7
- package/src/tools/renderers.ts +2 -2
- package/src/tools/search.ts +6 -3
- package/src/tools/{todo-write.ts → todo.ts} +32 -35
- package/src/utils/session-color.ts +39 -14
- package/src/web/search/index.ts +1 -1
- package/src/web/search/provider.ts +18 -34
- package/src/web/search/types.ts +73 -32
|
@@ -6,7 +6,7 @@ import chalk from "chalk";
|
|
|
6
6
|
import * as z from "zod/v4";
|
|
7
7
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
8
|
import type { Theme } from "../modes/theme/theme";
|
|
9
|
-
import
|
|
9
|
+
import todoDescription from "../prompts/tools/todo.md" with { type: "text" };
|
|
10
10
|
import type { ToolSession } from "../sdk";
|
|
11
11
|
import type { SessionEntry } from "../session/session-manager";
|
|
12
12
|
import { renderStatusLine, renderTreeList } from "../tui";
|
|
@@ -40,7 +40,7 @@ export interface TodoCompletionTransition {
|
|
|
40
40
|
content: string;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export interface
|
|
43
|
+
export interface TodoToolDetails {
|
|
44
44
|
phases: TodoPhase[];
|
|
45
45
|
storage: "session" | "memory";
|
|
46
46
|
completedTasks?: TodoCompletionTransition[];
|
|
@@ -68,14 +68,14 @@ const TodoOpEntry = z.object({
|
|
|
68
68
|
text: z.string().optional().describe("note text"),
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
const
|
|
71
|
+
const todoSchema = z
|
|
72
72
|
.object({
|
|
73
73
|
ops: z.array(TodoOpEntry).min(1).describe("ordered todo operations"),
|
|
74
74
|
})
|
|
75
75
|
.describe("apply ordered todo operations");
|
|
76
76
|
|
|
77
|
-
type
|
|
78
|
-
type TodoOpEntryValue =
|
|
77
|
+
type TodoParams = z.infer<typeof todoSchema>;
|
|
78
|
+
type TodoOpEntryValue = TodoParams["ops"][number];
|
|
79
79
|
|
|
80
80
|
// =============================================================================
|
|
81
81
|
// State helpers
|
|
@@ -159,7 +159,7 @@ export function getLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPha
|
|
|
159
159
|
}
|
|
160
160
|
if (entry.type !== "message") continue;
|
|
161
161
|
const message = entry.message as { role?: string; toolName?: string; details?: unknown; isError?: boolean };
|
|
162
|
-
if (message.role !== "toolResult" || message.toolName !== "
|
|
162
|
+
if (message.role !== "toolResult" || message.toolName !== "todo" || message.isError) continue;
|
|
163
163
|
|
|
164
164
|
const details = message.details as { phases?: unknown } | undefined;
|
|
165
165
|
if (!details || !Array.isArray(details.phases)) continue;
|
|
@@ -178,7 +178,7 @@ export function getLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPha
|
|
|
178
178
|
* the caller can render a `+N more` hint. When every task in `tasks` is
|
|
179
179
|
* closed (completed or abandoned), returns the trailing `maxVisible` tasks
|
|
180
180
|
* with `hiddenOpenCount = 0`, so the panel keeps useful context until the
|
|
181
|
-
* active-phase pointer advances on the next `
|
|
181
|
+
* active-phase pointer advances on the next `todo`.
|
|
182
182
|
*
|
|
183
183
|
* Task identity and order are preserved — this is a slice, never a sort.
|
|
184
184
|
*/
|
|
@@ -383,7 +383,7 @@ function applyEntry(phases: TodoPhase[], entry: TodoOpEntryValue, errors: string
|
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
function applyParams(phases: TodoPhase[], params:
|
|
386
|
+
function applyParams(phases: TodoPhase[], params: TodoParams): { phases: TodoPhase[]; errors: string[] } {
|
|
387
387
|
const errors: string[] = [];
|
|
388
388
|
let next = phases;
|
|
389
389
|
for (const entry of params.ops) {
|
|
@@ -393,10 +393,10 @@ function applyParams(phases: TodoPhase[], params: TodoWriteParams): { phases: To
|
|
|
393
393
|
return { phases: next, errors };
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
/** Apply an array of `
|
|
396
|
+
/** Apply an array of `todo`-style ops to existing phases. Used by /todo slash command. */
|
|
397
397
|
export function applyOpsToPhases(
|
|
398
398
|
currentPhases: TodoPhase[],
|
|
399
|
-
ops:
|
|
399
|
+
ops: TodoParams["ops"],
|
|
400
400
|
): { phases: TodoPhase[]; errors: string[] } {
|
|
401
401
|
return applyParams(clonePhases(currentPhases), { ops });
|
|
402
402
|
}
|
|
@@ -586,33 +586,33 @@ function formatSummary(phases: TodoPhase[], errors: string[]): string {
|
|
|
586
586
|
// Tool Class
|
|
587
587
|
// =============================================================================
|
|
588
588
|
|
|
589
|
-
export class
|
|
590
|
-
readonly name = "
|
|
589
|
+
export class TodoTool implements AgentTool<typeof todoSchema, TodoToolDetails> {
|
|
590
|
+
readonly name = "todo";
|
|
591
591
|
readonly approval = "read" as const;
|
|
592
|
-
readonly label = "Todo
|
|
592
|
+
readonly label = "Todo";
|
|
593
593
|
readonly summary = "Write a structured todo list to track progress within a session";
|
|
594
594
|
readonly description: string;
|
|
595
|
-
readonly parameters =
|
|
595
|
+
readonly parameters = todoSchema;
|
|
596
596
|
readonly concurrency = "exclusive";
|
|
597
597
|
readonly strict = true;
|
|
598
598
|
readonly loadMode = "discoverable";
|
|
599
599
|
constructor(private readonly session: ToolSession) {
|
|
600
|
-
this.description = prompt.render(
|
|
600
|
+
this.description = prompt.render(todoDescription);
|
|
601
601
|
}
|
|
602
602
|
|
|
603
603
|
async execute(
|
|
604
604
|
_toolCallId: string,
|
|
605
|
-
params:
|
|
605
|
+
params: TodoParams,
|
|
606
606
|
_signal?: AbortSignal,
|
|
607
|
-
_onUpdate?: AgentToolUpdateCallback<
|
|
607
|
+
_onUpdate?: AgentToolUpdateCallback<TodoToolDetails>,
|
|
608
608
|
_context?: AgentToolContext,
|
|
609
|
-
): Promise<AgentToolResult<
|
|
609
|
+
): Promise<AgentToolResult<TodoToolDetails>> {
|
|
610
610
|
const previousPhases = clonePhases(this.session.getTodoPhases?.() ?? []);
|
|
611
611
|
const { phases: updated, errors } = applyParams(clonePhases(previousPhases), params);
|
|
612
612
|
const completedTasks = getCompletionTransitions(previousPhases, updated);
|
|
613
613
|
this.session.setTodoPhases?.(updated);
|
|
614
614
|
const storage = this.session.getSessionFile() ? "session" : "memory";
|
|
615
|
-
const details:
|
|
615
|
+
const details: TodoToolDetails = { phases: updated, storage };
|
|
616
616
|
if (completedTasks.length > 0) details.completedTasks = completedTasks;
|
|
617
617
|
|
|
618
618
|
return {
|
|
@@ -627,7 +627,7 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
627
627
|
// TUI Renderer
|
|
628
628
|
// =============================================================================
|
|
629
629
|
|
|
630
|
-
type
|
|
630
|
+
type TodoRenderArgs = {
|
|
631
631
|
ops?: Array<{
|
|
632
632
|
op?: string;
|
|
633
633
|
task?: string;
|
|
@@ -701,9 +701,9 @@ function noteMarker(count: number, uiTheme: Theme): string {
|
|
|
701
701
|
return uiTheme.fg("dim", chalk.italic(` \u207a${toSuperscript(count)}`));
|
|
702
702
|
}
|
|
703
703
|
|
|
704
|
-
export const
|
|
705
|
-
export const
|
|
706
|
-
export const
|
|
704
|
+
export const TODO_STRIKE_HOLD_FRAMES = 2;
|
|
705
|
+
export const TODO_STRIKE_REVEAL_FRAMES = 12;
|
|
706
|
+
export const TODO_STRIKE_TOTAL_FRAMES = TODO_STRIKE_HOLD_FRAMES + TODO_STRIKE_REVEAL_FRAMES;
|
|
707
707
|
const EMPTY_COMPLETION_KEYS = new Set<string>();
|
|
708
708
|
const STRIKE_START = "\x1b[9m";
|
|
709
709
|
const STRIKE_END = "\x1b[29m";
|
|
@@ -721,11 +721,11 @@ function partialStrikethrough(text: string, visibleChars: number): string {
|
|
|
721
721
|
|
|
722
722
|
function strikeRevealCount(text: string, frame: number | undefined): number | undefined {
|
|
723
723
|
if (frame === undefined) return undefined;
|
|
724
|
-
if (frame <=
|
|
724
|
+
if (frame <= TODO_STRIKE_HOLD_FRAMES) return 0;
|
|
725
725
|
const chars = [...text];
|
|
726
726
|
if (chars.length === 0) return undefined;
|
|
727
|
-
const revealFrame = Math.min(frame -
|
|
728
|
-
return Math.ceil((chars.length * revealFrame) /
|
|
727
|
+
const revealFrame = Math.min(frame - TODO_STRIKE_HOLD_FRAMES, TODO_STRIKE_REVEAL_FRAMES);
|
|
728
|
+
return Math.ceil((chars.length * revealFrame) / TODO_STRIKE_REVEAL_FRAMES);
|
|
729
729
|
}
|
|
730
730
|
|
|
731
731
|
function formatTodoLine(
|
|
@@ -775,8 +775,8 @@ function renderNoteAttachments(phases: TodoPhase[], uiTheme: Theme): string[] {
|
|
|
775
775
|
return lines;
|
|
776
776
|
}
|
|
777
777
|
|
|
778
|
-
export const
|
|
779
|
-
renderCall(args:
|
|
778
|
+
export const todoToolRenderer = {
|
|
779
|
+
renderCall(args: TodoRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
780
780
|
const ops = args?.ops?.map(entry => {
|
|
781
781
|
const parts = [entry.op ?? "update"];
|
|
782
782
|
if (entry.task) parts.push(entry.task);
|
|
@@ -784,15 +784,15 @@ export const todoWriteToolRenderer = {
|
|
|
784
784
|
if (entry.items?.length) parts.push(`${entry.items.length} item${entry.items.length === 1 ? "" : "s"}`);
|
|
785
785
|
return parts.join(" ");
|
|
786
786
|
}) ?? ["update"];
|
|
787
|
-
const text = renderStatusLine({ icon: "pending", title: "Todo
|
|
787
|
+
const text = renderStatusLine({ icon: "pending", title: "Todo", meta: ops }, uiTheme);
|
|
788
788
|
return new Text(text, 0, 0);
|
|
789
789
|
},
|
|
790
790
|
|
|
791
791
|
renderResult(
|
|
792
|
-
result: { content: Array<{ type: string; text?: string }>; details?:
|
|
792
|
+
result: { content: Array<{ type: string; text?: string }>; details?: TodoToolDetails },
|
|
793
793
|
options: RenderResultOptions,
|
|
794
794
|
uiTheme: Theme,
|
|
795
|
-
_args?:
|
|
795
|
+
_args?: TodoRenderArgs,
|
|
796
796
|
): Component {
|
|
797
797
|
const phases = (result.details?.phases ?? []).filter(phase => phase.tasks.length > 0);
|
|
798
798
|
const completedTasks = result.details?.completedTasks ?? [];
|
|
@@ -806,10 +806,7 @@ export const todoWriteToolRenderer = {
|
|
|
806
806
|
keys.add(task.content);
|
|
807
807
|
}
|
|
808
808
|
const allTasks = phases.flatMap(phase => phase.tasks);
|
|
809
|
-
const header = renderStatusLine(
|
|
810
|
-
{ icon: "success", title: "Todo Write", meta: [`${allTasks.length} tasks`] },
|
|
811
|
-
uiTheme,
|
|
812
|
-
);
|
|
809
|
+
const header = renderStatusLine({ icon: "success", title: "Todo", meta: [`${allTasks.length} tasks`] }, uiTheme);
|
|
813
810
|
if (allTasks.length === 0) {
|
|
814
811
|
const fallback = result.content?.find(content => content.type === "text")?.text ?? "No todos";
|
|
815
812
|
return new Text(`${header}\n${uiTheme.fg("dim", fallback)}`, 0, 0);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { hslToHex, relativeLuminance } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Derive a stable hue (0-359) from a string using djb2 hash.
|
|
3
5
|
*/
|
|
@@ -10,27 +12,50 @@ function nameToHue(name: string): number {
|
|
|
10
12
|
return hash % 360;
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
const ACCENT_SATURATION = 0.9;
|
|
16
|
+
const ACCENT_DARK_LIGHTNESS = 0.72;
|
|
17
|
+
/** Minimum contrast ratio (WCAG AA large text) between a light-theme accent and its surface. */
|
|
18
|
+
const ACCENT_MIN_CONTRAST = 3;
|
|
19
|
+
|
|
13
20
|
/**
|
|
14
|
-
*
|
|
21
|
+
* Largest relative luminance an accent may have while still meeting
|
|
22
|
+
* {@link ACCENT_MIN_CONTRAST} against a surface of the given luminance.
|
|
15
23
|
*/
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
const f = (n: number) => {
|
|
19
|
-
const k = (n + h / 30) % 12;
|
|
20
|
-
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
21
|
-
return Math.round(255 * color)
|
|
22
|
-
.toString(16)
|
|
23
|
-
.padStart(2, "0");
|
|
24
|
-
};
|
|
25
|
-
return `#${f(0)}${f(8)}${f(4)}`;
|
|
24
|
+
function accentLuminanceCap(surfaceLuminance: number): number {
|
|
25
|
+
return Math.max(0, (surfaceLuminance + 0.05) / ACCENT_MIN_CONTRAST - 0.05);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Derive a stable CSS hex accent color from a session name.
|
|
30
|
-
*
|
|
30
|
+
*
|
|
31
|
+
* On dark themes (`surfaceLuminance` undefined) the accent is vivid (high
|
|
32
|
+
* saturation, high lightness). On light themes the lightness is reduced until the
|
|
33
|
+
* accent's perceived luminance clears {@link ACCENT_MIN_CONTRAST} against the
|
|
34
|
+
* actual surface it renders on — so it stays legible on near-white *and* mid-light
|
|
35
|
+
* backgrounds — while keeping the same per-session hue.
|
|
31
36
|
*/
|
|
32
|
-
export function getSessionAccentHex(name: string): string {
|
|
33
|
-
|
|
37
|
+
export function getSessionAccentHex(name: string, surfaceLuminance?: number): string {
|
|
38
|
+
const hue = nameToHue(name);
|
|
39
|
+
if (surfaceLuminance === undefined) {
|
|
40
|
+
return hslToHex(hue, ACCENT_SATURATION, ACCENT_DARK_LIGHTNESS);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cap = accentLuminanceCap(surfaceLuminance);
|
|
44
|
+
const top = hslToHex(hue, ACCENT_SATURATION, ACCENT_DARK_LIGHTNESS);
|
|
45
|
+
if ((relativeLuminance(top) ?? 0) <= cap) return top;
|
|
46
|
+
|
|
47
|
+
// Bisect lightness: `lo` always yields luminance <= cap, `hi` always above it.
|
|
48
|
+
let lo = 0;
|
|
49
|
+
let hi = ACCENT_DARK_LIGHTNESS;
|
|
50
|
+
for (let i = 0; i < 20; i++) {
|
|
51
|
+
const mid = (lo + hi) / 2;
|
|
52
|
+
if ((relativeLuminance(hslToHex(hue, ACCENT_SATURATION, mid)) ?? 0) > cap) {
|
|
53
|
+
hi = mid;
|
|
54
|
+
} else {
|
|
55
|
+
lo = mid;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return hslToHex(hue, ACCENT_SATURATION, lo);
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
/**
|
package/src/web/search/index.ts
CHANGED
|
@@ -220,7 +220,7 @@ export async function runSearchQuery(
|
|
|
220
220
|
/**
|
|
221
221
|
* Web search tool implementation.
|
|
222
222
|
*
|
|
223
|
-
* Supports
|
|
223
|
+
* Supports the configured web-search provider chain with automatic fallback.
|
|
224
224
|
*/
|
|
225
225
|
export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails> {
|
|
226
226
|
readonly name = "web_search";
|
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
// fetch/parse/format helpers) and only one — at most — is needed per session,
|
|
6
6
|
// so eager construction was wasted work at startup.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
8
|
+
// Provider modules are loaded lazily; display metadata lives in types.ts so UI
|
|
9
|
+
// listings can share it without importing provider implementations.
|
|
10
10
|
|
|
11
11
|
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
12
12
|
import type { SearchProvider } from "./providers/base";
|
|
13
|
-
import type
|
|
13
|
+
import { SEARCH_PROVIDER_LABELS, SEARCH_PROVIDER_ORDER, type SearchProviderId } from "./types";
|
|
14
14
|
|
|
15
15
|
export type { SearchParams } from "./providers/base";
|
|
16
16
|
export { SearchProvider } from "./providers/base";
|
|
17
|
+
export { SEARCH_PROVIDER_ORDER } from "./types";
|
|
17
18
|
|
|
18
19
|
interface ProviderMeta {
|
|
19
20
|
id: SearchProviderId;
|
|
@@ -25,72 +26,72 @@ interface ProviderMeta {
|
|
|
25
26
|
const PROVIDER_META: Record<SearchProviderId, ProviderMeta> = {
|
|
26
27
|
exa: {
|
|
27
28
|
id: "exa",
|
|
28
|
-
label:
|
|
29
|
+
label: SEARCH_PROVIDER_LABELS.exa,
|
|
29
30
|
load: async () => new (await import("./providers/exa")).ExaProvider(),
|
|
30
31
|
},
|
|
31
32
|
brave: {
|
|
32
33
|
id: "brave",
|
|
33
|
-
label:
|
|
34
|
+
label: SEARCH_PROVIDER_LABELS.brave,
|
|
34
35
|
load: async () => new (await import("./providers/brave")).BraveProvider(),
|
|
35
36
|
},
|
|
36
37
|
jina: {
|
|
37
38
|
id: "jina",
|
|
38
|
-
label:
|
|
39
|
+
label: SEARCH_PROVIDER_LABELS.jina,
|
|
39
40
|
load: async () => new (await import("./providers/jina")).JinaProvider(),
|
|
40
41
|
},
|
|
41
42
|
perplexity: {
|
|
42
43
|
id: "perplexity",
|
|
43
|
-
label:
|
|
44
|
+
label: SEARCH_PROVIDER_LABELS.perplexity,
|
|
44
45
|
load: async () => new (await import("./providers/perplexity")).PerplexityProvider(),
|
|
45
46
|
},
|
|
46
47
|
kimi: {
|
|
47
48
|
id: "kimi",
|
|
48
|
-
label:
|
|
49
|
+
label: SEARCH_PROVIDER_LABELS.kimi,
|
|
49
50
|
load: async () => new (await import("./providers/kimi")).KimiProvider(),
|
|
50
51
|
},
|
|
51
52
|
zai: {
|
|
52
53
|
id: "zai",
|
|
53
|
-
label:
|
|
54
|
+
label: SEARCH_PROVIDER_LABELS.zai,
|
|
54
55
|
load: async () => new (await import("./providers/zai")).ZaiProvider(),
|
|
55
56
|
},
|
|
56
57
|
anthropic: {
|
|
57
58
|
id: "anthropic",
|
|
58
|
-
label:
|
|
59
|
+
label: SEARCH_PROVIDER_LABELS.anthropic,
|
|
59
60
|
load: async () => new (await import("./providers/anthropic")).AnthropicProvider(),
|
|
60
61
|
},
|
|
61
62
|
gemini: {
|
|
62
63
|
id: "gemini",
|
|
63
|
-
label:
|
|
64
|
+
label: SEARCH_PROVIDER_LABELS.gemini,
|
|
64
65
|
load: async () => new (await import("./providers/gemini")).GeminiProvider(),
|
|
65
66
|
},
|
|
66
67
|
codex: {
|
|
67
68
|
id: "codex",
|
|
68
|
-
label:
|
|
69
|
+
label: SEARCH_PROVIDER_LABELS.codex,
|
|
69
70
|
load: async () => new (await import("./providers/codex")).CodexProvider(),
|
|
70
71
|
},
|
|
71
72
|
tavily: {
|
|
72
73
|
id: "tavily",
|
|
73
|
-
label:
|
|
74
|
+
label: SEARCH_PROVIDER_LABELS.tavily,
|
|
74
75
|
load: async () => new (await import("./providers/tavily")).TavilyProvider(),
|
|
75
76
|
},
|
|
76
77
|
parallel: {
|
|
77
78
|
id: "parallel",
|
|
78
|
-
label:
|
|
79
|
+
label: SEARCH_PROVIDER_LABELS.parallel,
|
|
79
80
|
load: async () => new (await import("./providers/parallel")).ParallelProvider(),
|
|
80
81
|
},
|
|
81
82
|
kagi: {
|
|
82
83
|
id: "kagi",
|
|
83
|
-
label:
|
|
84
|
+
label: SEARCH_PROVIDER_LABELS.kagi,
|
|
84
85
|
load: async () => new (await import("./providers/kagi")).KagiProvider(),
|
|
85
86
|
},
|
|
86
87
|
synthetic: {
|
|
87
88
|
id: "synthetic",
|
|
88
|
-
label:
|
|
89
|
+
label: SEARCH_PROVIDER_LABELS.synthetic,
|
|
89
90
|
load: async () => new (await import("./providers/synthetic")).SyntheticProvider(),
|
|
90
91
|
},
|
|
91
92
|
searxng: {
|
|
92
93
|
id: "searxng",
|
|
93
|
-
label:
|
|
94
|
+
label: SEARCH_PROVIDER_LABELS.searxng,
|
|
94
95
|
load: async () => new (await import("./providers/searxng")).SearXNGProvider(),
|
|
95
96
|
},
|
|
96
97
|
};
|
|
@@ -118,23 +119,6 @@ export async function getSearchProvider(id: SearchProviderId): Promise<SearchPro
|
|
|
118
119
|
return provider;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
|
|
122
|
-
"tavily",
|
|
123
|
-
"perplexity",
|
|
124
|
-
"brave",
|
|
125
|
-
"jina",
|
|
126
|
-
"kimi",
|
|
127
|
-
"anthropic",
|
|
128
|
-
"gemini",
|
|
129
|
-
"codex",
|
|
130
|
-
"zai",
|
|
131
|
-
"exa",
|
|
132
|
-
"parallel",
|
|
133
|
-
"kagi",
|
|
134
|
-
"synthetic",
|
|
135
|
-
"searxng",
|
|
136
|
-
];
|
|
137
|
-
|
|
138
122
|
/** Preferred provider set via settings (default: auto) */
|
|
139
123
|
let preferredProvId: SearchProviderId | "auto" = "auto";
|
|
140
124
|
|
package/src/web/search/types.ts
CHANGED
|
@@ -4,44 +4,85 @@
|
|
|
4
4
|
* Unified types for web search responses across supported providers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
export const SEARCH_PROVIDER_ORDER = [
|
|
8
|
+
"tavily",
|
|
9
|
+
"perplexity",
|
|
10
|
+
"brave",
|
|
11
|
+
"jina",
|
|
12
|
+
"kimi",
|
|
13
|
+
"anthropic",
|
|
14
|
+
"gemini",
|
|
15
|
+
"codex",
|
|
16
|
+
"zai",
|
|
17
|
+
"exa",
|
|
18
|
+
"parallel",
|
|
19
|
+
"kagi",
|
|
20
|
+
"synthetic",
|
|
21
|
+
"searxng",
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
7
24
|
/** Supported web search providers */
|
|
8
|
-
export type SearchProviderId =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
export type SearchProviderId = (typeof SEARCH_PROVIDER_ORDER)[number];
|
|
26
|
+
|
|
27
|
+
export const SEARCH_PROVIDER_PREFERENCES = ["auto", ...SEARCH_PROVIDER_ORDER] as const;
|
|
28
|
+
|
|
29
|
+
export const SEARCH_PROVIDER_OPTIONS = [
|
|
30
|
+
{
|
|
31
|
+
value: "auto",
|
|
32
|
+
label: "Auto",
|
|
33
|
+
description: "Automatically uses the first configured web-search provider",
|
|
34
|
+
},
|
|
35
|
+
{ value: "tavily", label: "Tavily", description: "Requires TAVILY_API_KEY" },
|
|
36
|
+
{ value: "perplexity", label: "Perplexity", description: "Requires PERPLEXITY_COOKIES or PERPLEXITY_API_KEY" },
|
|
37
|
+
{ value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
|
|
38
|
+
{ value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
|
|
39
|
+
{ value: "kimi", label: "Kimi", description: "Requires MOONSHOT_SEARCH_API_KEY or MOONSHOT_API_KEY" },
|
|
40
|
+
{
|
|
41
|
+
value: "anthropic",
|
|
42
|
+
label: "Anthropic",
|
|
43
|
+
description: "Claude's native web_search tool (uses Anthropic OAuth or ANTHROPIC_API_KEY)",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
value: "gemini",
|
|
47
|
+
label: "Gemini",
|
|
48
|
+
description: "Google Search grounding via Gemini (uses google-gemini-cli or google-antigravity OAuth)",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
value: "codex",
|
|
52
|
+
label: "OpenAI",
|
|
53
|
+
description: "OpenAI's native web_search (uses ChatGPT OAuth via /login openai-codex)",
|
|
54
|
+
},
|
|
55
|
+
{ value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" },
|
|
56
|
+
{ value: "exa", label: "Exa", description: "Requires EXA_API_KEY" },
|
|
57
|
+
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
58
|
+
{ value: "kagi", label: "Kagi", description: "Requires KAGI_API_KEY and Kagi Search API beta access" },
|
|
59
|
+
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
60
|
+
{ value: "searxng", label: "SearXNG", description: "Requires SEARXNG_ENDPOINT or searxng.endpoint" },
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
export const SEARCH_PROVIDER_LABELS: Record<SearchProviderId, string> = {
|
|
64
|
+
tavily: "Tavily",
|
|
65
|
+
perplexity: "Perplexity",
|
|
66
|
+
brave: "Brave",
|
|
67
|
+
jina: "Jina",
|
|
68
|
+
kimi: "Kimi",
|
|
69
|
+
anthropic: "Anthropic",
|
|
70
|
+
gemini: "Gemini",
|
|
71
|
+
codex: "OpenAI",
|
|
72
|
+
zai: "Z.AI",
|
|
73
|
+
exa: "Exa",
|
|
74
|
+
parallel: "Parallel",
|
|
75
|
+
kagi: "Kagi",
|
|
76
|
+
synthetic: "Synthetic",
|
|
77
|
+
searxng: "SearXNG",
|
|
78
|
+
};
|
|
23
79
|
|
|
24
80
|
export function isSearchProviderId(value: string): value is SearchProviderId {
|
|
25
|
-
return
|
|
26
|
-
"exa",
|
|
27
|
-
"brave",
|
|
28
|
-
"jina",
|
|
29
|
-
"kimi",
|
|
30
|
-
"zai",
|
|
31
|
-
"anthropic",
|
|
32
|
-
"perplexity",
|
|
33
|
-
"gemini",
|
|
34
|
-
"codex",
|
|
35
|
-
"tavily",
|
|
36
|
-
"parallel",
|
|
37
|
-
"kagi",
|
|
38
|
-
"synthetic",
|
|
39
|
-
"searxng",
|
|
40
|
-
].includes(value);
|
|
81
|
+
return SEARCH_PROVIDER_ORDER.includes(value as SearchProviderId);
|
|
41
82
|
}
|
|
42
83
|
|
|
43
84
|
export function isSearchProviderPreference(value: string): value is SearchProviderId | "auto" {
|
|
44
|
-
return value
|
|
85
|
+
return SEARCH_PROVIDER_PREFERENCES.includes(value as SearchProviderId | "auto");
|
|
45
86
|
}
|
|
46
87
|
|
|
47
88
|
/** Source returned by search (all providers) */
|