@oh-my-pi/pi-coding-agent 12.10.1 → 12.11.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 +31 -0
- package/package.json +7 -7
- package/src/cli/web-search-cli.ts +1 -0
- package/src/commands/web-search.ts +1 -0
- package/src/commit/model-selection.ts +2 -2
- package/src/config/model-registry.ts +259 -308
- package/src/config/model-resolver.ts +5 -69
- package/src/config/settings-schema.ts +10 -0
- package/src/modes/components/model-selector.ts +24 -0
- package/src/modes/components/settings-defs.ts +11 -1
- package/src/modes/controllers/selector-controller.ts +4 -0
- package/src/modes/interactive-mode.ts +1 -0
- package/src/priority.json +28 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/plan.md +1 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/session/agent-session.ts +41 -8
- package/src/session/auth-storage.ts +75 -0
- package/src/tools/browser.ts +30 -2
- package/src/utils/title-generator.ts +3 -2
- package/src/web/search/index.ts +4 -4
- package/src/web/search/provider.ts +4 -1
- package/src/web/search/providers/anthropic.ts +2 -17
- package/src/web/search/providers/synthetic.ts +136 -0
- package/src/web/search/types.ts +10 -1
|
@@ -2,39 +2,16 @@
|
|
|
2
2
|
* Model resolution, scoping, and initial selection
|
|
3
3
|
*/
|
|
4
4
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
5
|
-
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
|
5
|
+
import { type Api, DEFAULT_MODEL_PER_PROVIDER, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { isValidThinkingLevel } from "../cli/args";
|
|
8
|
+
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
8
9
|
import { fuzzyMatch } from "../utils/fuzzy";
|
|
9
10
|
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
|
10
11
|
import type { Settings } from "./settings";
|
|
11
12
|
|
|
12
13
|
/** Default model IDs for each known provider */
|
|
13
|
-
export const defaultModelPerProvider: Record<KnownProvider, string> =
|
|
14
|
-
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
|
|
15
|
-
anthropic: "claude-sonnet-4-6",
|
|
16
|
-
openai: "gpt-5.1-codex",
|
|
17
|
-
"openai-codex": "gpt-5.3-codex",
|
|
18
|
-
google: "gemini-2.5-pro",
|
|
19
|
-
"google-gemini-cli": "gemini-2.5-pro",
|
|
20
|
-
"google-antigravity": "gemini-3-pro-high",
|
|
21
|
-
"google-vertex": "gemini-3-pro-preview",
|
|
22
|
-
"github-copilot": "gpt-4o",
|
|
23
|
-
cursor: "claude-sonnet-4-6",
|
|
24
|
-
openrouter: "openai/gpt-5.1-codex",
|
|
25
|
-
"vercel-ai-gateway": "anthropic/claude-sonnet-4-6",
|
|
26
|
-
xai: "grok-4-fast-non-reasoning",
|
|
27
|
-
groq: "openai/gpt-oss-120b",
|
|
28
|
-
cerebras: "zai-glm-4.6",
|
|
29
|
-
zai: "glm-4.6",
|
|
30
|
-
mistral: "devstral-medium-latest",
|
|
31
|
-
minimax: "MiniMax-M2.5",
|
|
32
|
-
"minimax-code": "MiniMax-M2.5",
|
|
33
|
-
"minimax-code-cn": "MiniMax-M2.5",
|
|
34
|
-
opencode: "claude-sonnet-4-6",
|
|
35
|
-
"kimi-code": "kimi-k2.5",
|
|
36
|
-
synthetic: "hf:moonshotai/Kimi-K2.5",
|
|
37
|
-
};
|
|
14
|
+
export const defaultModelPerProvider: Record<KnownProvider, string> = DEFAULT_MODEL_PER_PROVIDER;
|
|
38
15
|
|
|
39
16
|
export interface ScopedModel {
|
|
40
17
|
model: Model<Api>;
|
|
@@ -42,47 +19,6 @@ export interface ScopedModel {
|
|
|
42
19
|
explicitThinkingLevel: boolean;
|
|
43
20
|
}
|
|
44
21
|
|
|
45
|
-
/** Priority chain for auto-discovering smol/fast models */
|
|
46
|
-
export const SMOL_MODEL_PRIORITY = [
|
|
47
|
-
// any spark
|
|
48
|
-
"gpt-5.3-codex-spark",
|
|
49
|
-
"gpt-5.3-spark",
|
|
50
|
-
"spark",
|
|
51
|
-
// cerebras zai
|
|
52
|
-
"cerebras/zai-glm-4.7",
|
|
53
|
-
"cerebras/zai-glm-4.6",
|
|
54
|
-
"cerebras/zai-glm",
|
|
55
|
-
// any haiku
|
|
56
|
-
"haiku-4-5",
|
|
57
|
-
"haiku-4.5",
|
|
58
|
-
"haiku",
|
|
59
|
-
// any flash
|
|
60
|
-
"flash",
|
|
61
|
-
// any mini
|
|
62
|
-
"mini",
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
/** Priority chain for auto-discovering slow/comprehensive models (reasoning, codex) */
|
|
66
|
-
export const SLOW_MODEL_PRIORITY = [
|
|
67
|
-
// any codex
|
|
68
|
-
"gpt-5.3-codex",
|
|
69
|
-
"gpt-5.3",
|
|
70
|
-
"gpt-5.2-codex",
|
|
71
|
-
"gpt-5.2",
|
|
72
|
-
"gpt-5.1-codex",
|
|
73
|
-
"gpt-5.1",
|
|
74
|
-
"codex",
|
|
75
|
-
// any opus
|
|
76
|
-
"opus-4.6",
|
|
77
|
-
"opus-4-6",
|
|
78
|
-
"opus-4.5",
|
|
79
|
-
"opus-4-5",
|
|
80
|
-
"opus-4.1",
|
|
81
|
-
"opus-4-1",
|
|
82
|
-
// whatever
|
|
83
|
-
"pro",
|
|
84
|
-
];
|
|
85
|
-
|
|
86
22
|
/**
|
|
87
23
|
* Parse a model string in "provider/modelId" format.
|
|
88
24
|
* Returns undefined if the format is invalid.
|
|
@@ -812,7 +748,7 @@ export async function findSmolModel(
|
|
|
812
748
|
}
|
|
813
749
|
|
|
814
750
|
// 2. Try priority chain
|
|
815
|
-
for (const pattern of
|
|
751
|
+
for (const pattern of MODEL_PRIO.smol) {
|
|
816
752
|
// Try exact match with provider prefix
|
|
817
753
|
const providerMatch = availableModels.find(m => `${m.provider}/${m.id}`.toLowerCase() === pattern);
|
|
818
754
|
if (providerMatch) return providerMatch;
|
|
@@ -855,7 +791,7 @@ export async function findSlowModel(
|
|
|
855
791
|
}
|
|
856
792
|
|
|
857
793
|
// 2. Try priority chain
|
|
858
|
-
for (const pattern of
|
|
794
|
+
for (const pattern of MODEL_PRIO.slow) {
|
|
859
795
|
// Try exact match first
|
|
860
796
|
const exactMatch = availableModels.find(m => m.id.toLowerCase() === pattern.toLowerCase());
|
|
861
797
|
if (exactMatch) return exactMatch;
|
|
@@ -244,6 +244,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
244
244
|
default: false,
|
|
245
245
|
ui: { tab: "input", label: "Collapse changelog", description: "Show condensed changelog after updates" },
|
|
246
246
|
},
|
|
247
|
+
autocompleteMaxVisible: {
|
|
248
|
+
type: "number",
|
|
249
|
+
default: 5,
|
|
250
|
+
ui: {
|
|
251
|
+
tab: "input",
|
|
252
|
+
label: "Autocomplete max items",
|
|
253
|
+
description: "Max visible items in autocomplete dropdown (3-20)",
|
|
254
|
+
submenu: true,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
247
257
|
normativeRewrite: {
|
|
248
258
|
type: "boolean",
|
|
249
259
|
default: false,
|
|
@@ -223,6 +223,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
223
223
|
const providerCmp = a.provider.localeCompare(b.provider);
|
|
224
224
|
if (providerCmp !== 0) return providerCmp;
|
|
225
225
|
|
|
226
|
+
// Priority field (lower = better, e.g. Codex priority values)
|
|
227
|
+
const aPri = a.model.priority ?? Number.MAX_SAFE_INTEGER;
|
|
228
|
+
const bPri = b.model.priority ?? Number.MAX_SAFE_INTEGER;
|
|
229
|
+
if (aPri !== bPri) return aPri - bPri;
|
|
230
|
+
|
|
231
|
+
// Version number descending (higher version = better model)
|
|
232
|
+
const aVer = extractVersionNumber(a.id);
|
|
233
|
+
const bVer = extractVersionNumber(b.id);
|
|
234
|
+
if (aVer !== bVer) return bVer - aVer;
|
|
235
|
+
|
|
226
236
|
const aIsLatest = latestRe.test(a.id);
|
|
227
237
|
const bIsLatest = latestRe.test(b.id);
|
|
228
238
|
const aDate = a.id.match(dateRe)?.[1] ?? "";
|
|
@@ -596,3 +606,17 @@ export class ModelSelectorComponent extends Container {
|
|
|
596
606
|
return this.#searchInput;
|
|
597
607
|
}
|
|
598
608
|
}
|
|
609
|
+
|
|
610
|
+
/** Extract the first version number from a model ID (e.g. "gemini-2.5-pro" → 2.5, "claude-sonnet-4-6" → 4.6). */
|
|
611
|
+
function extractVersionNumber(id: string): number {
|
|
612
|
+
// Dot-separated version: "gemini-2.5-pro" → 2.5
|
|
613
|
+
const dotMatch = id.match(/(?:^|[-_])(\d+\.\d+)/);
|
|
614
|
+
if (dotMatch) return Number.parseFloat(dotMatch[1]);
|
|
615
|
+
// Dash-separated short segments: "claude-sonnet-4-6" → 4.6, "llama-3-1-8b" → 3.1
|
|
616
|
+
const dashMatch = id.match(/(?:^|[-_])(\d{1,2})-(\d{1,2})(?=-|$)/);
|
|
617
|
+
if (dashMatch) return Number.parseFloat(`${dashMatch[1]}.${dashMatch[2]}`);
|
|
618
|
+
// Single number after separator: "gpt-4o" → 4
|
|
619
|
+
const singleMatch = id.match(/(?:^|[-_])(\d+)/);
|
|
620
|
+
if (singleMatch) return Number.parseFloat(singleMatch[1]);
|
|
621
|
+
return 0;
|
|
622
|
+
}
|
|
@@ -116,6 +116,15 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
116
116
|
{ value: "5", label: "5 lines" },
|
|
117
117
|
{ value: "10", label: "10 lines" },
|
|
118
118
|
],
|
|
119
|
+
// Autocomplete max visible
|
|
120
|
+
autocompleteMaxVisible: [
|
|
121
|
+
{ value: "3", label: "3 items" },
|
|
122
|
+
{ value: "5", label: "5 items" },
|
|
123
|
+
{ value: "7", label: "7 items" },
|
|
124
|
+
{ value: "10", label: "10 items" },
|
|
125
|
+
{ value: "15", label: "15 items" },
|
|
126
|
+
{ value: "20", label: "20 items" },
|
|
127
|
+
],
|
|
119
128
|
// Ask timeout
|
|
120
129
|
"ask.timeout": [
|
|
121
130
|
{ value: "0", label: "Disabled" },
|
|
@@ -155,7 +164,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
155
164
|
{
|
|
156
165
|
value: "auto",
|
|
157
166
|
label: "Auto",
|
|
158
|
-
description: "Priority: Exa > Brave > Jina > Perplexity > Anthropic > Gemini > Codex > Z.AI",
|
|
167
|
+
description: "Priority: Exa > Brave > Jina > Perplexity > Anthropic > Gemini > Codex > Z.AI > Synthetic",
|
|
159
168
|
},
|
|
160
169
|
{ value: "exa", label: "Exa", description: "Requires EXA_API_KEY" },
|
|
161
170
|
{ value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
|
|
@@ -163,6 +172,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
163
172
|
{ value: "perplexity", label: "Perplexity", description: "Requires PERPLEXITY_API_KEY" },
|
|
164
173
|
{ value: "anthropic", label: "Anthropic", description: "Uses Anthropic web search" },
|
|
165
174
|
{ value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" },
|
|
175
|
+
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
166
176
|
],
|
|
167
177
|
"providers.image": [
|
|
168
178
|
{ value: "auto", label: "Auto", description: "Priority: OpenRouter > Gemini" },
|
|
@@ -201,6 +201,10 @@ export class SelectorController {
|
|
|
201
201
|
this.ctx.ui.setClearOnShrink(value as boolean);
|
|
202
202
|
break;
|
|
203
203
|
|
|
204
|
+
case "autocompleteMaxVisible":
|
|
205
|
+
this.ctx.editor.setAutocompleteMaxVisible(typeof value === "number" ? value : Number(value));
|
|
206
|
+
break;
|
|
207
|
+
|
|
204
208
|
// Settings with UI side effects
|
|
205
209
|
case "showImages":
|
|
206
210
|
for (const child of this.ctx.chatContainer.children) {
|
|
@@ -189,6 +189,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
189
189
|
this.todoContainer = new Container();
|
|
190
190
|
this.editor = new CustomEditor(getEditorTheme());
|
|
191
191
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
192
|
+
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
192
193
|
this.editor.onAutocompleteCancel = () => {
|
|
193
194
|
this.ui.requestRender(true);
|
|
194
195
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"smol": [
|
|
3
|
+
"cerebras/zai-glm-4.7",
|
|
4
|
+
"cerebras/zai-glm-4.6",
|
|
5
|
+
"cerebras/zai-glm",
|
|
6
|
+
"haiku-4-5",
|
|
7
|
+
"haiku-4.5",
|
|
8
|
+
"haiku",
|
|
9
|
+
"flash",
|
|
10
|
+
"mini"
|
|
11
|
+
],
|
|
12
|
+
"slow": [
|
|
13
|
+
"gpt-5.3-codex",
|
|
14
|
+
"gpt-5.3",
|
|
15
|
+
"gpt-5.2-codex",
|
|
16
|
+
"gpt-5.2",
|
|
17
|
+
"gpt-5.1-codex",
|
|
18
|
+
"gpt-5.1",
|
|
19
|
+
"codex",
|
|
20
|
+
"opus-4.6",
|
|
21
|
+
"opus-4-6",
|
|
22
|
+
"opus-4.5",
|
|
23
|
+
"opus-4-5",
|
|
24
|
+
"opus-4.1",
|
|
25
|
+
"opus-4-1",
|
|
26
|
+
"pro"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: explore
|
|
3
3
|
description: Fast read-only codebase scout returning compressed context for handoff
|
|
4
4
|
tools: read, grep, find, bash
|
|
5
|
-
model: pi/smol
|
|
5
|
+
model: pi/smol
|
|
6
6
|
thinking-level: minimal
|
|
7
7
|
output:
|
|
8
8
|
properties:
|
|
@@ -3,7 +3,7 @@ name: plan
|
|
|
3
3
|
description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, or tasks completable in <5 tool calls.
|
|
4
4
|
tools: read, grep, find, bash
|
|
5
5
|
spawns: explore
|
|
6
|
-
model: pi/plan, pi/slow
|
|
6
|
+
model: pi/plan, pi/slow
|
|
7
7
|
thinking-level: high
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: reviewer
|
|
|
3
3
|
description: "Code review specialist for quality/security analysis"
|
|
4
4
|
tools: read, grep, find, bash, report_finding
|
|
5
5
|
spawns: explore, task
|
|
6
|
-
model: pi/slow
|
|
6
|
+
model: pi/slow
|
|
7
7
|
thinking-level: high
|
|
8
8
|
output:
|
|
9
9
|
properties:
|
|
@@ -16,7 +16,15 @@
|
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
|
|
19
|
-
import
|
|
19
|
+
import {
|
|
20
|
+
type Agent,
|
|
21
|
+
AgentBusyError,
|
|
22
|
+
type AgentEvent,
|
|
23
|
+
type AgentMessage,
|
|
24
|
+
type AgentState,
|
|
25
|
+
type AgentTool,
|
|
26
|
+
type ThinkingLevel,
|
|
27
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
20
28
|
import type {
|
|
21
29
|
AssistantMessage,
|
|
22
30
|
ImageContent,
|
|
@@ -304,6 +312,7 @@ export class AgentSession {
|
|
|
304
312
|
|
|
305
313
|
// Handoff state
|
|
306
314
|
#handoffAbortController: AbortController | undefined = undefined;
|
|
315
|
+
#skipPostTurnMaintenanceAssistantTimestamp: number | undefined = undefined;
|
|
307
316
|
|
|
308
317
|
// Retry state
|
|
309
318
|
#retryAbortController: AbortController | undefined = undefined;
|
|
@@ -579,6 +588,9 @@ export class AgentSession {
|
|
|
579
588
|
this.#lastAssistantMessage = event.message;
|
|
580
589
|
const assistantMsg = event.message as AssistantMessage;
|
|
581
590
|
this.#queueDeferredTtsrInjectionIfNeeded(assistantMsg);
|
|
591
|
+
if (this.#handoffAbortController) {
|
|
592
|
+
this.#skipPostTurnMaintenanceAssistantTimestamp = assistantMsg.timestamp;
|
|
593
|
+
}
|
|
582
594
|
if (
|
|
583
595
|
assistantMsg.stopReason !== "error" &&
|
|
584
596
|
assistantMsg.stopReason !== "aborted" &&
|
|
@@ -637,6 +649,11 @@ export class AgentSession {
|
|
|
637
649
|
const msg = this.#lastAssistantMessage;
|
|
638
650
|
this.#lastAssistantMessage = undefined;
|
|
639
651
|
|
|
652
|
+
if (this.#skipPostTurnMaintenanceAssistantTimestamp === msg.timestamp) {
|
|
653
|
+
this.#skipPostTurnMaintenanceAssistantTimestamp = undefined;
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
640
657
|
// Check for retryable errors first (overloaded, rate limit, server errors)
|
|
641
658
|
if (this.#isRetryableError(msg)) {
|
|
642
659
|
const didRetry = await this.#handleRetryableError(msg);
|
|
@@ -1607,9 +1624,7 @@ export class AgentSession {
|
|
|
1607
1624
|
// If streaming, queue via steer() or followUp() based on option
|
|
1608
1625
|
if (this.isStreaming) {
|
|
1609
1626
|
if (!options?.streamingBehavior) {
|
|
1610
|
-
throw new
|
|
1611
|
-
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.",
|
|
1612
|
-
);
|
|
1627
|
+
throw new AgentBusyError();
|
|
1613
1628
|
}
|
|
1614
1629
|
if (options.streamingBehavior === "followUp") {
|
|
1615
1630
|
await this.#queueFollowUp(expandedText, options?.images);
|
|
@@ -1650,9 +1665,7 @@ export class AgentSession {
|
|
|
1650
1665
|
|
|
1651
1666
|
if (this.isStreaming) {
|
|
1652
1667
|
if (!options?.streamingBehavior) {
|
|
1653
|
-
throw new
|
|
1654
|
-
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.",
|
|
1655
|
-
);
|
|
1668
|
+
throw new AgentBusyError();
|
|
1656
1669
|
}
|
|
1657
1670
|
await this.sendCustomMessage(message, { deliverAs: options.streamingBehavior });
|
|
1658
1671
|
return;
|
|
@@ -1777,7 +1790,7 @@ export class AgentSession {
|
|
|
1777
1790
|
}
|
|
1778
1791
|
|
|
1779
1792
|
const agentPromptOptions = options?.toolChoice ? { toolChoice: options.toolChoice } : undefined;
|
|
1780
|
-
await this
|
|
1793
|
+
await this.#promptAgentWithIdleRetry(messages, agentPromptOptions);
|
|
1781
1794
|
await this.#waitForRetry();
|
|
1782
1795
|
} finally {
|
|
1783
1796
|
this.#promptInFlight = false;
|
|
@@ -2800,6 +2813,8 @@ export class AgentSession {
|
|
|
2800
2813
|
throw new Error("Nothing to hand off (no messages yet)");
|
|
2801
2814
|
}
|
|
2802
2815
|
|
|
2816
|
+
this.#skipPostTurnMaintenanceAssistantTimestamp = undefined;
|
|
2817
|
+
|
|
2803
2818
|
this.#handoffAbortController = new AbortController();
|
|
2804
2819
|
|
|
2805
2820
|
// Build the handoff prompt
|
|
@@ -3693,6 +3708,24 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3693
3708
|
}
|
|
3694
3709
|
}
|
|
3695
3710
|
|
|
3711
|
+
async #promptAgentWithIdleRetry(messages: AgentMessage[], options?: { toolChoice?: ToolChoice }): Promise<void> {
|
|
3712
|
+
const deadline = Date.now() + 30_000;
|
|
3713
|
+
for (;;) {
|
|
3714
|
+
try {
|
|
3715
|
+
await this.agent.prompt(messages, options);
|
|
3716
|
+
return;
|
|
3717
|
+
} catch (err) {
|
|
3718
|
+
if (!(err instanceof AgentBusyError)) {
|
|
3719
|
+
throw err;
|
|
3720
|
+
}
|
|
3721
|
+
if (Date.now() >= deadline) {
|
|
3722
|
+
throw new Error("Timed out waiting for prior agent run to finish before prompting.");
|
|
3723
|
+
}
|
|
3724
|
+
await this.agent.waitForIdle();
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3696
3729
|
/** Whether auto-retry is currently in progress */
|
|
3697
3730
|
get isRetrying(): boolean {
|
|
3698
3731
|
return this.#retryPromise !== undefined;
|
|
@@ -14,16 +14,28 @@ import {
|
|
|
14
14
|
loginAnthropic,
|
|
15
15
|
loginAntigravity,
|
|
16
16
|
loginCerebras,
|
|
17
|
+
loginCloudflareAiGateway,
|
|
17
18
|
loginCursor,
|
|
18
19
|
loginGeminiCli,
|
|
19
20
|
loginGitHubCopilot,
|
|
21
|
+
loginHuggingface,
|
|
20
22
|
loginKimi,
|
|
23
|
+
loginLiteLLM,
|
|
21
24
|
loginMiniMaxCode,
|
|
22
25
|
loginMiniMaxCodeCn,
|
|
26
|
+
loginMoonshot,
|
|
27
|
+
loginNvidia,
|
|
28
|
+
loginOllama,
|
|
23
29
|
loginOpenAICodex,
|
|
24
30
|
loginOpenCode,
|
|
25
31
|
loginPerplexity,
|
|
32
|
+
loginQianfan,
|
|
33
|
+
loginQwenPortal,
|
|
26
34
|
loginSynthetic,
|
|
35
|
+
loginTogether,
|
|
36
|
+
loginVenice,
|
|
37
|
+
loginVllm,
|
|
38
|
+
loginXiaomi,
|
|
27
39
|
loginZai,
|
|
28
40
|
type OAuthController,
|
|
29
41
|
type OAuthCredentials,
|
|
@@ -693,11 +705,24 @@ export class AuthStorage {
|
|
|
693
705
|
case "perplexity":
|
|
694
706
|
credentials = await loginPerplexity(ctrl);
|
|
695
707
|
break;
|
|
708
|
+
case "huggingface": {
|
|
709
|
+
const apiKey = await loginHuggingface(ctrl);
|
|
710
|
+
await saveApiKeyCredential(apiKey);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
696
713
|
case "opencode": {
|
|
697
714
|
const apiKey = await loginOpenCode(ctrl);
|
|
698
715
|
await saveApiKeyCredential(apiKey);
|
|
699
716
|
return;
|
|
700
717
|
}
|
|
718
|
+
case "ollama": {
|
|
719
|
+
const apiKey = await loginOllama(ctrl);
|
|
720
|
+
if (!apiKey) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
await saveApiKeyCredential(apiKey);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
701
726
|
case "cerebras": {
|
|
702
727
|
const apiKey = await loginCerebras(ctrl);
|
|
703
728
|
await saveApiKeyCredential(apiKey);
|
|
@@ -708,6 +733,11 @@ export class AuthStorage {
|
|
|
708
733
|
await saveApiKeyCredential(apiKey);
|
|
709
734
|
return;
|
|
710
735
|
}
|
|
736
|
+
case "qianfan": {
|
|
737
|
+
const apiKey = await loginQianfan(ctrl);
|
|
738
|
+
await saveApiKeyCredential(apiKey);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
711
741
|
case "minimax-code": {
|
|
712
742
|
const apiKey = await loginMiniMaxCode(ctrl);
|
|
713
743
|
await saveApiKeyCredential(apiKey);
|
|
@@ -723,6 +753,51 @@ export class AuthStorage {
|
|
|
723
753
|
await saveApiKeyCredential(apiKey);
|
|
724
754
|
return;
|
|
725
755
|
}
|
|
756
|
+
case "venice": {
|
|
757
|
+
const apiKey = await loginVenice(ctrl);
|
|
758
|
+
await saveApiKeyCredential(apiKey);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
case "litellm": {
|
|
762
|
+
const apiKey = await loginLiteLLM(ctrl);
|
|
763
|
+
await saveApiKeyCredential(apiKey);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
case "moonshot": {
|
|
767
|
+
const apiKey = await loginMoonshot(ctrl);
|
|
768
|
+
await saveApiKeyCredential(apiKey);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
case "together": {
|
|
772
|
+
const apiKey = await loginTogether(ctrl);
|
|
773
|
+
await saveApiKeyCredential(apiKey);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
case "cloudflare-ai-gateway": {
|
|
777
|
+
const apiKey = await loginCloudflareAiGateway(ctrl);
|
|
778
|
+
await saveApiKeyCredential(apiKey);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
case "vllm": {
|
|
782
|
+
const apiKey = await loginVllm(ctrl);
|
|
783
|
+
await saveApiKeyCredential(apiKey);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
case "qwen-portal": {
|
|
787
|
+
const apiKey = await loginQwenPortal(ctrl);
|
|
788
|
+
await saveApiKeyCredential(apiKey);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
case "nvidia": {
|
|
792
|
+
const apiKey = await loginNvidia(ctrl);
|
|
793
|
+
await saveApiKeyCredential(apiKey);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
case "xiaomi": {
|
|
797
|
+
const apiKey = await loginXiaomi(ctrl);
|
|
798
|
+
await saveApiKeyCredential(apiKey);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
726
801
|
default: {
|
|
727
802
|
const customProvider = getOAuthProvider(provider);
|
|
728
803
|
if (!customProvider) {
|
package/src/tools/browser.ts
CHANGED
|
@@ -4,10 +4,18 @@ import { Readability } from "@mozilla/readability";
|
|
|
4
4
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
5
5
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import { logger, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { getPuppeteerDir } from "@oh-my-pi/pi-utils/dirs";
|
|
7
8
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
9
|
import { type HTMLElement, parseHTML } from "linkedom";
|
|
9
|
-
import type {
|
|
10
|
-
|
|
10
|
+
import type {
|
|
11
|
+
Browser,
|
|
12
|
+
CDPSession,
|
|
13
|
+
ElementHandle,
|
|
14
|
+
KeyInput,
|
|
15
|
+
Page,
|
|
16
|
+
default as Puppeteer,
|
|
17
|
+
SerializedAXNode,
|
|
18
|
+
} from "puppeteer";
|
|
11
19
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
12
20
|
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
|
13
21
|
import type { ToolSession } from "../sdk";
|
|
@@ -31,6 +39,25 @@ import stealthWorkerScript from "./puppeteer/13_stealth_worker.txt" with { type:
|
|
|
31
39
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
32
40
|
import { toolResult } from "./tool-result";
|
|
33
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Lazy-import puppeteer from a safe CWD so cosmiconfig doesn't choke
|
|
44
|
+
* on malformed package.json files in the user's project tree.
|
|
45
|
+
*/
|
|
46
|
+
let puppeteerModule: typeof Puppeteer | undefined;
|
|
47
|
+
async function loadPuppeteer(): Promise<typeof Puppeteer> {
|
|
48
|
+
if (puppeteerModule) return puppeteerModule;
|
|
49
|
+
const prev = process.cwd();
|
|
50
|
+
const safeDir = getPuppeteerDir();
|
|
51
|
+
await Bun.write(path.join(safeDir, "package.json"), "{}");
|
|
52
|
+
try {
|
|
53
|
+
process.chdir(safeDir);
|
|
54
|
+
puppeteerModule = (await import("puppeteer")).default;
|
|
55
|
+
return puppeteerModule;
|
|
56
|
+
} finally {
|
|
57
|
+
process.chdir(prev);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
34
61
|
const DEFAULT_TIMEOUT_SECONDS = 30;
|
|
35
62
|
const MAX_TIMEOUT_SECONDS = 120;
|
|
36
63
|
const DEFAULT_VIEWPORT = { width: 1365, height: 768, deviceScaleFactor: 1.25 };
|
|
@@ -488,6 +515,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
488
515
|
await this.#closeBrowser();
|
|
489
516
|
this.#currentHeadless = this.session.settings.get("browser.headless");
|
|
490
517
|
const initialViewport = params?.viewport ?? DEFAULT_VIEWPORT;
|
|
518
|
+
const puppeteer = await loadPuppeteer();
|
|
491
519
|
this.#browser = await puppeteer.launch({
|
|
492
520
|
headless: this.#currentHeadless,
|
|
493
521
|
defaultViewport: this.#currentHeadless ? initialViewport : null,
|
|
@@ -5,8 +5,9 @@ import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
|
5
5
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import type { ModelRegistry } from "../config/model-registry";
|
|
8
|
-
import { parseModelString
|
|
8
|
+
import { parseModelString } from "../config/model-resolver";
|
|
9
9
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
10
|
+
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
10
11
|
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
11
12
|
|
|
12
13
|
const TITLE_SYSTEM_PROMPT = renderPromptTemplate(titleSystemPrompt);
|
|
@@ -34,7 +35,7 @@ function getTitleModelCandidates(registry: ModelRegistry, savedSmolModel?: strin
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
for (const pattern of
|
|
38
|
+
for (const pattern of MODEL_PRIO.smol) {
|
|
38
39
|
const needle = pattern.toLowerCase();
|
|
39
40
|
const exactMatch = availableModels.find(model => model.id.toLowerCase() === needle);
|
|
40
41
|
addCandidate(exactMatch);
|
package/src/web/search/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified Web Search Tool
|
|
3
3
|
*
|
|
4
|
-
* Single tool supporting Anthropic, Perplexity, Exa, Brave, Jina, Gemini, Codex,
|
|
4
|
+
* Single tool supporting Anthropic, Perplexity, Exa, Brave, Jina, Gemini, Codex, Z.AI, and Synthetic
|
|
5
5
|
* providers with provider-specific parameters exposed conditionally.
|
|
6
6
|
*
|
|
7
7
|
* When EXA_API_KEY is available, additional specialized tools are exposed:
|
|
@@ -33,7 +33,7 @@ import { SearchProviderError } from "./types";
|
|
|
33
33
|
export const webSearchSchema = Type.Object({
|
|
34
34
|
query: Type.String({ description: "Search query" }),
|
|
35
35
|
provider: Type.Optional(
|
|
36
|
-
StringEnum(["auto", "exa", "brave", "jina", "zai", "anthropic", "perplexity", "gemini", "codex"], {
|
|
36
|
+
StringEnum(["auto", "exa", "brave", "jina", "zai", "anthropic", "perplexity", "gemini", "codex", "synthetic"], {
|
|
37
37
|
description: "Search provider (default: auto)",
|
|
38
38
|
}),
|
|
39
39
|
),
|
|
@@ -47,7 +47,7 @@ export const webSearchSchema = Type.Object({
|
|
|
47
47
|
|
|
48
48
|
export type SearchParams = {
|
|
49
49
|
query: string;
|
|
50
|
-
provider?: "auto" | "exa" | "brave" | "jina" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex";
|
|
50
|
+
provider?: "auto" | "exa" | "brave" | "jina" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex" | "synthetic";
|
|
51
51
|
recency?: "day" | "week" | "month" | "year";
|
|
52
52
|
limit?: number;
|
|
53
53
|
/** Maximum output tokens. Defaults to 4096. */
|
|
@@ -236,7 +236,7 @@ export async function runSearchQuery(
|
|
|
236
236
|
/**
|
|
237
237
|
* Web search tool implementation.
|
|
238
238
|
*
|
|
239
|
-
* Supports Anthropic, Perplexity, Exa, Brave, Jina, Gemini, Codex,
|
|
239
|
+
* Supports Anthropic, Perplexity, Exa, Brave, Jina, Gemini, Codex, Z.AI, and Synthetic providers with automatic fallback.
|
|
240
240
|
* Session is accepted for interface consistency but not used.
|
|
241
241
|
*/
|
|
242
242
|
export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails> {
|
|
@@ -6,6 +6,7 @@ import { ExaProvider } from "./providers/exa";
|
|
|
6
6
|
import { GeminiProvider } from "./providers/gemini";
|
|
7
7
|
import { JinaProvider } from "./providers/jina";
|
|
8
8
|
import { PerplexityProvider } from "./providers/perplexity";
|
|
9
|
+
import { SyntheticProvider } from "./providers/synthetic";
|
|
9
10
|
import { ZaiProvider } from "./providers/zai";
|
|
10
11
|
import type { SearchProviderId } from "./types";
|
|
11
12
|
|
|
@@ -21,6 +22,7 @@ const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
|
|
|
21
22
|
anthropic: new AnthropicProvider(),
|
|
22
23
|
gemini: new GeminiProvider(),
|
|
23
24
|
codex: new CodexProvider(),
|
|
25
|
+
synthetic: new SyntheticProvider(),
|
|
24
26
|
} as const;
|
|
25
27
|
|
|
26
28
|
const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
|
|
@@ -32,6 +34,7 @@ const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
|
|
|
32
34
|
"gemini",
|
|
33
35
|
"codex",
|
|
34
36
|
"zai",
|
|
37
|
+
"synthetic",
|
|
35
38
|
];
|
|
36
39
|
|
|
37
40
|
export function getSearchProvider(provider: SearchProviderId): SearchProvider {
|
|
@@ -46,7 +49,7 @@ export function setPreferredSearchProvider(provider: SearchProviderId | "auto"):
|
|
|
46
49
|
preferredProvId = provider;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
/** Determine which providers are configured (priority: Exa → Brave → Jina → Perplexity → Anthropic → Gemini → Codex → Z.AI) */
|
|
52
|
+
/** Determine which providers are configured (priority: Exa → Brave → Jina → Perplexity → Anthropic → Gemini → Codex → Z.AI → Synthetic) */
|
|
50
53
|
export async function resolveProviderChain(
|
|
51
54
|
preferredProvider: SearchProviderId | "auto" = preferredProvId,
|
|
52
55
|
): Promise<SearchProvider[]> {
|