@oh-my-pi/pi-coding-agent 16.2.5 → 16.2.7
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 +41 -1
- package/dist/cli.js +3089 -3104
- package/dist/types/cli/bench-cli.d.ts +6 -1
- package/dist/types/commands/bench.d.ts +8 -0
- package/dist/types/config/model-discovery.d.ts +6 -1
- package/dist/types/config/service-tier.d.ts +39 -24
- package/dist/types/config/settings-schema.d.ts +37 -37
- package/dist/types/edit/index.d.ts +1 -0
- package/dist/types/edit/renderer.d.ts +4 -0
- package/dist/types/edit/snapshot-details.d.ts +33 -0
- package/dist/types/mcp/config-writer.d.ts +48 -0
- package/dist/types/mcp/oauth-flow.d.ts +4 -6
- package/dist/types/mcp/types.d.ts +3 -0
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +9 -2
- package/dist/types/session/agent-session.d.ts +37 -13
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/session-context.d.ts +2 -2
- package/dist/types/session/session-entries.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +5 -6
- package/dist/types/system-prompt.test.d.ts +1 -0
- package/dist/types/task/executor.d.ts +6 -6
- package/dist/types/task/types.d.ts +6 -0
- package/dist/types/task/worktree.d.ts +32 -6
- package/dist/types/tiny/title-client.d.ts +5 -1
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/utils/git.d.ts +17 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +2 -2
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +12 -12
- package/src/cli/args.ts +32 -1
- package/src/cli/bench-cli.ts +97 -22
- package/src/cli/tiny-models-cli.ts +18 -4
- package/src/cli/web-search-cli.ts +6 -1
- package/src/commands/bench.ts +10 -1
- package/src/config/mcp-schema.json +11 -2
- package/src/config/model-discovery.ts +66 -8
- package/src/config/model-registry.ts +13 -6
- package/src/config/service-tier.ts +85 -56
- package/src/config/settings-schema.ts +42 -36
- package/src/config/settings.ts +47 -0
- package/src/edit/hashline/execute.ts +15 -9
- package/src/edit/index.ts +19 -6
- package/src/edit/modes/patch.ts +3 -2
- package/src/edit/modes/replace.ts +3 -2
- package/src/edit/renderer.ts +4 -0
- package/src/edit/snapshot-details.ts +77 -0
- package/src/eval/agent-bridge.ts +4 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +2 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/config-writer.ts +121 -0
- package/src/mcp/config.ts +10 -6
- package/src/mcp/oauth-flow.ts +10 -8
- package/src/mcp/transports/stdio.ts +9 -17
- package/src/mcp/types.ts +3 -0
- package/src/modes/components/extensions/extension-dashboard.ts +46 -0
- package/src/modes/components/extensions/state-manager.ts +24 -3
- package/src/modes/controllers/event-controller.ts +24 -8
- package/src/modes/controllers/tool-args-reveal.ts +100 -22
- package/src/modes/utils/transcript-render-helpers.ts +2 -2
- package/src/prompts/bench.md +4 -10
- package/src/prompts/tools/irc.md +19 -29
- package/src/prompts/tools/job.md +8 -14
- package/src/prompts/tools/lsp.md +19 -30
- package/src/prompts/tools/task.md +42 -62
- package/src/sdk.ts +13 -11
- package/src/session/agent-session.ts +196 -76
- package/src/session/messages.ts +1 -1
- package/src/session/session-context.ts +4 -4
- package/src/session/session-entries.ts +2 -2
- package/src/session/session-listing.ts +9 -8
- package/src/session/session-loader.ts +98 -3
- package/src/session/session-manager.ts +43 -6
- package/src/slash-commands/builtin-registry.ts +2 -10
- package/src/subprocess/worker-client.ts +12 -4
- package/src/system-prompt.test.ts +158 -0
- package/src/system-prompt.ts +69 -26
- package/src/task/executor.ts +23 -16
- package/src/task/index.ts +7 -5
- package/src/task/isolation-runner.ts +15 -1
- package/src/task/types.ts +6 -0
- package/src/task/worktree.ts +219 -38
- package/src/tiny/title-client.ts +19 -13
- package/src/tools/index.ts +3 -3
- package/src/tools/irc.ts +9 -3
- package/src/tools/path-utils.ts +4 -2
- package/src/tools/read.ts +28 -28
- package/src/utils/file-mentions.ts +10 -1
- package/src/utils/git.ts +38 -0
- package/src/web/search/index.ts +14 -8
- package/src/web/search/providers/duckduckgo.ts +150 -78
- package/src/web/search/providers/gemini.ts +268 -185
- package/src/web/search/types.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageContent, Message, MessageAttribution,
|
|
1
|
+
import type { ImageContent, Message, MessageAttribution, ServiceTierByFamily, TextContent } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { ArtifactManager } from "./artifacts";
|
|
3
3
|
import { type BlobPutOptions, type BlobPutResult } from "./blob-store";
|
|
4
4
|
import { type BashExecutionMessage, type CustomMessage, type FileMentionMessage, type HookMessage, type PythonExecutionMessage } from "./messages";
|
|
@@ -75,10 +75,9 @@ export declare class SessionManager {
|
|
|
75
75
|
/** Flush pending writes. Call before switching sessions or on shutdown. */
|
|
76
76
|
flush(): Promise<void>;
|
|
77
77
|
/**
|
|
78
|
-
* Synchronously
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* lands mid-`writeFileSync`.
|
|
78
|
+
* Synchronously makes the current append-only session durable. Avoid rewriting
|
|
79
|
+
* an already-current file: large restored sessions can contain GiB of compacted
|
|
80
|
+
* history, and Ctrl+C must not rebuild the whole JSONL string just to flush.
|
|
82
81
|
*/
|
|
83
82
|
flushSync(): void;
|
|
84
83
|
/** Flush, then close the append writer. */
|
|
@@ -142,7 +141,7 @@ export declare class SessionManager {
|
|
|
142
141
|
appendMessage(message: Message | CustomMessage | HookMessage | BashExecutionMessage | PythonExecutionMessage | FileMentionMessage): string;
|
|
143
142
|
/** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */
|
|
144
143
|
appendThinkingLevelChange(thinkingLevel?: string, configured?: string): string;
|
|
145
|
-
appendServiceTierChange(serviceTier:
|
|
144
|
+
appendServiceTierChange(serviceTier: ServiceTierByFamily | null): string;
|
|
146
145
|
appendModeChange(mode: string, data?: Record<string, unknown>): string;
|
|
147
146
|
/**
|
|
148
147
|
* Append a model change as a child of the current leaf, then advance the leaf.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
|
|
5
5
|
*/
|
|
6
6
|
import type { AgentTelemetryConfig, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
|
-
import type {
|
|
7
|
+
import type { ServiceTierByFamily } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { Rule } from "../capability/rule";
|
|
9
9
|
import { ModelRegistry } from "../config/model-registry";
|
|
10
10
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
@@ -123,12 +123,12 @@ export interface ExecutorOptions {
|
|
|
123
123
|
modelRegistry?: ModelRegistry;
|
|
124
124
|
settings?: Settings;
|
|
125
125
|
/**
|
|
126
|
-
* Parent session's live
|
|
127
|
-
* subagent whose `
|
|
126
|
+
* Parent session's live per-family service tiers, the source of truth for a
|
|
127
|
+
* subagent whose `tier.subagent` is `"inherit"`. `null` = the parent
|
|
128
128
|
* explicitly has no tier (e.g. `/fast off`); omitted = no live session, so
|
|
129
|
-
* inherit falls back to the configured `
|
|
129
|
+
* inherit falls back to the subagent's configured `tier.*` settings.
|
|
130
130
|
*/
|
|
131
|
-
parentServiceTier?:
|
|
131
|
+
parentServiceTier?: ServiceTierByFamily | null;
|
|
132
132
|
/** Override local:// protocol options so subagent shares parent's local:// root */
|
|
133
133
|
localProtocolOptions?: LocalProtocolOptions;
|
|
134
134
|
/**
|
|
@@ -191,7 +191,7 @@ export declare function finalizeSubprocessOutput(args: FinalizeSubprocessOutputA
|
|
|
191
191
|
* Create proxy tools that reuse the parent's MCP connections.
|
|
192
192
|
*/
|
|
193
193
|
export declare function createMCPProxyTools(mcpManager: MCPManager): CustomTool[];
|
|
194
|
-
export declare function createSubagentSettings(baseSettings: Settings, overrides?: Partial<Record<SettingPath, unknown>>, inheritedServiceTier?:
|
|
194
|
+
export declare function createSubagentSettings(baseSettings: Settings, overrides?: Partial<Record<SettingPath, unknown>>, inheritedServiceTier?: ServiceTierByFamily | null): Settings;
|
|
195
195
|
export declare function finalizeSubagentLifecycle(args: {
|
|
196
196
|
id: string;
|
|
197
197
|
session: AgentSession;
|
|
@@ -334,6 +334,12 @@ export interface SingleResult {
|
|
|
334
334
|
patchPath?: string;
|
|
335
335
|
/** Branch name for isolated branch-mode output */
|
|
336
336
|
branchName?: string;
|
|
337
|
+
/**
|
|
338
|
+
* Baseline commit SHA the task branch was created from. Passed to
|
|
339
|
+
* `mergeTaskBranches` so cherry-pick uses the inclusive range
|
|
340
|
+
* `branchBaseSha..branchName` and preserves every agent commit's message.
|
|
341
|
+
*/
|
|
342
|
+
branchBaseSha?: string;
|
|
337
343
|
/** Nested repo patches to apply after parent merge */
|
|
338
344
|
nestedPatches?: NestedRepoPatch[];
|
|
339
345
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
@@ -80,11 +80,32 @@ export declare function cleanupIsolation(handle: IsolationHandle): Promise<void>
|
|
|
80
80
|
export interface CommitToBranchResult {
|
|
81
81
|
branchName?: string;
|
|
82
82
|
nestedPatches: NestedRepoPatch[];
|
|
83
|
+
/**
|
|
84
|
+
* SHA of the parent-repo commit the task branch was created on top of, so
|
|
85
|
+
* {@link mergeTaskBranches} can cherry-pick the range `baseSha..branchName`
|
|
86
|
+
* and preserve every agent commit's message and author.
|
|
87
|
+
*/
|
|
88
|
+
baseSha?: string;
|
|
83
89
|
}
|
|
84
90
|
/**
|
|
85
|
-
*
|
|
86
|
-
* Only root
|
|
87
|
-
* separately
|
|
91
|
+
* Capture task-only changes from the isolation worktree onto a parent-repo
|
|
92
|
+
* branch named `omp/task/${taskId}`. Only root-repo changes go on the branch;
|
|
93
|
+
* nested-repo patches are returned separately because the parent git can't
|
|
94
|
+
* track files inside gitlinks.
|
|
95
|
+
*
|
|
96
|
+
* If the agent committed inside isolation (HEAD moved past
|
|
97
|
+
* `baseline.root.headCommit`), clean-baseline runs fetch the raw commit range
|
|
98
|
+
* into the parent repo and later cherry-pick `baseSha..branchName`, preserving
|
|
99
|
+
* every message and author verbatim. Dirty-baseline runs rewrite each agent
|
|
100
|
+
* commit against the captured baseline WIP before committing it to the task
|
|
101
|
+
* branch, so user staged/unstaged/untracked changes present at isolation
|
|
102
|
+
* start are not replayed into the parent commit history.
|
|
103
|
+
*
|
|
104
|
+
* If the agent did not commit, the captured delta is collapsed onto a single
|
|
105
|
+
* branch commit with an AI-generated (or fallback) message — the legacy
|
|
106
|
+
* behaviour.
|
|
107
|
+
*
|
|
108
|
+
* Returns `null` when no root or nested changes exist.
|
|
88
109
|
*/
|
|
89
110
|
export declare function commitToBranch(isolationDir: string, baseline: WorktreeBaseline, taskId: string, description: string | undefined, commitMessage?: (diff: string) => Promise<string | null>): Promise<CommitToBranchResult | null>;
|
|
90
111
|
export interface MergeBranchResult {
|
|
@@ -95,14 +116,19 @@ export interface MergeBranchResult {
|
|
|
95
116
|
stashConflict?: string;
|
|
96
117
|
}
|
|
97
118
|
/**
|
|
98
|
-
* Cherry-pick task branch commits sequentially onto HEAD.
|
|
99
|
-
*
|
|
100
|
-
*
|
|
119
|
+
* Cherry-pick task branch commits sequentially onto HEAD. When `baseSha` is
|
|
120
|
+
* provided the cherry-pick uses the inclusive range `baseSha..branchName`,
|
|
121
|
+
* replaying every commit individually and preserving each commit's message
|
|
122
|
+
* and author. When omitted, the branch is cherry-picked as a single commit
|
|
123
|
+
* (legacy callers).
|
|
124
|
+
*
|
|
125
|
+
* Stops on the first conflict and reports which branches succeeded.
|
|
101
126
|
*/
|
|
102
127
|
export declare function mergeTaskBranches(repoRoot: string, branches: Array<{
|
|
103
128
|
branchName: string;
|
|
104
129
|
taskId: string;
|
|
105
130
|
description?: string;
|
|
131
|
+
baseSha?: string;
|
|
106
132
|
}>): Promise<MergeBranchResult>;
|
|
107
133
|
/** Clean up temporary task branches. */
|
|
108
134
|
export declare function cleanupTaskBranches(repoRoot: string, branches: string[]): Promise<void>;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type RefCountedWorkerHandle, type SpawnedSubprocess } from "../subprocess/worker-client";
|
|
2
2
|
import type { TinyTitleProgressEvent, TinyTitleWorkerInbound, TinyTitleWorkerOutbound } from "./title-protocol";
|
|
3
|
+
export interface TinyTitleDownloadResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
}
|
|
3
7
|
export interface TinyTitleDownloadOptions {
|
|
4
8
|
signal?: AbortSignal;
|
|
5
9
|
onProgress?: (event: TinyTitleProgressEvent) => void;
|
|
@@ -52,7 +56,7 @@ export declare class TinyTitleClient {
|
|
|
52
56
|
maxTokens?: number;
|
|
53
57
|
signal?: AbortSignal;
|
|
54
58
|
}): Promise<string | null>;
|
|
55
|
-
downloadModel(modelKey: string, options?: TinyTitleDownloadOptions): Promise<
|
|
59
|
+
downloadModel(modelKey: string, options?: TinyTitleDownloadOptions): Promise<TinyTitleDownloadResult>;
|
|
56
60
|
terminate(): Promise<void>;
|
|
57
61
|
}
|
|
58
62
|
export declare const tinyTitleClient: TinyTitleClient;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
|
2
2
|
import type { AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import type { FetchImpl, ImageContent, Model,
|
|
3
|
+
import type { FetchImpl, ImageContent, Model, ServiceTierByFamily, ToolChoice } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import type { AsyncJobManager } from "../async/job-manager";
|
|
5
5
|
import type { Rule } from "../capability/rule";
|
|
6
6
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
@@ -191,8 +191,8 @@ export interface ToolSession {
|
|
|
191
191
|
getActiveModelString?: () => string | undefined;
|
|
192
192
|
/** Get the current session model object (provider/api capabilities), regardless of how it was chosen. */
|
|
193
193
|
getActiveModel?: () => Model | undefined;
|
|
194
|
-
/** Get the session's live
|
|
195
|
-
|
|
194
|
+
/** Get the session's live per-family service tiers (undefined = none). Source of truth for subagent `tier.subagent: inherit`. */
|
|
195
|
+
getServiceTierByFamily?: () => ServiceTierByFamily | undefined;
|
|
196
196
|
/** Auth storage for passing to subagents (avoids re-discovery) */
|
|
197
197
|
authStorage?: import("../session/auth-storage").AuthStorage;
|
|
198
198
|
/** Model registry for passing to subagents (avoids re-discovery) */
|
|
@@ -63,8 +63,18 @@ export interface StatusOptions {
|
|
|
63
63
|
readonly untrackedFiles?: "all" | "no" | "normal";
|
|
64
64
|
readonly z?: boolean;
|
|
65
65
|
}
|
|
66
|
+
export interface CommitAuthor {
|
|
67
|
+
readonly date?: string;
|
|
68
|
+
readonly email: string;
|
|
69
|
+
readonly name: string;
|
|
70
|
+
}
|
|
71
|
+
export interface CommitDetails {
|
|
72
|
+
readonly author: CommitAuthor;
|
|
73
|
+
readonly message: string;
|
|
74
|
+
}
|
|
66
75
|
export interface CommitOptions {
|
|
67
76
|
readonly allowEmpty?: boolean;
|
|
77
|
+
readonly author?: CommitAuthor;
|
|
68
78
|
readonly files?: readonly string[];
|
|
69
79
|
readonly signal?: AbortSignal;
|
|
70
80
|
}
|
|
@@ -78,6 +88,7 @@ export interface PatchOptions {
|
|
|
78
88
|
readonly cached?: boolean;
|
|
79
89
|
readonly check?: boolean;
|
|
80
90
|
readonly env?: Record<string, string | undefined>;
|
|
91
|
+
readonly threeWay?: boolean;
|
|
81
92
|
readonly signal?: AbortSignal;
|
|
82
93
|
}
|
|
83
94
|
export interface RestoreOptions {
|
|
@@ -195,12 +206,18 @@ export declare const show: ((cwd: string, revision: string, options?: {
|
|
|
195
206
|
/** Get the path prefix of the current directory relative to the repo root. */
|
|
196
207
|
prefix(cwd: string, signal?: AbortSignal): Promise<string>;
|
|
197
208
|
};
|
|
209
|
+
/** Read commit message and author metadata for replay/rewrite flows. */
|
|
210
|
+
export declare function commitDetails(cwd: string, revision: string, signal?: AbortSignal): Promise<CommitDetails>;
|
|
198
211
|
export declare const log: {
|
|
199
212
|
/** Recent commit subjects (one-line each). */
|
|
200
213
|
subjects(cwd: string, count: number, signal?: AbortSignal): Promise<string[]>;
|
|
201
214
|
/** Recent commits as `<short-sha> <subject>` onelines. */
|
|
202
215
|
onelines(cwd: string, count: number, signal?: AbortSignal): Promise<string[]>;
|
|
203
216
|
};
|
|
217
|
+
export declare const revList: {
|
|
218
|
+
/** Commits in `base..head`, oldest first. */
|
|
219
|
+
range(cwd: string, base: string, head: string, signal?: AbortSignal): Promise<string[]>;
|
|
220
|
+
};
|
|
204
221
|
export declare const branch: {
|
|
205
222
|
/** Current branch name, or null if detached/unavailable. */
|
|
206
223
|
current(cwd: string, signal?: AbortSignal): Promise<string | null>;
|
|
@@ -2,9 +2,9 @@ import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
|
2
2
|
import type { SearchResponse } from "../../../web/search/types";
|
|
3
3
|
import type { SearchParams } from "./base";
|
|
4
4
|
import { SearchProvider } from "./base";
|
|
5
|
-
/** Execute DuckDuckGo
|
|
5
|
+
/** Execute a DuckDuckGo web search via the no-JS HTML frontend. */
|
|
6
6
|
export declare function searchDuckDuckGo(params: SearchParams): Promise<SearchResponse>;
|
|
7
|
-
/** Search provider for DuckDuckGo
|
|
7
|
+
/** Search provider for DuckDuckGo (no API key required). */
|
|
8
8
|
export declare class DuckDuckGoProvider extends SearchProvider {
|
|
9
9
|
readonly id = "duckduckgo";
|
|
10
10
|
readonly label = "DuckDuckGo";
|
|
@@ -78,7 +78,7 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
|
|
|
78
78
|
}, {
|
|
79
79
|
readonly value: "duckduckgo";
|
|
80
80
|
readonly label: "DuckDuckGo";
|
|
81
|
-
readonly description: "
|
|
81
|
+
readonly description: "Scrapes the DuckDuckGo HTML frontend (no API key)";
|
|
82
82
|
}];
|
|
83
83
|
/** Supported web search providers (every option except `auto`). */
|
|
84
84
|
export type SearchProviderId = Exclude<(typeof SEARCH_PROVIDER_OPTIONS)[number]["value"], "auto">;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.2.
|
|
4
|
+
"version": "16.2.7",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -55,17 +55,17 @@
|
|
|
55
55
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
56
56
|
"@babel/parser": "^7.29.7",
|
|
57
57
|
"@mozilla/readability": "^0.6.0",
|
|
58
|
-
"@oh-my-pi/hashline": "16.2.
|
|
59
|
-
"@oh-my-pi/omp-stats": "16.2.
|
|
60
|
-
"@oh-my-pi/pi-agent-core": "16.2.
|
|
61
|
-
"@oh-my-pi/pi-ai": "16.2.
|
|
62
|
-
"@oh-my-pi/pi-catalog": "16.2.
|
|
63
|
-
"@oh-my-pi/pi-mnemopi": "16.2.
|
|
64
|
-
"@oh-my-pi/pi-natives": "16.2.
|
|
65
|
-
"@oh-my-pi/pi-tui": "16.2.
|
|
66
|
-
"@oh-my-pi/pi-utils": "16.2.
|
|
67
|
-
"@oh-my-pi/pi-wire": "16.2.
|
|
68
|
-
"@oh-my-pi/snapcompact": "16.2.
|
|
58
|
+
"@oh-my-pi/hashline": "16.2.7",
|
|
59
|
+
"@oh-my-pi/omp-stats": "16.2.7",
|
|
60
|
+
"@oh-my-pi/pi-agent-core": "16.2.7",
|
|
61
|
+
"@oh-my-pi/pi-ai": "16.2.7",
|
|
62
|
+
"@oh-my-pi/pi-catalog": "16.2.7",
|
|
63
|
+
"@oh-my-pi/pi-mnemopi": "16.2.7",
|
|
64
|
+
"@oh-my-pi/pi-natives": "16.2.7",
|
|
65
|
+
"@oh-my-pi/pi-tui": "16.2.7",
|
|
66
|
+
"@oh-my-pi/pi-utils": "16.2.7",
|
|
67
|
+
"@oh-my-pi/pi-wire": "16.2.7",
|
|
68
|
+
"@oh-my-pi/snapcompact": "16.2.7",
|
|
69
69
|
"@opentelemetry/api": "^1.9.1",
|
|
70
70
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
71
71
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
package/src/cli/args.ts
CHANGED
|
@@ -94,6 +94,35 @@ const PARSE_DEPS: ParseDeps = {
|
|
|
94
94
|
thinkingEfforts: CLI_THINKING_LEVELS,
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
+
const WINDOWS_PATH_VALUE_FLAGS: ReadonlySet<string> = new Set(["--extension", "-e", "--hook"]);
|
|
98
|
+
const WINDOWS_PATH_START_RE =
|
|
99
|
+
/^(?:[A-Za-z]:[\\/]|\\\\[?]\\(?:[A-Za-z]:[\\/]|UNC[\\/])|\\\\[^\\/]+[\\/][^\\/]+[\\/]|\/\/[?]\/(?:[A-Za-z]:\/|UNC\/)|\/\/[^/]+\/[^/]+\/)/;
|
|
100
|
+
const WINDOWS_MODULE_PATH_SUFFIX_RE = /\.(?:[cm]?[jt]sx?)$/i;
|
|
101
|
+
|
|
102
|
+
function consumeBuiltInStringValue(flag: string, args: string[], valueIndex: number): { value: string; index: number } {
|
|
103
|
+
const value = args[valueIndex];
|
|
104
|
+
if (
|
|
105
|
+
value === undefined ||
|
|
106
|
+
!WINDOWS_PATH_VALUE_FLAGS.has(flag) ||
|
|
107
|
+
!WINDOWS_PATH_START_RE.test(value) ||
|
|
108
|
+
WINDOWS_MODULE_PATH_SUFFIX_RE.test(value)
|
|
109
|
+
) {
|
|
110
|
+
return { value: value ?? "", index: valueIndex };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let candidate = value;
|
|
114
|
+
for (let index = valueIndex + 1; index < args.length; index++) {
|
|
115
|
+
const next = args[index];
|
|
116
|
+
if (next === PROFILE_BOOTSTRAP_BOUNDARY_ARG || next.startsWith("-")) break;
|
|
117
|
+
candidate += ` ${next}`;
|
|
118
|
+
if (WINDOWS_MODULE_PATH_SUFFIX_RE.test(candidate)) {
|
|
119
|
+
return { value: candidate, index };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { value, index: valueIndex };
|
|
124
|
+
}
|
|
125
|
+
|
|
97
126
|
export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { type: "boolean" | "string" }>): Args {
|
|
98
127
|
// Work on a copy: the `--option=value` handling below splices the value
|
|
99
128
|
// into the array, and callers reuse the same argv (the post-extension
|
|
@@ -161,7 +190,9 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
161
190
|
// here only when its boolean extension is NOT loaded) would otherwise swallow
|
|
162
191
|
// the marker as its value and drop the user's trailing message.
|
|
163
192
|
if (i + 1 < args.length && args[i + 1] !== PROFILE_BOOTSTRAP_BOUNDARY_ARG) {
|
|
164
|
-
|
|
193
|
+
const consumed = consumeBuiltInStringValue(arg, args, i + 1);
|
|
194
|
+
i = consumed.index;
|
|
195
|
+
STRING_SETTERS[arg](result, consumed.value, PARSE_DEPS);
|
|
165
196
|
}
|
|
166
197
|
} else if (OPTIONAL_VALUE_FLAGS.has(arg)) {
|
|
167
198
|
const config = OPTIONAL_FLAGS[arg];
|
package/src/cli/bench-cli.ts
CHANGED
|
@@ -8,9 +8,12 @@ import type {
|
|
|
8
8
|
Context,
|
|
9
9
|
Effort,
|
|
10
10
|
Model,
|
|
11
|
+
ProviderSessionState,
|
|
12
|
+
ServiceTier,
|
|
13
|
+
ServiceTierByFamily,
|
|
11
14
|
SimpleStreamOptions,
|
|
12
15
|
} from "@oh-my-pi/pi-ai";
|
|
13
|
-
import { streamSimple } from "@oh-my-pi/pi-ai";
|
|
16
|
+
import { resolveModelServiceTier, streamSimple } from "@oh-my-pi/pi-ai";
|
|
14
17
|
import { buildModelProviderPriorityRank, type CanonicalModelVariant } from "@oh-my-pi/pi-catalog/identity";
|
|
15
18
|
import { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
16
19
|
import { formatDuration, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
@@ -23,12 +26,14 @@ import {
|
|
|
23
26
|
getModelMatchPreferences,
|
|
24
27
|
resolveCliModel,
|
|
25
28
|
} from "../config/model-resolver";
|
|
29
|
+
import { buildServiceTierByFamily, serviceTierForAllFamilies, serviceTierSettingToTier } from "../config/service-tier";
|
|
26
30
|
import { Settings } from "../config/settings";
|
|
27
31
|
import benchPrompt from "../prompts/bench.md" with { type: "text" };
|
|
28
32
|
import { discoverAuthStorage, loadCliExtensionProviders } from "../sdk";
|
|
29
33
|
import { resolveThinkingLevelForModel, shouldDisableReasoning, toReasoningEffort } from "../thinking";
|
|
30
34
|
|
|
31
|
-
const DEFAULT_RUNS =
|
|
35
|
+
const DEFAULT_RUNS = 10;
|
|
36
|
+
const DEFAULT_PAR = 4;
|
|
32
37
|
const DEFAULT_MAX_TOKENS = 512;
|
|
33
38
|
const ERROR_WIDTH = 110;
|
|
34
39
|
const BENCH_PROMPT = benchPrompt.trim();
|
|
@@ -39,7 +44,10 @@ export interface BenchCommandArgs {
|
|
|
39
44
|
runs?: number;
|
|
40
45
|
maxTokens?: number;
|
|
41
46
|
prompt?: string;
|
|
47
|
+
/** Service-tier setting value (`none` omits); overrides the configured `serviceTier` setting. */
|
|
48
|
+
serviceTier?: string;
|
|
42
49
|
json?: boolean;
|
|
50
|
+
par?: number;
|
|
43
51
|
};
|
|
44
52
|
}
|
|
45
53
|
|
|
@@ -99,6 +107,8 @@ export interface BenchSummary {
|
|
|
99
107
|
maxTokens: number;
|
|
100
108
|
models: BenchModelReport[];
|
|
101
109
|
failures: number;
|
|
110
|
+
/** Requested per-family service tiers, resolved per model before reaching the wire. */
|
|
111
|
+
serviceTierByFamily?: ServiceTierByFamily;
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
type BenchStreamSimple = (
|
|
@@ -131,6 +141,13 @@ function normalizePositiveInteger(name: string, value: number | undefined, fallb
|
|
|
131
141
|
return value;
|
|
132
142
|
}
|
|
133
143
|
|
|
144
|
+
function closeProviderSessionStates(providerSessionState: Map<string, ProviderSessionState>): void {
|
|
145
|
+
for (const state of providerSessionState.values()) {
|
|
146
|
+
state.close();
|
|
147
|
+
}
|
|
148
|
+
providerSessionState.clear();
|
|
149
|
+
}
|
|
150
|
+
|
|
134
151
|
function isFirstTokenEvent(event: AssistantMessageEvent): boolean {
|
|
135
152
|
switch (event.type) {
|
|
136
153
|
case "text_delta":
|
|
@@ -188,6 +205,8 @@ interface BenchRequestOptions {
|
|
|
188
205
|
reasoning?: Effort;
|
|
189
206
|
/** Only set for an explicit `:off` suffix — some endpoints reject disablement. */
|
|
190
207
|
disableReasoning?: boolean;
|
|
208
|
+
/** Requested service tier passed to `streamSimple`; absent omits the option. The provider layer applies scope/support gating before it reaches the wire. */
|
|
209
|
+
serviceTier?: ServiceTier;
|
|
191
210
|
}
|
|
192
211
|
|
|
193
212
|
async function runBenchRequest(
|
|
@@ -198,6 +217,7 @@ async function runBenchRequest(
|
|
|
198
217
|
): Promise<BenchRunResult> {
|
|
199
218
|
const startedAt = now();
|
|
200
219
|
let firstTokenAt: number | undefined;
|
|
220
|
+
const providerSessionState = new Map<string, ProviderSessionState>();
|
|
201
221
|
try {
|
|
202
222
|
const context: Context = {
|
|
203
223
|
// Codex's Responses endpoint 400s with "Instructions are required" when no
|
|
@@ -214,6 +234,9 @@ async function runBenchRequest(
|
|
|
214
234
|
: options.maxTokens,
|
|
215
235
|
reasoning: options.reasoning,
|
|
216
236
|
disableReasoning: options.disableReasoning,
|
|
237
|
+
serviceTier: options.serviceTier,
|
|
238
|
+
providerSessionState,
|
|
239
|
+
preferWebsockets: true,
|
|
217
240
|
// pi-ai opts every OpenRouter request into response caching (1h TTL).
|
|
218
241
|
// Bench sends a byte-identical request each run, so within the TTL
|
|
219
242
|
// OpenRouter replays the cached generation with zeroed usage — the run
|
|
@@ -270,6 +293,8 @@ async function runBenchRequest(
|
|
|
270
293
|
};
|
|
271
294
|
} catch (error) {
|
|
272
295
|
return { ok: false, error: getErrorMessage(error) };
|
|
296
|
+
} finally {
|
|
297
|
+
closeProviderSessionStates(providerSessionState);
|
|
273
298
|
}
|
|
274
299
|
}
|
|
275
300
|
|
|
@@ -469,10 +494,11 @@ function resolveBenchModels(
|
|
|
469
494
|
}
|
|
470
495
|
return resolved;
|
|
471
496
|
}
|
|
472
|
-
|
|
473
497
|
export async function runBenchCommand(command: BenchCommandArgs, deps: BenchDependencies = {}): Promise<BenchSummary> {
|
|
474
498
|
const runs = normalizePositiveInteger("runs", command.flags.runs, DEFAULT_RUNS);
|
|
475
499
|
const maxTokens = normalizePositiveInteger("max-tokens", command.flags.maxTokens, DEFAULT_MAX_TOKENS);
|
|
500
|
+
const par =
|
|
501
|
+
command.flags.par !== undefined ? normalizePositiveInteger("par", command.flags.par, DEFAULT_PAR) : DEFAULT_PAR;
|
|
476
502
|
const prompt = command.flags.prompt?.trim() || BENCH_PROMPT;
|
|
477
503
|
const json = command.flags.json === true;
|
|
478
504
|
const randomSessionId = deps.randomSessionId ?? (() => Bun.randomUUIDv7());
|
|
@@ -493,6 +519,18 @@ export async function runBenchCommand(command: BenchCommandArgs, deps: BenchDepe
|
|
|
493
519
|
const runtime = await (deps.createRuntime ?? createDefaultRuntime)();
|
|
494
520
|
try {
|
|
495
521
|
const targets = resolveBenchModels(command.models, runtime.modelRegistry, runtime.settings, writeStderr);
|
|
522
|
+
// Explicit `--service-tier` (a single value broadcast across families) wins;
|
|
523
|
+
// otherwise fall back to the configured per-family `tier.*` settings. Each
|
|
524
|
+
// model resolves its own family's tier below before reaching the wire.
|
|
525
|
+
const flagTier = command.flags.serviceTier ? serviceTierSettingToTier(command.flags.serviceTier) : undefined;
|
|
526
|
+
const serviceTierByFamily = command.flags.serviceTier
|
|
527
|
+
? serviceTierForAllFamilies(flagTier)
|
|
528
|
+
: buildServiceTierByFamily(
|
|
529
|
+
runtime.settings?.get("tier.openai") ?? "none",
|
|
530
|
+
runtime.settings?.get("tier.anthropic") ?? "none",
|
|
531
|
+
runtime.settings?.get("tier.google") ?? "none",
|
|
532
|
+
);
|
|
533
|
+
if (!json && flagTier) writeStdout(`${chalk.dim(`service tier: ${flagTier}`)}\n`);
|
|
496
534
|
const reports: BenchModelReport[] = [];
|
|
497
535
|
for (const { selector, model, thinking } of targets) {
|
|
498
536
|
if (!json) {
|
|
@@ -501,21 +539,29 @@ export async function runBenchCommand(command: BenchCommandArgs, deps: BenchDepe
|
|
|
501
539
|
writeStdout(`${chalk.bold(resolvedModel)}${resolvedNote}\n`);
|
|
502
540
|
}
|
|
503
541
|
const results: BenchRunResult[] = [];
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (!json
|
|
517
|
-
|
|
518
|
-
|
|
542
|
+
|
|
543
|
+
// Preflight check: let's verify credentials before starting any runs.
|
|
544
|
+
// This matches the old sequential break behavior exactly and avoids launching/printing
|
|
545
|
+
// multiple failures.
|
|
546
|
+
const testSessionId = randomSessionId();
|
|
547
|
+
const preflightKey = await runtime.modelRegistry.getApiKey(model, testSessionId);
|
|
548
|
+
if (!preflightKey) {
|
|
549
|
+
const failure: BenchRunFailure = {
|
|
550
|
+
ok: false,
|
|
551
|
+
error: `No credentials for provider "${model.provider}". Run \`omp\` and use /login, or set the provider API key.`,
|
|
552
|
+
};
|
|
553
|
+
results.push(failure);
|
|
554
|
+
if (!json) writeStdout(`${formatRunLine(failure, 0, runs)}\n`);
|
|
555
|
+
reports.push(buildModelReport(selector, model, thinking, results));
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// We will launch up to `par` workers/requests concurrently.
|
|
560
|
+
// To keep output clean, non-JSON output emits entries in correct index order.
|
|
561
|
+
let nextToPrint = 0;
|
|
562
|
+
|
|
563
|
+
const runWorker = async (index: number) => {
|
|
564
|
+
const sessionId = index === 0 ? testSessionId : randomSessionId();
|
|
519
565
|
const result = await runBenchRequest(
|
|
520
566
|
model,
|
|
521
567
|
{
|
|
@@ -525,20 +571,49 @@ export async function runBenchCommand(command: BenchCommandArgs, deps: BenchDepe
|
|
|
525
571
|
maxTokens,
|
|
526
572
|
reasoning: toReasoningEffort(thinking),
|
|
527
573
|
disableReasoning: shouldDisableReasoning(thinking) ? true : undefined,
|
|
574
|
+
serviceTier: resolveModelServiceTier(serviceTierByFamily, model),
|
|
528
575
|
},
|
|
529
576
|
streamFn,
|
|
530
577
|
now,
|
|
531
578
|
);
|
|
532
|
-
results
|
|
579
|
+
results[index] = result;
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// Concurrency-limited running pool
|
|
583
|
+
const queue = Array.from({ length: runs }, (_, i) => i);
|
|
584
|
+
const activeWorkers: Promise<void>[] = [];
|
|
585
|
+
|
|
586
|
+
const processNext = async (): Promise<void> => {
|
|
587
|
+
if (queue.length === 0) return;
|
|
588
|
+
const index = queue.shift()!;
|
|
589
|
+
|
|
590
|
+
// Pre-print a status update if requested and interactive
|
|
591
|
+
if (!json && interactive) {
|
|
592
|
+
writeStdout(chalk.dim(` … run ${index + 1}/${runs} streaming\n`));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
await runWorker(index);
|
|
596
|
+
|
|
597
|
+
// Attempt to print completed results that are in-order
|
|
533
598
|
if (!json) {
|
|
534
|
-
|
|
535
|
-
|
|
599
|
+
while (nextToPrint < runs && results[nextToPrint] !== undefined) {
|
|
600
|
+
const res = results[nextToPrint];
|
|
601
|
+
writeStdout(`${formatRunLine(res, nextToPrint, runs)}\n`);
|
|
602
|
+
nextToPrint++;
|
|
603
|
+
}
|
|
536
604
|
}
|
|
605
|
+
|
|
606
|
+
await processNext();
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
for (let w = 0; w < Math.min(par, runs); w++) {
|
|
610
|
+
activeWorkers.push(processNext());
|
|
537
611
|
}
|
|
612
|
+
await Promise.all(activeWorkers);
|
|
538
613
|
reports.push(buildModelReport(selector, model, thinking, results));
|
|
539
614
|
}
|
|
540
615
|
const failures = reports.reduce((sum, report) => sum + report.results.filter(result => !result.ok).length, 0);
|
|
541
|
-
const summary: BenchSummary = { runs, maxTokens, models: reports, failures };
|
|
616
|
+
const summary: BenchSummary = { runs, maxTokens, models: reports, failures, serviceTierByFamily };
|
|
542
617
|
if (json) {
|
|
543
618
|
writeStdout(`${JSON.stringify(summary, null, 2)}\n`);
|
|
544
619
|
} else if (reports.length > 1 || runs > 1) {
|
|
@@ -28,12 +28,21 @@ interface ProgressReporter {
|
|
|
28
28
|
interface DownloadResult {
|
|
29
29
|
model: TinyLocalModelKey;
|
|
30
30
|
ok: boolean;
|
|
31
|
+
error?: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function writeLine(text = ""): void {
|
|
34
35
|
process.stdout.write(`${text}\n`);
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
function downloadErrorSummary(error: string | undefined): string | undefined {
|
|
39
|
+
return error
|
|
40
|
+
?.split(/\r?\n/)
|
|
41
|
+
.map(line => line.trim())
|
|
42
|
+
.find(line => line.length > 0)
|
|
43
|
+
?.replace(/^Error:\s*/, "");
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
export function resolveModels(model: string | undefined): TinyLocalModelKey[] {
|
|
38
47
|
if (!model) return [DEFAULT_TINY_TITLE_LOCAL_MODEL_KEY];
|
|
39
48
|
// `all` is a prefetch convenience: skip models that fail before load (unsupported
|
|
@@ -101,10 +110,15 @@ async function downloadOne(modelKey: TinyLocalModelKey, json: boolean | undefine
|
|
|
101
110
|
const label = getTinyLocalModelSpec(modelKey)?.label ?? modelKey;
|
|
102
111
|
if (!json && !process.stdout.isTTY) writeLine(`Downloading ${label} (${modelKey})...`);
|
|
103
112
|
const progress = makeProgressReporter(modelKey, json);
|
|
104
|
-
const
|
|
105
|
-
progress.finish(ok);
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
const result = await tinyTitleClient.downloadModel(modelKey, { onProgress: progress.onProgress });
|
|
114
|
+
progress.finish(result.ok);
|
|
115
|
+
const error = downloadErrorSummary(result.error);
|
|
116
|
+
if (!json && !process.stdout.isTTY) {
|
|
117
|
+
writeLine(result.ok ? `Downloaded ${label}.` : `Failed to download ${label}${error ? `: ${error}` : ""}.`);
|
|
118
|
+
} else if (!json && !result.ok && error) {
|
|
119
|
+
writeLine(`${label} failed: ${error}`);
|
|
120
|
+
}
|
|
121
|
+
return result.error ? { model: modelKey, ok: result.ok, error: result.error } : { model: modelKey, ok: result.ok };
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
export async function runTinyModelsCommand(command: TinyModelsCommandArgs): Promise<void> {
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* Handles `omp q`/`omp web-search` subcommands for testing web search providers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import chalk from "chalk";
|
|
9
|
+
import { applyProviderGlobalsFromSettings } from "../config/provider-globals";
|
|
10
|
+
import { Settings } from "../config/settings";
|
|
9
11
|
import { initTheme, theme } from "../modes/theme/theme";
|
|
10
12
|
import { runSearchQuery, type SearchQueryParams } from "../web/search/index";
|
|
11
13
|
import { SEARCH_PROVIDER_ORDER } from "../web/search/provider";
|
|
@@ -85,6 +87,9 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
85
87
|
process.exit(1);
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
const settings = await Settings.init({ cwd: getProjectDir() });
|
|
91
|
+
applyProviderGlobalsFromSettings(settings);
|
|
92
|
+
|
|
88
93
|
await initTheme();
|
|
89
94
|
|
|
90
95
|
const params: SearchQueryParams = {
|