@oh-my-pi/pi-coding-agent 16.4.5 → 16.4.8
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 +54 -0
- package/dist/cli.js +3271 -3224
- package/dist/types/cli/bench-cli.d.ts +1 -7
- package/dist/types/cli/usage-cli.d.ts +1 -0
- package/dist/types/commands/usage.d.ts +7 -0
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +3 -8
- package/dist/types/modes/components/model-browser.d.ts +14 -1
- package/dist/types/modes/components/model-hub.d.ts +4 -3
- package/dist/types/modes/components/plan-review-overlay.d.ts +2 -0
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/controllers/input-controller.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/queue-input.d.ts +8 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/session/agent-storage.d.ts +57 -0
- package/package.json +12 -12
- package/scripts/build-binary.ts +0 -1
- package/scripts/compile-binary.ts +4 -3
- package/src/cli/bench-cli.ts +7 -26
- package/src/cli/usage-cli.ts +11 -0
- package/src/commands/usage.ts +13 -2
- package/src/config/settings-schema.ts +1 -1
- package/src/eval/js/shared/rewrite-imports.ts +31 -13
- package/src/modes/components/advisor-config.ts +3 -1
- package/src/modes/components/custom-editor.test.ts +58 -1
- package/src/modes/components/custom-editor.ts +42 -11
- package/src/modes/components/model-browser.ts +154 -60
- package/src/modes/components/model-hub.ts +475 -122
- package/src/modes/components/plan-review-overlay.ts +7 -0
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/usage-row.ts +5 -6
- package/src/modes/components/welcome.ts +13 -14
- package/src/modes/controllers/input-controller.ts +140 -6
- package/src/modes/controllers/selector-controller.ts +20 -13
- package/src/modes/controllers/todo-command-controller.ts +1 -2
- package/src/modes/interactive-mode.ts +18 -0
- package/src/modes/queue-input.ts +132 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +19 -20
- package/src/session/agent-session.ts +184 -48
- package/src/session/agent-storage.ts +330 -3
- package/src/session/history-storage.ts +1 -34
- package/src/slash-commands/builtin-registry.ts +9 -0
- package/src/web/search/providers/perplexity.ts +18 -2
|
@@ -30,7 +30,7 @@ export interface BenchRunSuccess {
|
|
|
30
30
|
ttftMs: number;
|
|
31
31
|
durationMs: number;
|
|
32
32
|
outputTokens: number;
|
|
33
|
-
/**
|
|
33
|
+
/** Output tokens/sec over the total request duration. */
|
|
34
34
|
tokensPerSecond: number;
|
|
35
35
|
}
|
|
36
36
|
export interface BenchRunFailure {
|
|
@@ -74,12 +74,6 @@ export interface BenchDependencies {
|
|
|
74
74
|
now?: () => number;
|
|
75
75
|
stdoutIsTTY?: boolean;
|
|
76
76
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Tokens/s over the generation window (duration minus TTFT) so queue/prefill
|
|
79
|
-
* latency does not dilute throughput. Falls back to total duration when the
|
|
80
|
-
* response arrived as a single chunk (TTFT ~ duration).
|
|
81
|
-
*/
|
|
82
|
-
export declare function computeTokensPerSecond(outputTokens: number, durationMs: number, ttftMs: number, deltaChunkCount: number): number;
|
|
83
77
|
export declare function formatBenchTable(summary: BenchSummary): string;
|
|
84
78
|
export declare function runBenchCommand(command: BenchCommandArgs, deps?: BenchDependencies): Promise<BenchSummary>;
|
|
85
79
|
export {};
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
import { Command } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
export default class Usage extends Command {
|
|
6
6
|
static description: string;
|
|
7
|
+
static args: {
|
|
8
|
+
action: import("@oh-my-pi/pi-utils/cli").ArgDescriptor & {
|
|
9
|
+
description: string;
|
|
10
|
+
required: false;
|
|
11
|
+
options: string[];
|
|
12
|
+
};
|
|
13
|
+
};
|
|
7
14
|
static flags: {
|
|
8
15
|
json: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
|
|
9
16
|
char: string;
|
|
@@ -1384,7 +1384,7 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
1384
1384
|
readonly tab: "model";
|
|
1385
1385
|
readonly group: "Retry & Fallback";
|
|
1386
1386
|
readonly label: "Retry Fallback Chains";
|
|
1387
|
-
readonly description: 'JSON object mapping model roles to ordered fallback
|
|
1387
|
+
readonly description: 'JSON object mapping model roles, model selectors ("provider/model-id"), or provider wildcards ("provider/*") to ordered fallback selectors, e.g. {"default":["openai/gpt-4o-mini"],"google-antigravity/*":["google/*","google-vertex/*"]}. Model-oriented keys apply whenever that model/provider is active, regardless of role; a "provider/*" entry keeps the failing model\'s id and swaps the provider.';
|
|
1388
1388
|
};
|
|
1389
1389
|
};
|
|
1390
1390
|
readonly "retry.fallbackRevertPolicy": {
|
|
@@ -86,14 +86,9 @@ export declare class CustomEditor extends Editor {
|
|
|
86
86
|
static readonly SHIMMER_FRAME_MS = 70;
|
|
87
87
|
/** Time for the gradient to sweep one full cycle across each keyword. */
|
|
88
88
|
static readonly SHIMMER_PERIOD_MS = 1800;
|
|
89
|
-
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* When the editor is focused, the buffer contains a magic keyword, and `magicKeywords.enabled`
|
|
93
|
-
* is on, the gradient shifts every frame to produce a Claude-Code-style shimmer; each render
|
|
94
|
-
* schedules the next frame, so losing focus, deleting the keyword, or flipping the setting
|
|
95
|
-
* stops the animation on its own. The static glow itself runs even when shimmering is gated
|
|
96
|
-
* off, matching existing behavior for the editor and sent bubbles. */
|
|
89
|
+
/** Decorate magic keywords, attachments, and the queue-composer header/list markers.
|
|
90
|
+
* Queue shorthand reserves its first logical line as a dim `Queueing` label; sequential
|
|
91
|
+
* item markers use the accent color so separate follow-ups remain visible while composing. */
|
|
97
92
|
decorateText: (text: string) => string;
|
|
98
93
|
/** Optional test/host override for the magic-keyword shimmer gate. When
|
|
99
94
|
* defined, takes precedence over the global `magicKeywords.enabled` setting,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { type Component, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import type { Settings } from "../../config/settings.js";
|
|
4
|
+
import type { ModelPerfStats } from "../../session/agent-storage.js";
|
|
4
5
|
import { type ConfiguredThinkingLevel } from "../../thinking.js";
|
|
5
6
|
/** One selectable model row. `selector` is the canonical `provider/id` key. */
|
|
6
7
|
export interface ModelBrowserItem {
|
|
@@ -73,8 +74,12 @@ export declare class ModelBrowser implements Component {
|
|
|
73
74
|
setItems(items: ModelBrowserItem[]): void;
|
|
74
75
|
setRoles(roles: RoleAssignments): void;
|
|
75
76
|
setMruOrder(order: ReadonlyArray<string>): void;
|
|
77
|
+
/** Measured TPS/TTFT averages keyed by `provider/id` selector (see AgentStorage.getModelPerf). */
|
|
78
|
+
setPerfStats(perf: ReadonlyMap<string, ModelPerfStats>): void;
|
|
76
79
|
setMaxVisible(rows: number): void;
|
|
77
80
|
setShowProvider(show: boolean): void;
|
|
81
|
+
/** Focused: accent cursor + selected-row background band. Unfocused: dim cursor, no band. */
|
|
82
|
+
setFocused(focused: boolean): void;
|
|
78
83
|
/** Total rendered height for the current `maxVisible` (host layout budgeting). */
|
|
79
84
|
get renderedRows(): number;
|
|
80
85
|
get query(): string;
|
|
@@ -83,7 +88,13 @@ export declare class ModelBrowser implements Component {
|
|
|
83
88
|
get visibleCount(): number;
|
|
84
89
|
/** Move selection to `selector`; false when it is not in the current view. */
|
|
85
90
|
selectSelector(selector: string): boolean;
|
|
86
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Move the selection by `delta` rows, skipping disabled rows. Single steps
|
|
93
|
+
* wrap at the ends; `wrap: false` (page/home/end jumps) clamps instead.
|
|
94
|
+
*/
|
|
95
|
+
moveSelection(delta: number, options?: {
|
|
96
|
+
wrap?: boolean;
|
|
97
|
+
}): void;
|
|
87
98
|
handleInput(data: string): void;
|
|
88
99
|
/** Cancel-key ladder: clear a non-empty query first, then bubble to the host. */
|
|
89
100
|
handleCancel(): void;
|
|
@@ -92,6 +103,8 @@ export declare class ModelBrowser implements Component {
|
|
|
92
103
|
* row (the search row).
|
|
93
104
|
*/
|
|
94
105
|
routeMouse(event: SgrMouseEvent, line: number): void;
|
|
106
|
+
/** Drop the hover band. Hosts call this when the pointer leaves the browser pane. */
|
|
107
|
+
clearHover(): void;
|
|
95
108
|
render(width: number): string[];
|
|
96
109
|
invalidate(): void;
|
|
97
110
|
}
|
|
@@ -5,17 +5,18 @@ import type { Settings } from "../../config/settings.js";
|
|
|
5
5
|
import { type ConfiguredThinkingLevel } from "../../thinking.js";
|
|
6
6
|
/** `roles` is the full /models hub; `pick` is a one-shot session/embedded picker. */
|
|
7
7
|
export type ModelHubMode = "roles" | "pick";
|
|
8
|
-
export type ModelHubAction = "modelRole" | "retryFallback";
|
|
9
8
|
/** A `--models` scope entry (mirrors the session's scoped model list). */
|
|
10
9
|
export interface ScopedModelItem {
|
|
11
10
|
model: Model;
|
|
12
11
|
thinkingLevel?: string;
|
|
13
12
|
}
|
|
14
13
|
export interface ModelHubCallbacks {
|
|
15
|
-
/** Persist a role assignment
|
|
16
|
-
onAssign: (model: Model, role: string, thinkingLevel: ConfiguredThinkingLevel | undefined, selector: string
|
|
14
|
+
/** Persist a role assignment. */
|
|
15
|
+
onAssign: (model: Model, role: string, thinkingLevel: ConfiguredThinkingLevel | undefined, selector: string) => void;
|
|
17
16
|
/** Clear a configured role back to auto-selection. */
|
|
18
17
|
onUnassign: (role: string) => void;
|
|
18
|
+
/** Persist a `retry.fallbackChains` entry — keyed by a role, `provider/model-id`, or `provider/*`; an empty chain clears the key. */
|
|
19
|
+
onFallbackChainChange?: (role: string, chain: string[]) => void;
|
|
19
20
|
/** Pick-mode activation: session-only switch or embedded pick. */
|
|
20
21
|
onPick?: (model: Model, selector: string) => void;
|
|
21
22
|
/** Locked provider activation: forward to the /login flow. */
|
|
@@ -23,6 +23,8 @@ export interface PlanReviewOverlayCallbacks {
|
|
|
23
23
|
onPick: (label: string) => void;
|
|
24
24
|
/** Invoked on Esc / cancel. */
|
|
25
25
|
onCancel: () => void;
|
|
26
|
+
/** Invoked with the current full plan text when the copy hotkey is pressed. */
|
|
27
|
+
onCopyPlan?: (content: string) => void | Promise<void>;
|
|
26
28
|
/** Invoked when the external-editor key is pressed (overlay stays open). */
|
|
27
29
|
onExternalEditor?: () => void;
|
|
28
30
|
/** Invoked when the external-editor key edits the active annotation draft. */
|
|
@@ -9,6 +9,10 @@ export declare const WELCOME_SESSION_SLOTS = 4;
|
|
|
9
9
|
* the box height is constant regardless of how many servers a project has.
|
|
10
10
|
*/
|
|
11
11
|
export declare const WELCOME_LSP_SLOTS = 4;
|
|
12
|
+
/** Pick a tip from `tips`, biased toward "[NEW]" tips by {@link NEW_TIP_WEIGHT};
|
|
13
|
+
* `r` is a uniform sample in [0, 1). Returns "" when `tips` is empty.
|
|
14
|
+
* Exported for tests. */
|
|
15
|
+
export declare function pickWeightedTip(tips: readonly string[], r: number): string;
|
|
12
16
|
export declare function renderWelcomeTip(tip: string, boxWidth: number, phase?: number): string[];
|
|
13
17
|
export interface RecentSession {
|
|
14
18
|
name: string;
|
|
@@ -33,6 +33,8 @@ export declare class InputController {
|
|
|
33
33
|
handleCtrlZ(): void;
|
|
34
34
|
handleDequeue(): void;
|
|
35
35
|
handleRetry(): Promise<void>;
|
|
36
|
+
/** Queue `/queue` input behind an active turn, or start it immediately when idle. */
|
|
37
|
+
handleQueueCommand(text: string): Promise<void>;
|
|
36
38
|
/** Send editor text as a follow-up message (queued behind current stream). */
|
|
37
39
|
handleFollowUp(): Promise<void>;
|
|
38
40
|
restoreQueuedMessagesToEditor(options?: {
|
|
@@ -365,6 +365,8 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
365
365
|
handleCtrlZ(): void;
|
|
366
366
|
handleDequeue(): void;
|
|
367
367
|
handleImagePaste(): Promise<boolean>;
|
|
368
|
+
/** Queue slash-command input behind the active turn. */
|
|
369
|
+
handleQueueCommand(message: string): Promise<void>;
|
|
368
370
|
handleBtwCommand(question: string): Promise<void>;
|
|
369
371
|
handleTanCommand(work: string): Promise<void>;
|
|
370
372
|
hasActiveBtw(): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Prefix matcher shared by queue-list parsing and editor highlighting. */
|
|
2
|
+
export declare const QUEUE_LIST_MARKER_RE: RegExp;
|
|
3
|
+
/** Extract the message body from the `->` / `=>` yield-queue shorthand. */
|
|
4
|
+
export declare function parseQueueShorthand(text: string): string | undefined;
|
|
5
|
+
/** Whether text currently forms a sequential queue list, including an unfinished trailing item. */
|
|
6
|
+
export declare function isQueuedMessageList(text: string): boolean;
|
|
7
|
+
/** Split a sequential numeric, Roman-numeral, or alphabetic list into queue entries. */
|
|
8
|
+
export declare function splitQueuedMessages(text: string): string[];
|
|
@@ -352,6 +352,8 @@ export interface InteractiveModeContext {
|
|
|
352
352
|
handleCtrlZ(): void;
|
|
353
353
|
handleDequeue(): void;
|
|
354
354
|
handleImagePaste(): Promise<boolean>;
|
|
355
|
+
/** Queue a message for delivery only after the active agent turn would stop. */
|
|
356
|
+
handleQueueCommand(message: string): Promise<void>;
|
|
355
357
|
handleBtwCommand(question: string): Promise<void>;
|
|
356
358
|
handleTanCommand(work: string): Promise<void>;
|
|
357
359
|
hasActiveBtw(): boolean;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { type AuthCredential, type AuthCredentialStore, type StoredAuthCredential } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { RawSettings as Settings } from "../config/settings.js";
|
|
3
|
+
/** One completed request's timing, folded into the per-model aggregates. */
|
|
4
|
+
export interface ModelPerfSample {
|
|
5
|
+
/** Output tokens the provider reported for the turn. */
|
|
6
|
+
outputTokens: number;
|
|
7
|
+
/** Total request duration in milliseconds. */
|
|
8
|
+
durationMs: number;
|
|
9
|
+
/** Time to first token in milliseconds; omit when the provider did not report one. */
|
|
10
|
+
ttftMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Recency-weighted per-model performance averages. */
|
|
13
|
+
export interface ModelPerfStats {
|
|
14
|
+
/** Decayed sample count backing the averages. */
|
|
15
|
+
samples: number;
|
|
16
|
+
/** Average output tokens/sec over the total request duration. */
|
|
17
|
+
tps: number;
|
|
18
|
+
/** Average time-to-first-token in milliseconds; null when no sample reported one. */
|
|
19
|
+
ttftMs: number | null;
|
|
20
|
+
}
|
|
21
|
+
/** Current agent.db schema version; bump when schema changes require migration. */
|
|
22
|
+
export declare const SCHEMA_VERSION = 6;
|
|
3
23
|
/**
|
|
4
24
|
* Unified SQLite storage for agent settings, model usage, and auth credentials.
|
|
5
25
|
* Delegates auth credential operations to AuthCredentialStore from @oh-my-pi/pi-ai.
|
|
@@ -36,6 +56,43 @@ export declare class AgentStorage {
|
|
|
36
56
|
* @returns Array of model keys ("provider/modelId") in MRU order
|
|
37
57
|
*/
|
|
38
58
|
getModelUsageOrder(): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Folds one completed request's timing into the model's perf aggregates.
|
|
61
|
+
* TPS is measured over the total request duration — not the post-TTFT
|
|
62
|
+
* decode window, which undercounts generation time (and so inflates the
|
|
63
|
+
* rate) when reasoning tokens are generated before the first visible
|
|
64
|
+
* token. Invalid samples (no tokens, no duration) are dropped.
|
|
65
|
+
*
|
|
66
|
+
* Deferred like prompt history: samples are batched and written in one
|
|
67
|
+
* transaction after {@link MODEL_PERF_FLUSH_DELAY_MS}, keeping SQLite off
|
|
68
|
+
* the turn-completion hot path. Fire-and-forget safe — flush failures are
|
|
69
|
+
* logged, never thrown; await the returned promise only to observe the flush.
|
|
70
|
+
* @param modelKey - Model key in "provider/modelId" format
|
|
71
|
+
*/
|
|
72
|
+
recordModelPerf(modelKey: string, sample: ModelPerfSample): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Returns recency-weighted TPS/TTFT averages for every model with recorded
|
|
75
|
+
* requests, keyed by "provider/modelId". Read by the /models browser.
|
|
76
|
+
* Also kicks the one-time background stats.db import; until it completes,
|
|
77
|
+
* models without live samples are simply absent.
|
|
78
|
+
*/
|
|
79
|
+
getModelPerf(): Map<string, ModelPerfStats>;
|
|
80
|
+
/**
|
|
81
|
+
* Imports recent measurable request rows from an `omp stats` database
|
|
82
|
+
* (`messages` table) into the model_perf aggregates. Walks newest-first
|
|
83
|
+
* over the timestamp index in {@link MODEL_PERF_BACKFILL_CHUNK}-row chunks,
|
|
84
|
+
* yielding to the event loop between chunks, and keeps at most
|
|
85
|
+
* {@link MODEL_PERF_DECAY_AT} rows per model within the
|
|
86
|
+
* {@link MODEL_PERF_BACKFILL_MAX_AGE_MS} window — beyond either bound the
|
|
87
|
+
* live decay would erase the contribution anyway. Errored turns are
|
|
88
|
+
* excluded; aborted turns with reported usage count, matching live capture.
|
|
89
|
+
* Sums land in one additive transaction at the end, so concurrent live
|
|
90
|
+
* samples merge correctly regardless of order.
|
|
91
|
+
* @param statsDbPath - Path to a stats.db file; opened read-only
|
|
92
|
+
* @returns Number of rows folded in
|
|
93
|
+
* @throws When the stats db cannot be opened or queried
|
|
94
|
+
*/
|
|
95
|
+
backfillModelPerfFromStats(statsDbPath: string): Promise<number>;
|
|
39
96
|
/**
|
|
40
97
|
* Checks if any auth credentials exist in storage.
|
|
41
98
|
* @returns True if at least one credential is stored
|
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.4.
|
|
4
|
+
"version": "16.4.8",
|
|
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",
|
|
@@ -52,17 +52,17 @@
|
|
|
52
52
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
53
53
|
"@babel/parser": "^7.29.7",
|
|
54
54
|
"@mozilla/readability": "^0.6.0",
|
|
55
|
-
"@oh-my-pi/hashline": "16.4.
|
|
56
|
-
"@oh-my-pi/omp-stats": "16.4.
|
|
57
|
-
"@oh-my-pi/pi-agent-core": "16.4.
|
|
58
|
-
"@oh-my-pi/pi-ai": "16.4.
|
|
59
|
-
"@oh-my-pi/pi-catalog": "16.4.
|
|
60
|
-
"@oh-my-pi/pi-mnemopi": "16.4.
|
|
61
|
-
"@oh-my-pi/pi-natives": "16.4.
|
|
62
|
-
"@oh-my-pi/pi-tui": "16.4.
|
|
63
|
-
"@oh-my-pi/pi-utils": "16.4.
|
|
64
|
-
"@oh-my-pi/pi-wire": "16.4.
|
|
65
|
-
"@oh-my-pi/snapcompact": "16.4.
|
|
55
|
+
"@oh-my-pi/hashline": "16.4.8",
|
|
56
|
+
"@oh-my-pi/omp-stats": "16.4.8",
|
|
57
|
+
"@oh-my-pi/pi-agent-core": "16.4.8",
|
|
58
|
+
"@oh-my-pi/pi-ai": "16.4.8",
|
|
59
|
+
"@oh-my-pi/pi-catalog": "16.4.8",
|
|
60
|
+
"@oh-my-pi/pi-mnemopi": "16.4.8",
|
|
61
|
+
"@oh-my-pi/pi-natives": "16.4.8",
|
|
62
|
+
"@oh-my-pi/pi-tui": "16.4.8",
|
|
63
|
+
"@oh-my-pi/pi-utils": "16.4.8",
|
|
64
|
+
"@oh-my-pi/pi-wire": "16.4.8",
|
|
65
|
+
"@oh-my-pi/snapcompact": "16.4.8",
|
|
66
66
|
"@opentelemetry/api": "^1.9.1",
|
|
67
67
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
68
68
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
package/scripts/build-binary.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { buildDocsIndexPayload } from "./generate-docs-index";
|
|
2
2
|
import { createLegacyPiVirtualModulePlugin } from "./legacy-pi-virtual-module";
|
|
3
3
|
|
|
4
|
+
/** Native runtime dependencies always resolved from the on-demand install instead of embedded into compiled binaries. */
|
|
5
|
+
export const COMPILED_EXTERNAL_DEPENDENCIES: readonly string[] = Object.freeze(["fastembed", "onnxruntime-node"]);
|
|
6
|
+
|
|
4
7
|
/** Inputs shared by local and release coding-agent binary builds. */
|
|
5
8
|
export interface CodingAgentCompileOptions {
|
|
6
9
|
/** Absolute repository root used for package resolution. */
|
|
@@ -13,8 +16,6 @@ export interface CodingAgentCompileOptions {
|
|
|
13
16
|
readonly transformersVersion: string;
|
|
14
17
|
/** Optional cross-compilation runtime target. */
|
|
15
18
|
readonly target?: Bun.Build.CompileTarget;
|
|
16
|
-
/** Dependencies intentionally resolved from the runtime filesystem. */
|
|
17
|
-
readonly external?: readonly string[];
|
|
18
19
|
/** Match release builds that minify identifiers while retaining names. */
|
|
19
20
|
readonly minifyIdentifiers?: boolean;
|
|
20
21
|
/** Disable Bun's built-in Darwin signing before the caller re-signs. */
|
|
@@ -34,7 +35,7 @@ export async function compileCodingAgent(options: CodingAgentCompileOptions): Pr
|
|
|
34
35
|
const output = await Bun.build({
|
|
35
36
|
entrypoints: [options.entrypoint],
|
|
36
37
|
root: options.repoRoot,
|
|
37
|
-
external:
|
|
38
|
+
external: [...COMPILED_EXTERNAL_DEPENDENCIES],
|
|
38
39
|
define: {
|
|
39
40
|
"process.env.PI_COMPILED": JSON.stringify("true"),
|
|
40
41
|
"process.env.PI_TINY_TRANSFORMERS_VERSION": JSON.stringify(options.transformersVersion),
|
package/src/cli/bench-cli.ts
CHANGED
|
@@ -74,7 +74,7 @@ export interface BenchRunSuccess {
|
|
|
74
74
|
ttftMs: number;
|
|
75
75
|
durationMs: number;
|
|
76
76
|
outputTokens: number;
|
|
77
|
-
/**
|
|
77
|
+
/** Output tokens/sec over the total request duration. */
|
|
78
78
|
tokensPerSecond: number;
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -181,23 +181,6 @@ function hasVisibleFinalContent(message: AssistantMessage): boolean {
|
|
|
181
181
|
});
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
185
|
-
* Tokens/s over the generation window (duration minus TTFT) so queue/prefill
|
|
186
|
-
* latency does not dilute throughput. Falls back to total duration when the
|
|
187
|
-
* response arrived as a single chunk (TTFT ~ duration).
|
|
188
|
-
*/
|
|
189
|
-
export function computeTokensPerSecond(
|
|
190
|
-
outputTokens: number,
|
|
191
|
-
durationMs: number,
|
|
192
|
-
ttftMs: number,
|
|
193
|
-
deltaChunkCount: number,
|
|
194
|
-
): number {
|
|
195
|
-
const decodeMs = durationMs - ttftMs;
|
|
196
|
-
// Fall back to total duration when the response arrived as a single chunk/non-streaming.
|
|
197
|
-
const windowMs = decodeMs > 0 && deltaChunkCount >= 2 ? decodeMs : durationMs;
|
|
198
|
-
return windowMs > 0 ? (outputTokens * 1000) / windowMs : 0;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
184
|
interface BenchRequestOptions {
|
|
202
185
|
apiKey: ApiKeyResolver;
|
|
203
186
|
sessionId: string;
|
|
@@ -247,17 +230,10 @@ async function runBenchRequest(
|
|
|
247
230
|
headers: model.provider === "openrouter" ? { "X-OpenRouter-Cache": "false" } : undefined,
|
|
248
231
|
});
|
|
249
232
|
let message: AssistantMessage | undefined;
|
|
250
|
-
let deltaChunkCount = 0;
|
|
251
233
|
for await (const event of stream) {
|
|
252
234
|
if (firstTokenAt === undefined && isFirstTokenEvent(event)) {
|
|
253
235
|
firstTokenAt = now();
|
|
254
236
|
}
|
|
255
|
-
if (
|
|
256
|
-
(event.type === "text_delta" || event.type === "thinking_delta" || event.type === "toolcall_delta") &&
|
|
257
|
-
event.delta.length > 0
|
|
258
|
-
) {
|
|
259
|
-
deltaChunkCount++;
|
|
260
|
-
}
|
|
261
237
|
if (event.type === "error") {
|
|
262
238
|
return { ok: false, error: event.error.errorMessage ?? "request failed" };
|
|
263
239
|
}
|
|
@@ -291,7 +267,12 @@ async function runBenchRequest(
|
|
|
291
267
|
ttftMs,
|
|
292
268
|
durationMs,
|
|
293
269
|
outputTokens,
|
|
294
|
-
|
|
270
|
+
// TPS over the TOTAL request duration, deliberately not the post-TTFT
|
|
271
|
+
// decode window: reasoning models can spend seconds generating hidden
|
|
272
|
+
// thinking tokens (counted in usage.output) before the first visible
|
|
273
|
+
// byte, so "duration - TTFT" inflates TPS several-fold on providers
|
|
274
|
+
// that buffer or hide reasoning (e.g. google vs google-vertex).
|
|
275
|
+
tokensPerSecond: durationMs > 0 ? (outputTokens * 1000) / durationMs : 0,
|
|
295
276
|
};
|
|
296
277
|
} catch (error) {
|
|
297
278
|
return { ok: false, error: getErrorMessage(error) };
|
package/src/cli/usage-cli.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { discoverAuthStorage } from "../sdk";
|
|
|
23
23
|
const BAR_WIDTH = 28;
|
|
24
24
|
|
|
25
25
|
export interface UsageCommandArgs {
|
|
26
|
+
action?: string;
|
|
26
27
|
json?: boolean;
|
|
27
28
|
provider?: string;
|
|
28
29
|
redact?: boolean;
|
|
@@ -755,6 +756,16 @@ function redactReportForJson(
|
|
|
755
756
|
export async function runUsageCommand(cmd: UsageCommandArgs): Promise<void> {
|
|
756
757
|
const authStorage = await discoverAuthStorage();
|
|
757
758
|
try {
|
|
759
|
+
if (cmd.action === "invalidate") {
|
|
760
|
+
const provider = cmd.provider?.toLowerCase();
|
|
761
|
+
await authStorage.invalidateUsageCache(provider);
|
|
762
|
+
if (provider) {
|
|
763
|
+
process.stdout.write(`Invalidated cached usage reports for provider "${provider}".\n`);
|
|
764
|
+
} else {
|
|
765
|
+
process.stdout.write("Invalidated cached usage reports for all providers.\n");
|
|
766
|
+
}
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
758
769
|
if (cmd.history) {
|
|
759
770
|
const days = cmd.days !== undefined && Number.isFinite(cmd.days) && cmd.days > 0 ? cmd.days : 7;
|
|
760
771
|
const nowMs = Date.now();
|
package/src/commands/usage.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Show provider usage limits for every authenticated account.
|
|
3
3
|
*/
|
|
4
|
-
import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
4
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { runUsageCommand } from "../cli/usage-cli";
|
|
6
6
|
|
|
7
7
|
export default class Usage extends Command {
|
|
8
8
|
static description = "Show provider usage limits for every authenticated account";
|
|
9
9
|
|
|
10
|
+
static args = {
|
|
11
|
+
action: Args.string({
|
|
12
|
+
description: "Optional subcommand to execute",
|
|
13
|
+
required: false,
|
|
14
|
+
options: ["invalidate"],
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
|
|
10
18
|
static flags = {
|
|
11
19
|
json: Flags.boolean({ char: "j", description: "Output usage reports as JSON", default: false }),
|
|
12
20
|
provider: Flags.string({ char: "p", description: "Only show usage for this provider id (e.g. anthropic)" }),
|
|
@@ -28,11 +36,14 @@ export default class Usage extends Command {
|
|
|
28
36
|
"# Redact account identifiers for screenshots\n omp usage --redact",
|
|
29
37
|
"# Machine-readable output\n omp usage --json",
|
|
30
38
|
"# Usage-limit trend over the last 30 days\n omp usage --history --days 30",
|
|
39
|
+
"# Invalidate cached usage reports for all providers\n omp usage invalidate",
|
|
40
|
+
"# Invalidate cached usage reports for a specific provider\n omp usage invalidate --provider anthropic",
|
|
31
41
|
];
|
|
32
42
|
|
|
33
43
|
async run(): Promise<void> {
|
|
34
|
-
const { flags } = await this.parse(Usage);
|
|
44
|
+
const { args, flags } = await this.parse(Usage);
|
|
35
45
|
await runUsageCommand({
|
|
46
|
+
action: args.action,
|
|
36
47
|
json: flags.json,
|
|
37
48
|
provider: flags.provider,
|
|
38
49
|
redact: flags.redact,
|
|
@@ -1375,7 +1375,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1375
1375
|
group: "Retry & Fallback",
|
|
1376
1376
|
label: "Retry Fallback Chains",
|
|
1377
1377
|
description:
|
|
1378
|
-
'JSON object mapping model roles to ordered fallback
|
|
1378
|
+
'JSON object mapping model roles, model selectors ("provider/model-id"), or provider wildcards ("provider/*") to ordered fallback selectors, e.g. {"default":["openai/gpt-4o-mini"],"google-antigravity/*":["google/*","google-vertex/*"]}. Model-oriented keys apply whenever that model/provider is active, regardless of role; a "provider/*" entry keeps the failing model\'s id and swaps the provider.',
|
|
1379
1379
|
},
|
|
1380
1380
|
},
|
|
1381
1381
|
"retry.fallbackRevertPolicy": {
|
|
@@ -48,6 +48,15 @@ type BabelClassDeclaration = {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
type BabelLexicalDecl = BabelVariableDeclaration | BabelClassDeclaration;
|
|
51
|
+
type BabelFunctionDeclaration = {
|
|
52
|
+
type: "FunctionDeclaration";
|
|
53
|
+
start: number;
|
|
54
|
+
end: number;
|
|
55
|
+
id: { start: number; end: number; name: string } | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Top-level declarations whose bindings must survive the cell (demoted and/or published). */
|
|
59
|
+
type BabelPublishableDecl = BabelLexicalDecl | BabelFunctionDeclaration;
|
|
51
60
|
|
|
52
61
|
type BabelExpressionStatement = {
|
|
53
62
|
type: "ExpressionStatement";
|
|
@@ -322,7 +331,7 @@ function collectBindingNames(pattern: unknown, names: string[]): void {
|
|
|
322
331
|
}
|
|
323
332
|
}
|
|
324
333
|
|
|
325
|
-
function getLexicalBindingNames(node:
|
|
334
|
+
function getLexicalBindingNames(node: BabelPublishableDecl): string[] {
|
|
326
335
|
const names: string[] = [];
|
|
327
336
|
if (node.type === "VariableDeclaration") {
|
|
328
337
|
for (const declaration of node.declarations ?? []) collectBindingNames(declaration.id, names);
|
|
@@ -348,40 +357,49 @@ function appendGlobalBindingPublish(source: string, names: readonly string[]): s
|
|
|
348
357
|
* let { a, b } = obj; -> var { a, b } = obj;
|
|
349
358
|
* class Foo extends Bar {} -> var Foo = class extends Bar {};
|
|
350
359
|
*
|
|
351
|
-
* When the source must run inside the async wrapper, demoted `var`s
|
|
352
|
-
*
|
|
353
|
-
*
|
|
360
|
+
* When the source must run inside the async wrapper (top-level `await`), demoted `var`s —
|
|
361
|
+
* and the user's own top-level `var` and `function` declarations — would be scoped to the
|
|
362
|
+
* wrapper function and die with the cell. In that mode we publish every top-level binding
|
|
363
|
+
* back to the wrapper's lexical `this`, which is the worker global object.
|
|
354
364
|
*
|
|
355
|
-
* Nested declarations (inside functions, blocks, classes) are left alone
|
|
365
|
+
* Nested declarations (inside functions, blocks, classes) are left alone — they're
|
|
356
366
|
* scoped to their enclosing function/block regardless of `var` vs `let`/`const`.
|
|
357
367
|
*/
|
|
358
368
|
async function demoteTopLevelLexicals(code: string, options: { publishGlobals?: boolean } = {}): Promise<string> {
|
|
359
|
-
|
|
369
|
+
const publishGlobals = options.publishGlobals === true;
|
|
370
|
+
const fastPath = publishGlobals ? /\b(?:const|let|class|var|function)\b/ : /\b(?:const|let|class)\b/;
|
|
371
|
+
if (!fastPath.test(code)) return code;
|
|
360
372
|
|
|
361
373
|
const ast = await parseProgram(code);
|
|
362
374
|
if (!ast) {
|
|
363
375
|
return code;
|
|
364
376
|
}
|
|
365
377
|
|
|
366
|
-
const targets:
|
|
378
|
+
const targets: Array<{ node: BabelPublishableDecl; demote: boolean }> = [];
|
|
367
379
|
for (const node of ast.program.body) {
|
|
368
380
|
if (node.type === "VariableDeclaration") {
|
|
369
381
|
const decl = node as unknown as BabelVariableDeclaration;
|
|
370
|
-
if (decl.kind === "const" || decl.kind === "let") targets.push(decl);
|
|
382
|
+
if (decl.kind === "const" || decl.kind === "let") targets.push({ node: decl, demote: true });
|
|
383
|
+
else if (publishGlobals) targets.push({ node: decl, demote: false });
|
|
371
384
|
} else if (node.type === "ClassDeclaration") {
|
|
372
385
|
const decl = node as unknown as BabelClassDeclaration;
|
|
373
|
-
if (decl.id) targets.push(decl);
|
|
386
|
+
if (decl.id) targets.push({ node: decl, demote: true });
|
|
387
|
+
} else if (publishGlobals && node.type === "FunctionDeclaration") {
|
|
388
|
+
const decl = node as unknown as BabelFunctionDeclaration;
|
|
389
|
+
if (decl.id) targets.push({ node: decl, demote: false });
|
|
374
390
|
}
|
|
375
391
|
}
|
|
376
392
|
if (targets.length === 0) return code;
|
|
377
393
|
|
|
378
|
-
targets.sort((a, b) => b.start - a.start);
|
|
394
|
+
targets.sort((a, b) => b.node.start - a.node.start);
|
|
379
395
|
let result = code;
|
|
380
|
-
for (const node of targets) {
|
|
396
|
+
for (const { node, demote } of targets) {
|
|
381
397
|
const segment = result.slice(node.start, node.end);
|
|
382
|
-
const bindingNames =
|
|
398
|
+
const bindingNames = publishGlobals ? getLexicalBindingNames(node) : [];
|
|
383
399
|
let replacement: string;
|
|
384
|
-
if (
|
|
400
|
+
if (!demote) {
|
|
401
|
+
replacement = segment;
|
|
402
|
+
} else if (node.type === "VariableDeclaration") {
|
|
385
403
|
replacement = `var${segment.slice(node.kind.length)}`;
|
|
386
404
|
} else {
|
|
387
405
|
const id = node.id;
|
|
@@ -461,7 +461,8 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
#showModelPicker(index: number): void {
|
|
464
|
-
const
|
|
464
|
+
const storage = this.#settings.getStorage();
|
|
465
|
+
const mruOrder = storage?.getModelUsageOrder() ?? [];
|
|
465
466
|
let models: ReadonlyArray<Model>;
|
|
466
467
|
if (this.#scopedModels.length > 0) {
|
|
467
468
|
models = this.#scopedModels.map(scoped => scoped.model);
|
|
@@ -477,6 +478,7 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
477
478
|
|
|
478
479
|
const picker = new ModelBrowser(this.#settings, {});
|
|
479
480
|
picker.setMruOrder(mruOrder);
|
|
481
|
+
picker.setPerfStats(storage?.getModelPerf() ?? new Map());
|
|
480
482
|
picker.setItems(items);
|
|
481
483
|
picker.onActivate = item => {
|
|
482
484
|
const efforts = getSupportedEfforts(item.model);
|