@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.2
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 +142 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- 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/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -1
- 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/status-line.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 +17 -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 +16 -5
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +21 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/workflow.d.ts +3 -3
- 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/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +8 -3
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +17 -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/eval.d.ts +8 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- 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/path-utils.d.ts +8 -0
- 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 +6 -2
- 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/tools/yield.d.ts +8 -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/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +54 -21
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -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 +36 -11
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +29 -24
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +106 -43
- 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 +47 -53
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +110 -31
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +22 -6
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +100 -72
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +14 -7
- 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 +164 -109
- 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/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- 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/status-line.ts +19 -4
- package/src/modes/components/tips.txt +2 -1
- 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 +2 -3
- 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 +67 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +170 -126
- 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 +274 -112
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +21 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/modes/workflow.ts +10 -10
- 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/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +37 -46
- package/src/session/agent-session.ts +119 -18
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +109 -28
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +76 -38
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +211 -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 +57 -6
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval.ts +13 -2
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +51 -30
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/github-cache.ts +25 -0
- 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 +10 -3
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +66 -39
- package/src/tools/read.ts +48 -28
- 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 +118 -81
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +83 -64
- package/src/tools/yield.ts +10 -1
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +11 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/codex.ts +37 -8
- 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
|
@@ -218,4 +218,33 @@ export const miscFixtures: Record<string, GalleryFixture> = {
|
|
|
218
218
|
},
|
|
219
219
|
},
|
|
220
220
|
},
|
|
221
|
+
|
|
222
|
+
// Built-in tool with no dedicated renderer — exercises the generic fallback
|
|
223
|
+
// (`#formatToolExecution`) path so its padded, state-tinted block is QA'd.
|
|
224
|
+
report_tool_issue: {
|
|
225
|
+
label: "Report Tool Issue",
|
|
226
|
+
streamingArgs: { tool: "lsp" },
|
|
227
|
+
args: {
|
|
228
|
+
tool: "lsp",
|
|
229
|
+
report: "Rename returned no edit for an exported symbol that has 12 references",
|
|
230
|
+
},
|
|
231
|
+
result: { content: [{ type: "text", text: "Noted, thanks!" }] },
|
|
232
|
+
errorResult: {
|
|
233
|
+
content: [{ type: "text", text: "Could not record the report: issue tracker unreachable" }],
|
|
234
|
+
isError: true,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// Stand-in for a custom/extension tool that ships no renderer — same generic
|
|
239
|
+
// fallback path most MCP/extension tools take.
|
|
240
|
+
custom: {
|
|
241
|
+
label: "Custom Tool",
|
|
242
|
+
streamingArgs: { query: "weather" },
|
|
243
|
+
args: { query: "weather in Tokyo", units: "metric" },
|
|
244
|
+
result: { content: [{ type: "text", text: "Tokyo: 22°C, partly cloudy, humidity 64%." }] },
|
|
245
|
+
errorResult: {
|
|
246
|
+
content: [{ type: "text", text: "Upstream provider returned 503 Service Unavailable" }],
|
|
247
|
+
isError: true,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
221
250
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { getProjectDir, normalizePathForComparison, setProjectDir } from "@oh-my-pi/pi-utils";
|
|
5
|
+
import type { Args } from "./args";
|
|
6
|
+
|
|
7
|
+
async function maybeAutoChdir(parsed: Args): Promise<void> {
|
|
8
|
+
if (parsed.allowHome || parsed.cwd) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
if (!home) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const normalizePath = normalizePathForComparison;
|
|
18
|
+
|
|
19
|
+
const cwd = normalizePath(getProjectDir());
|
|
20
|
+
const normalizedHome = normalizePath(home);
|
|
21
|
+
if (cwd !== normalizedHome) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const isDirectory = async (p: string) => {
|
|
26
|
+
try {
|
|
27
|
+
const s = await fs.stat(p);
|
|
28
|
+
return s.isDirectory();
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const candidates = [path.join(home, "tmp"), "/tmp", "/var/tmp"];
|
|
35
|
+
for (const candidate of candidates) {
|
|
36
|
+
try {
|
|
37
|
+
if (!(await isDirectory(candidate))) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
setProjectDir(candidate);
|
|
41
|
+
return;
|
|
42
|
+
} catch {
|
|
43
|
+
// Try next candidate.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const fallback = os.tmpdir();
|
|
49
|
+
if (fallback && normalizePath(fallback) !== cwd && (await isDirectory(fallback))) {
|
|
50
|
+
setProjectDir(fallback);
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// Ignore fallback errors.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function applyStartupCwd(parsed: Args): Promise<void> {
|
|
58
|
+
if (parsed.cwd) {
|
|
59
|
+
setProjectDir(parsed.cwd);
|
|
60
|
+
// setProjectDir resolves the (possibly relative) target against the launch
|
|
61
|
+
// cwd and chdirs into it. Re-sync parsed.cwd to the resolved absolute path
|
|
62
|
+
// so downstream consumers (buildSessionOptions, settings/discovery, session
|
|
63
|
+
// persistence) don't re-resolve a relative string against the new cwd.
|
|
64
|
+
parsed.cwd = getProjectDir();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await maybeAutoChdir(parsed);
|
|
68
|
+
}
|
package/src/commands/launch.ts
CHANGED
|
@@ -49,6 +49,9 @@ export default class Index extends Command {
|
|
|
49
49
|
"allow-home": Flags.boolean({
|
|
50
50
|
description: "Allow starting in ~ without auto-switching to a temp dir",
|
|
51
51
|
}),
|
|
52
|
+
cwd: Flags.string({
|
|
53
|
+
description: "Directory to start in (overrides the launch cwd)",
|
|
54
|
+
}),
|
|
52
55
|
mode: Flags.string({
|
|
53
56
|
description: "Output mode: text (default), json, rpc, or rpc-ui",
|
|
54
57
|
options: ["text", "json", "rpc", "acp", "rpc-ui"],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import analysisSystemPrompt from "../../commit/prompts/analysis-system.md" with { type: "text" };
|
|
@@ -14,7 +14,7 @@ const ConventionalAnalysisTool = createConventionalAnalysisTool(
|
|
|
14
14
|
|
|
15
15
|
export interface ConventionalAnalysisInput {
|
|
16
16
|
model: Model<Api>;
|
|
17
|
-
apiKey:
|
|
17
|
+
apiKey: ApiKey;
|
|
18
18
|
thinkingLevel?: ThinkingLevel;
|
|
19
19
|
contextFiles?: Array<{ path: string; content: string }>;
|
|
20
20
|
userContext?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import * as z from "zod/v4";
|
|
@@ -19,7 +19,7 @@ const SummaryTool = {
|
|
|
19
19
|
|
|
20
20
|
export interface SummaryInput {
|
|
21
21
|
model: Model<Api>;
|
|
22
|
-
apiKey:
|
|
22
|
+
apiKey: ApiKey;
|
|
23
23
|
thinkingLevel?: ThinkingLevel;
|
|
24
24
|
commitType: string;
|
|
25
25
|
scope: string | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import * as z from "zod/v4";
|
|
@@ -25,7 +25,7 @@ export const changelogTool = {
|
|
|
25
25
|
|
|
26
26
|
export interface ChangelogPromptInput {
|
|
27
27
|
model: Model<Api>;
|
|
28
|
-
apiKey:
|
|
28
|
+
apiKey: ApiKey;
|
|
29
29
|
thinkingLevel?: ThinkingLevel;
|
|
30
30
|
changelogPath: string;
|
|
31
31
|
isPackageChangelog: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { CHANGELOG_CATEGORIES } from "../../commit/types";
|
|
6
6
|
import * as git from "../../utils/git";
|
|
@@ -15,7 +15,7 @@ const DEFAULT_MAX_DIFF_CHARS = 120_000;
|
|
|
15
15
|
export interface ChangelogFlowInput {
|
|
16
16
|
cwd: string;
|
|
17
17
|
model: Model<Api>;
|
|
18
|
-
apiKey:
|
|
18
|
+
apiKey: ApiKey;
|
|
19
19
|
thinkingLevel?: ThinkingLevel;
|
|
20
20
|
stagedFiles: string[];
|
|
21
21
|
dryRun: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { parseFileDiffs } from "../../commit/git/diff";
|
|
5
5
|
import type { ConventionalAnalysis } from "../../commit/types";
|
|
@@ -21,10 +21,10 @@ export interface MapReduceSettings {
|
|
|
21
21
|
|
|
22
22
|
export interface MapReduceInput {
|
|
23
23
|
model: Model<Api>;
|
|
24
|
-
apiKey:
|
|
24
|
+
apiKey: ApiKey;
|
|
25
25
|
thinkingLevel?: ThinkingLevel;
|
|
26
26
|
smolModel: Model<Api>;
|
|
27
|
-
smolApiKey:
|
|
27
|
+
smolApiKey: ApiKey;
|
|
28
28
|
smolThinkingLevel?: ThinkingLevel;
|
|
29
29
|
diff: string;
|
|
30
30
|
stat: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, AssistantMessage, Message, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, AssistantMessage, Message, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import fileObserverSystemPrompt from "../../commit/prompts/file-observer-system.md" with { type: "text" };
|
|
@@ -18,7 +18,7 @@ const RETRY_BACKOFF_MS = 1000;
|
|
|
18
18
|
|
|
19
19
|
export interface MapPhaseInput {
|
|
20
20
|
model: Model<Api>;
|
|
21
|
-
apiKey:
|
|
21
|
+
apiKey: ApiKey;
|
|
22
22
|
thinkingLevel?: ThinkingLevel;
|
|
23
23
|
files: FileDiff[];
|
|
24
24
|
config?: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import reduceSystemPrompt from "../../commit/prompts/reduce-system.md" with { type: "text" };
|
|
@@ -12,7 +12,7 @@ const ReduceTool = createConventionalAnalysisTool("Synthesize file observations
|
|
|
12
12
|
|
|
13
13
|
export interface ReducePhaseInput {
|
|
14
14
|
model: Model<Api>;
|
|
15
|
-
apiKey:
|
|
15
|
+
apiKey: ApiKey;
|
|
16
16
|
thinkingLevel?: ThinkingLevel;
|
|
17
17
|
observations: FileObservation[];
|
|
18
18
|
stat: string;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { ApiKeyResolverRegistry } from "../config/api-key-resolver";
|
|
3
4
|
import { MODEL_ROLE_IDS } from "../config/model-registry";
|
|
4
5
|
import {
|
|
6
|
+
getModelMatchPreferences,
|
|
5
7
|
type ModelLookupRegistry,
|
|
6
8
|
parseModelPattern,
|
|
7
9
|
resolveModelRoleValue,
|
|
@@ -12,13 +14,19 @@ import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
|
12
14
|
|
|
13
15
|
export interface ResolvedCommitModel {
|
|
14
16
|
model: Model<Api>;
|
|
15
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Resolver for the model's bearer: re-resolves on 401 / usage-limit so the
|
|
19
|
+
* whole commit pipeline (analysis, map/reduce, changelog) inherits the
|
|
20
|
+
* central force-refresh + account-rotation policy.
|
|
21
|
+
*/
|
|
22
|
+
apiKey: ApiKey;
|
|
16
23
|
thinkingLevel?: ThinkingLevel;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
type CommitModelRegistry = ModelLookupRegistry &
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
type CommitModelRegistry = ModelLookupRegistry &
|
|
27
|
+
ApiKeyResolverRegistry & {
|
|
28
|
+
getApiKey: (model: Model<Api>) => Promise<string | undefined>;
|
|
29
|
+
};
|
|
22
30
|
|
|
23
31
|
export async function resolvePrimaryModel(
|
|
24
32
|
override: string | undefined,
|
|
@@ -26,7 +34,7 @@ export async function resolvePrimaryModel(
|
|
|
26
34
|
modelRegistry: CommitModelRegistry,
|
|
27
35
|
): Promise<ResolvedCommitModel> {
|
|
28
36
|
const available = modelRegistry.getAvailable();
|
|
29
|
-
const matchPreferences =
|
|
37
|
+
const matchPreferences = getModelMatchPreferences(settings);
|
|
30
38
|
const resolved = override
|
|
31
39
|
? resolveModelRoleValue(override, available, { settings, matchPreferences, modelRegistry })
|
|
32
40
|
: resolveRoleSelection(["commit", "smol", ...MODEL_ROLE_IDS], settings, available, modelRegistry);
|
|
@@ -38,28 +46,45 @@ export async function resolvePrimaryModel(
|
|
|
38
46
|
if (!apiKey) {
|
|
39
47
|
throw new Error(`No API key available for model ${model.provider}/${model.id}`);
|
|
40
48
|
}
|
|
41
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
model,
|
|
51
|
+
apiKey: modelRegistry.resolver(model.provider, { baseUrl: model.baseUrl }),
|
|
52
|
+
thinkingLevel: resolved?.thinkingLevel,
|
|
53
|
+
};
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
export async function resolveSmolModel(
|
|
45
57
|
settings: Settings,
|
|
46
58
|
modelRegistry: CommitModelRegistry,
|
|
47
59
|
fallbackModel: Model<Api>,
|
|
48
|
-
fallbackApiKey:
|
|
60
|
+
fallbackApiKey: ApiKey,
|
|
49
61
|
): Promise<ResolvedCommitModel> {
|
|
50
62
|
const available = modelRegistry.getAvailable();
|
|
51
63
|
const resolvedSmol = resolveRoleSelection(["smol"], settings, available, modelRegistry);
|
|
52
64
|
if (resolvedSmol?.model) {
|
|
53
65
|
const apiKey = await modelRegistry.getApiKey(resolvedSmol.model);
|
|
54
|
-
if (apiKey)
|
|
66
|
+
if (apiKey) {
|
|
67
|
+
return {
|
|
68
|
+
model: resolvedSmol.model,
|
|
69
|
+
apiKey: modelRegistry.resolver(resolvedSmol.model.provider, {
|
|
70
|
+
baseUrl: resolvedSmol.model.baseUrl,
|
|
71
|
+
}),
|
|
72
|
+
thinkingLevel: resolvedSmol.thinkingLevel,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
55
75
|
}
|
|
56
76
|
|
|
57
|
-
const matchPreferences =
|
|
77
|
+
const matchPreferences = getModelMatchPreferences(settings);
|
|
58
78
|
for (const pattern of MODEL_PRIO.smol) {
|
|
59
79
|
const candidate = parseModelPattern(pattern, available, matchPreferences, { modelRegistry }).model;
|
|
60
80
|
if (!candidate) continue;
|
|
61
81
|
const apiKey = await modelRegistry.getApiKey(candidate);
|
|
62
|
-
if (apiKey)
|
|
82
|
+
if (apiKey) {
|
|
83
|
+
return {
|
|
84
|
+
model: candidate,
|
|
85
|
+
apiKey: modelRegistry.resolver(candidate.provider, { baseUrl: candidate.baseUrl }),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
return { model: fallbackModel, apiKey: fallbackApiKey };
|
package/src/commit/pipeline.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { getProjectDir, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { ModelRegistry } from "../config/model-registry";
|
|
6
6
|
import { Settings } from "../config/settings";
|
|
@@ -145,10 +145,10 @@ async function generateAnalysis(input: {
|
|
|
145
145
|
contextFiles: Array<{ path: string; content: string }>;
|
|
146
146
|
userContext?: string;
|
|
147
147
|
primaryModel: Model<Api>;
|
|
148
|
-
primaryApiKey:
|
|
148
|
+
primaryApiKey: ApiKey;
|
|
149
149
|
primaryThinkingLevel?: ThinkingLevel;
|
|
150
150
|
smolModel: Model<Api>;
|
|
151
|
-
smolApiKey:
|
|
151
|
+
smolApiKey: ApiKey;
|
|
152
152
|
smolThinkingLevel?: ThinkingLevel;
|
|
153
153
|
commitSettings: {
|
|
154
154
|
mapReduceEnabled: boolean;
|
|
@@ -206,7 +206,7 @@ async function generateSummaryWithRetry(input: {
|
|
|
206
206
|
analysis: ConventionalAnalysis;
|
|
207
207
|
stat: string;
|
|
208
208
|
model: Model<Api>;
|
|
209
|
-
apiKey:
|
|
209
|
+
apiKey: ApiKey;
|
|
210
210
|
thinkingLevel?: ThinkingLevel;
|
|
211
211
|
userContext?: string;
|
|
212
212
|
}): Promise<{ summary: string }> {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ApiKeyResolver, AuthStorage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
|
|
3
|
+
export interface ApiKeyResolverOptions {
|
|
4
|
+
/** Session id for credential stickiness; read at resolve time by the caller. */
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
/** Provider base URL hint forwarded to the auth-storage cascade. */
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Minimal slice of `ModelRegistry` the resolver needs. Typed structurally so
|
|
12
|
+
* narrower registry shells (e.g. the commit pipeline's `CommitModelRegistry`)
|
|
13
|
+
* can build resolvers without depending on the full class.
|
|
14
|
+
*/
|
|
15
|
+
export interface ApiKeyResolverRegistry {
|
|
16
|
+
getApiKeyForProvider(
|
|
17
|
+
provider: string,
|
|
18
|
+
sessionId?: string,
|
|
19
|
+
options?: { baseUrl?: string; forceRefresh?: boolean; signal?: AbortSignal },
|
|
20
|
+
): Promise<string | undefined>;
|
|
21
|
+
authStorage: Pick<AuthStorage, "rotateSessionCredential">;
|
|
22
|
+
/**
|
|
23
|
+
* Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
|
|
24
|
+
* policy: initial → resolve; step (b) → force-refresh same account; step (c)
|
|
25
|
+
* → rotate to a sibling credential, then re-resolve.
|
|
26
|
+
*
|
|
27
|
+
* The resolver is stateless (safe to reuse across requests). Callers that
|
|
28
|
+
* need the initial key for a guard can call `resolveApiKeyOnce(resolver)`.
|
|
29
|
+
*/
|
|
30
|
+
resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default implementation of {@link ApiKeyResolverRegistry.resolver}.
|
|
35
|
+
* Also usable standalone for structural registries that don't carry the method.
|
|
36
|
+
*/
|
|
37
|
+
export function createApiKeyResolver(
|
|
38
|
+
registry: Pick<ApiKeyResolverRegistry, "getApiKeyForProvider" | "authStorage">,
|
|
39
|
+
provider: string,
|
|
40
|
+
options: ApiKeyResolverOptions = {},
|
|
41
|
+
): ApiKeyResolver {
|
|
42
|
+
const { sessionId, baseUrl } = options;
|
|
43
|
+
return async ({ lastChance, error, signal }) => {
|
|
44
|
+
if (error === undefined) {
|
|
45
|
+
return registry.getApiKeyForProvider(provider, sessionId, { baseUrl });
|
|
46
|
+
}
|
|
47
|
+
if (lastChance) {
|
|
48
|
+
// Account constraint (401 / usage / account-rate-limit): rotate to a
|
|
49
|
+
// sibling credential. We do NOT honor any retry-after here — if a
|
|
50
|
+
// sibling exists we switch immediately; the precise no-sibling backoff
|
|
51
|
+
// is owned by `markUsageLimitReached` (default + server usage-report
|
|
52
|
+
// reset) and the outer whole-turn retry layer.
|
|
53
|
+
await registry.authStorage.rotateSessionCredential(provider, sessionId, { error, signal });
|
|
54
|
+
return registry.getApiKeyForProvider(provider, sessionId, { baseUrl });
|
|
55
|
+
}
|
|
56
|
+
return registry.getApiKeyForProvider(provider, sessionId, { baseUrl, forceRefresh: true, signal });
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const DEFAULT_MODEL_PROVIDER_ORDER = [
|
|
2
|
+
// First-party / native account providers. Prefer these over relays when the
|
|
3
|
+
// same upstream model is available in more than one place.
|
|
4
|
+
"openai-codex",
|
|
5
|
+
"anthropic",
|
|
6
|
+
"openai",
|
|
7
|
+
"google-gemini-cli",
|
|
8
|
+
"google",
|
|
9
|
+
"google-vertex",
|
|
10
|
+
"kimi-code",
|
|
11
|
+
"moonshot",
|
|
12
|
+
"qwen-portal",
|
|
13
|
+
"zai",
|
|
14
|
+
"xai-oauth",
|
|
15
|
+
"xai",
|
|
16
|
+
"mistral",
|
|
17
|
+
"deepseek",
|
|
18
|
+
"groq",
|
|
19
|
+
|
|
20
|
+
// High-quality aggregators / hosted inference providers.
|
|
21
|
+
"fireworks",
|
|
22
|
+
"cerebras",
|
|
23
|
+
"openrouter",
|
|
24
|
+
"together",
|
|
25
|
+
|
|
26
|
+
// Generic gateways and editor/proxy providers. These are useful when picked
|
|
27
|
+
// explicitly, but should not win ambiguous automatic role selection.
|
|
28
|
+
"alibaba-coding-plan",
|
|
29
|
+
"google-antigravity",
|
|
30
|
+
"opencode-zen",
|
|
31
|
+
"gitlab-duo",
|
|
32
|
+
"opencode-go",
|
|
33
|
+
"kilo",
|
|
34
|
+
"vercel-ai-gateway",
|
|
35
|
+
"cloudflare-ai-gateway",
|
|
36
|
+
"nanogpt",
|
|
37
|
+
"github-copilot",
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
function addProviderRank(rank: Map<string, number>, provider: string): void {
|
|
41
|
+
const normalized = provider.trim().toLowerCase();
|
|
42
|
+
if (!normalized || rank.has(normalized)) return;
|
|
43
|
+
rank.set(normalized, rank.size);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildModelProviderPriorityRank(configuredProviderOrder?: readonly string[]): Map<string, number> {
|
|
47
|
+
const rank = new Map<string, number>();
|
|
48
|
+
for (const provider of configuredProviderOrder ?? []) {
|
|
49
|
+
addProviderRank(rank, provider);
|
|
50
|
+
}
|
|
51
|
+
for (const provider of DEFAULT_MODEL_PROVIDER_ORDER) {
|
|
52
|
+
addProviderRank(rank, provider);
|
|
53
|
+
}
|
|
54
|
+
return rank;
|
|
55
|
+
}
|
|
@@ -95,12 +95,14 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
|
|
|
95
95
|
...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
|
|
96
96
|
];
|
|
97
97
|
|
|
98
|
+
import type { ApiKeyResolver } from "@oh-my-pi/pi-ai";
|
|
98
99
|
import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
|
|
99
100
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/utils/oauth/types";
|
|
100
101
|
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
101
102
|
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
102
103
|
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
103
104
|
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
105
|
+
import { type ApiKeyResolverOptions, createApiKeyResolver } from "./api-key-resolver";
|
|
104
106
|
import { type ConfigError, ConfigFile } from "./config-file";
|
|
105
107
|
import {
|
|
106
108
|
buildCanonicalModelIndex,
|
|
@@ -116,6 +118,7 @@ import {
|
|
|
116
118
|
getModelLikeIdSegments,
|
|
117
119
|
stripBracketedModelIdAffixes,
|
|
118
120
|
} from "./model-id-affixes";
|
|
121
|
+
import { buildModelProviderPriorityRank } from "./model-provider-priority";
|
|
119
122
|
import {
|
|
120
123
|
type ModelOverride,
|
|
121
124
|
type ModelsConfig,
|
|
@@ -2206,27 +2209,8 @@ export class ModelRegistry {
|
|
|
2206
2209
|
});
|
|
2207
2210
|
}
|
|
2208
2211
|
|
|
2209
|
-
#providerRank(
|
|
2210
|
-
|
|
2211
|
-
const result = new Map<string, number>();
|
|
2212
|
-
let nextRank = 0;
|
|
2213
|
-
for (const provider of configuredProviders) {
|
|
2214
|
-
const normalized = provider.trim().toLowerCase();
|
|
2215
|
-
if (!normalized || result.has(normalized)) {
|
|
2216
|
-
continue;
|
|
2217
|
-
}
|
|
2218
|
-
result.set(normalized, nextRank);
|
|
2219
|
-
nextRank += 1;
|
|
2220
|
-
}
|
|
2221
|
-
for (const model of models) {
|
|
2222
|
-
const normalized = model.provider.toLowerCase();
|
|
2223
|
-
if (result.has(normalized)) {
|
|
2224
|
-
continue;
|
|
2225
|
-
}
|
|
2226
|
-
result.set(normalized, nextRank);
|
|
2227
|
-
nextRank += 1;
|
|
2228
|
-
}
|
|
2229
|
-
return result;
|
|
2212
|
+
#providerRank(): Map<string, number> {
|
|
2213
|
+
return buildModelProviderPriorityRank(getConfiguredProviderOrderFromSettings());
|
|
2230
2214
|
}
|
|
2231
2215
|
|
|
2232
2216
|
#resolveCanonicalVariant(
|
|
@@ -2236,7 +2220,7 @@ export class ModelRegistry {
|
|
|
2236
2220
|
if (variants.length === 0) {
|
|
2237
2221
|
return undefined;
|
|
2238
2222
|
}
|
|
2239
|
-
const providerRank = this.#providerRank(
|
|
2223
|
+
const providerRank = this.#providerRank();
|
|
2240
2224
|
const modelOrder = new Map<string, number>();
|
|
2241
2225
|
for (let index = 0; index < allCandidates.length; index += 1) {
|
|
2242
2226
|
modelOrder.set(formatCanonicalVariantSelector(allCandidates[index]!), index);
|
|
@@ -2365,12 +2349,33 @@ export class ModelRegistry {
|
|
|
2365
2349
|
|
|
2366
2350
|
/**
|
|
2367
2351
|
* Get API key for a provider (e.g., "openai").
|
|
2352
|
+
*
|
|
2353
|
+
* `options.forceRefresh` powers step (b) of the auth-retry policy — it
|
|
2354
|
+
* re-mints the session-sticky OAuth token even when the cached copy still
|
|
2355
|
+
* looks valid. `options.signal` is threaded into any broker-bound refresh.
|
|
2368
2356
|
*/
|
|
2369
|
-
async getApiKeyForProvider(
|
|
2357
|
+
async getApiKeyForProvider(
|
|
2358
|
+
provider: string,
|
|
2359
|
+
sessionId?: string,
|
|
2360
|
+
options?: { baseUrl?: string; forceRefresh?: boolean; signal?: AbortSignal },
|
|
2361
|
+
): Promise<string | undefined> {
|
|
2370
2362
|
if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
|
|
2371
2363
|
return kNoAuth;
|
|
2372
2364
|
}
|
|
2373
|
-
return this.authStorage.getApiKey(provider, sessionId, {
|
|
2365
|
+
return this.authStorage.getApiKey(provider, sessionId, {
|
|
2366
|
+
baseUrl: options?.baseUrl,
|
|
2367
|
+
forceRefresh: options?.forceRefresh,
|
|
2368
|
+
signal: options?.signal,
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
/**
|
|
2373
|
+
* Build an {@link ApiKeyResolver} for this provider, implementing the
|
|
2374
|
+
* central a/b/c auth-retry policy. Callers that need the initial key for
|
|
2375
|
+
* a guard can call `resolveApiKeyOnce(resolver)`.
|
|
2376
|
+
*/
|
|
2377
|
+
resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver {
|
|
2378
|
+
return createApiKeyResolver(this, provider, options);
|
|
2374
2379
|
}
|
|
2375
2380
|
|
|
2376
2381
|
async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
|