@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1
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 +78 -0
- package/README.md +1 -1
- package/dist/cli.js +4685 -4614
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +374 -47
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
- package/dist/types/live/attestation.d.ts +2 -0
- package/dist/types/live/controller.d.ts +48 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +35 -0
- package/dist/types/live/visualizer.d.ts +30 -0
- package/dist/types/lsp/client.d.ts +16 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/custom-message.d.ts +1 -1
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/message-frame.d.ts +8 -4
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/session-account-selector.d.ts +11 -0
- package/dist/types/modes/components/settings-defs.d.ts +7 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +321 -0
- package/dist/types/session/agent-session.d.ts +135 -753
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +51 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +24 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +158 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/streaming-output.d.ts +8 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
- package/dist/types/stt/index.d.ts +0 -2
- package/dist/types/stt/stt-controller.d.ts +7 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/tiny/title-client.d.ts +10 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/computer/protocol.d.ts +43 -0
- package/dist/types/tools/computer/supervisor.d.ts +32 -0
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +15 -0
- package/dist/types/tools/computer-renderer.d.ts +22 -0
- package/dist/types/tools/computer.d.ts +71 -0
- package/dist/types/tools/context.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +21 -0
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +7 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +10 -29
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/utils/tools-manager.d.ts +1 -2
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/types.d.ts +94 -0
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +3 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/setup-cli.ts +2 -14
- package/src/cli/usage-cli.ts +10 -1
- package/src/cli.ts +8 -0
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/model-registry.ts +16 -2
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +270 -51
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/executor-base.ts +1 -0
- package/src/eval/js/executor.ts +2 -0
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +90 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/extensions/wrapper.ts +68 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +517 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +422 -0
- package/src/live/visualizer.ts +214 -0
- package/src/lsp/client.ts +29 -9
- package/src/lsp/index.ts +19 -4
- package/src/main.ts +8 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +1 -1
- package/src/modes/components/assistant-message.ts +7 -2
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/message-frame.ts +14 -8
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/settings-defs.ts +23 -1
- package/src/modes/components/settings-selector.ts +169 -1
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.ts +28 -112
- package/src/modes/controllers/command-controller.ts +56 -15
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/input-controller.ts +47 -39
- package/src/modes/controllers/live-command-controller.ts +255 -0
- package/src/modes/controllers/mcp-command-controller.ts +10 -1
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +80 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +176 -38
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +215 -136
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +343 -0
- package/src/session/agent-session.ts +1940 -12218
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/auth-storage.ts +1 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +217 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +266 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.ts +6 -3
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +0 -46
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +980 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +52 -5
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +257 -20
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/usage-report.ts +22 -3
- package/src/stt/downloader.ts +0 -2
- package/src/stt/index.ts +0 -2
- package/src/stt/stt-controller.ts +57 -146
- package/src/system-prompt.ts +45 -17
- package/src/task/executor.ts +285 -70
- package/src/task/index.ts +98 -105
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +1 -0
- package/src/tiny/title-client.ts +22 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +116 -86
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/computer/protocol.ts +28 -0
- package/src/tools/computer/supervisor.ts +258 -0
- package/src/tools/computer/worker-entry.ts +25 -0
- package/src/tools/computer/worker.ts +135 -0
- package/src/tools/computer-renderer.ts +108 -0
- package/src/tools/computer.ts +433 -0
- package/src/tools/context.ts +2 -0
- package/src/tools/default-renderer.ts +139 -0
- package/src/tools/essential-tools.ts +1 -0
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +14 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +9 -1
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +121 -29
- package/src/tts/streaming-player.ts +82 -283
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/clipboard.ts +1 -30
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/utils/tool-choice.ts +14 -0
- package/src/utils/tools-manager.ts +1 -19
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +3 -0
- package/dist/types/stt/recorder.d.ts +0 -30
- package/dist/types/stt/transcriber.d.ts +0 -14
- package/dist/types/stt/wav.d.ts +0 -29
- package/dist/types/tts/player.d.ts +0 -32
- package/src/stt/recorder.ts +0 -551
- package/src/stt/transcriber.ts +0 -60
- package/src/stt/wav.ts +0 -173
- package/src/tts/player.ts +0 -137
package/src/cli/bench-cli.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
import { resolveModelServiceTier, streamSimple } from "@oh-my-pi/pi-ai";
|
|
17
17
|
import { buildModelProviderPriorityRank } from "@oh-my-pi/pi-catalog/identity";
|
|
18
18
|
import { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
19
|
-
import { formatDuration, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
19
|
+
import { formatDuration, getProjectDir, prompt } from "@oh-my-pi/pi-utils";
|
|
20
20
|
import chalk from "chalk";
|
|
21
21
|
import type { ApiKeyResolverModel } from "../config/api-key-resolver";
|
|
22
22
|
import { ModelRegistry } from "../config/model-registry";
|
|
@@ -28,6 +28,9 @@ import {
|
|
|
28
28
|
} from "../config/model-resolver";
|
|
29
29
|
import { buildServiceTierByFamily, serviceTierForAllFamilies, serviceTierSettingToTier } from "../config/service-tier";
|
|
30
30
|
import { Settings } from "../config/settings";
|
|
31
|
+
import cachePrefixTemplate from "../prompts/bench/cache-prefix.md" with { type: "text" };
|
|
32
|
+
import cachePrefixChunk from "../prompts/bench/cache-prefix-chunk.md" with { type: "text" };
|
|
33
|
+
import cacheSuffixTemplate from "../prompts/bench/cache-suffix.md" with { type: "text" };
|
|
31
34
|
import benchPrompt from "../prompts/bench.md" with { type: "text" };
|
|
32
35
|
import { discoverAuthStorage, loadCliExtensionProviders } from "../sdk";
|
|
33
36
|
import {
|
|
@@ -40,8 +43,18 @@ import {
|
|
|
40
43
|
const DEFAULT_RUNS = 10;
|
|
41
44
|
const DEFAULT_PAR = 4;
|
|
42
45
|
const DEFAULT_MAX_TOKENS = 512;
|
|
46
|
+
const DEFAULT_CACHE_MAX_TOKENS = 64;
|
|
47
|
+
const DEFAULT_CACHE_PREFIX_BYTES = 8_192;
|
|
48
|
+
const DEFAULT_CACHE_PAIRS = 1;
|
|
49
|
+
const DEFAULT_CACHE_CONCURRENCY = 1;
|
|
43
50
|
const ERROR_WIDTH = 110;
|
|
44
51
|
const BENCH_PROMPT = benchPrompt.trim();
|
|
52
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
53
|
+
const UTF8_DECODER = new TextDecoder();
|
|
54
|
+
const CACHE_PREFIX_CHUNK = cachePrefixChunk;
|
|
55
|
+
const CACHE_PREFIX_PLACEHOLDER = "__OMP_CACHE_BENCH_RAW_PREFIX__";
|
|
56
|
+
const CACHE_PREFIX_CHUNK_BYTES = UTF8_ENCODER.encode(CACHE_PREFIX_CHUNK).byteLength;
|
|
57
|
+
const RESPONSE_CACHE_STATUS_HEADERS = ["cf-aig-cache-status"] as const;
|
|
45
58
|
|
|
46
59
|
export interface BenchCommandArgs {
|
|
47
60
|
models: string[];
|
|
@@ -53,6 +66,11 @@ export interface BenchCommandArgs {
|
|
|
53
66
|
serviceTier?: string;
|
|
54
67
|
json?: boolean;
|
|
55
68
|
par?: number;
|
|
69
|
+
cache?: boolean;
|
|
70
|
+
cachePrefixFile?: string;
|
|
71
|
+
cachePrefixBytes?: number;
|
|
72
|
+
cachePairs?: number;
|
|
73
|
+
cacheConcurrency?: number;
|
|
56
74
|
};
|
|
57
75
|
}
|
|
58
76
|
|
|
@@ -85,6 +103,44 @@ export interface BenchRunFailure {
|
|
|
85
103
|
|
|
86
104
|
export type BenchRunResult = BenchRunSuccess | BenchRunFailure;
|
|
87
105
|
|
|
106
|
+
export type CacheObservation =
|
|
107
|
+
| "prompt_cache_read_observed"
|
|
108
|
+
| "prompt_cache_write_observed"
|
|
109
|
+
| "response_cache_hit_observed"
|
|
110
|
+
| "no_provider_proof";
|
|
111
|
+
|
|
112
|
+
export interface BenchCacheUsage {
|
|
113
|
+
inputTokens: number;
|
|
114
|
+
outputTokens: number;
|
|
115
|
+
cacheReadTokens: number;
|
|
116
|
+
cacheWriteTokens: number;
|
|
117
|
+
totalTokens: number;
|
|
118
|
+
cost: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface BenchCacheRunReport {
|
|
122
|
+
phase: "cold" | "warm";
|
|
123
|
+
result: BenchRunResult;
|
|
124
|
+
usage?: BenchCacheUsage;
|
|
125
|
+
requestIdObserved: boolean;
|
|
126
|
+
observations: CacheObservation[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface BenchCachePairReport {
|
|
130
|
+
cold: BenchCacheRunReport;
|
|
131
|
+
warm: BenchCacheRunReport;
|
|
132
|
+
/** The nominal cold request showed cache reuse, so it is not a true cold baseline. */
|
|
133
|
+
coldAlreadyWarm: boolean;
|
|
134
|
+
/** Structural-only comparisons: prompt text and cache keys are never emitted. */
|
|
135
|
+
stablePrefix: true;
|
|
136
|
+
suffixChanged: true;
|
|
137
|
+
promptCacheKeyStable: true;
|
|
138
|
+
statefulResponsesDisabled: true;
|
|
139
|
+
freshProviderSessionState: true;
|
|
140
|
+
/** "unavailable" when a transport does not expose the provider payload locally. */
|
|
141
|
+
payloadStructureStable: boolean | "unavailable";
|
|
142
|
+
}
|
|
143
|
+
|
|
88
144
|
export interface BenchAverages {
|
|
89
145
|
ttftMs: number;
|
|
90
146
|
durationMs: number;
|
|
@@ -102,6 +158,7 @@ export interface BenchModelReport {
|
|
|
102
158
|
results: BenchRunResult[];
|
|
103
159
|
/** Averages over successful runs; null when every run failed. */
|
|
104
160
|
average: BenchAverages | null;
|
|
161
|
+
cachePairs?: BenchCachePairReport[];
|
|
105
162
|
}
|
|
106
163
|
|
|
107
164
|
export interface BenchSummary {
|
|
@@ -111,6 +168,10 @@ export interface BenchSummary {
|
|
|
111
168
|
failures: number;
|
|
112
169
|
/** Requested per-family service tiers, resolved per model before reaching the wire. */
|
|
113
170
|
serviceTierByFamily?: ServiceTierByFamily;
|
|
171
|
+
cache?: {
|
|
172
|
+
pairs: number;
|
|
173
|
+
concurrency: number;
|
|
174
|
+
};
|
|
114
175
|
}
|
|
115
176
|
|
|
116
177
|
type BenchStreamSimple = (
|
|
@@ -127,6 +188,7 @@ export interface BenchDependencies {
|
|
|
127
188
|
setExitCode?: (code: number) => void;
|
|
128
189
|
streamSimple?: BenchStreamSimple;
|
|
129
190
|
now?: () => number;
|
|
191
|
+
readTextFile?: (path: string, maxBytes: number) => Promise<string>;
|
|
130
192
|
stdoutIsTTY?: boolean;
|
|
131
193
|
}
|
|
132
194
|
|
|
@@ -184,10 +246,176 @@ function hasVisibleFinalContent(message: AssistantMessage): boolean {
|
|
|
184
246
|
});
|
|
185
247
|
}
|
|
186
248
|
|
|
249
|
+
interface CacheRequestCapture {
|
|
250
|
+
payloadStructure?: string;
|
|
251
|
+
requestIdObserved: boolean;
|
|
252
|
+
responseCacheHit: boolean;
|
|
253
|
+
usage?: BenchCacheUsage;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function payloadStructure(payload: unknown): string {
|
|
257
|
+
if (payload === null) return "null";
|
|
258
|
+
if (Array.isArray(payload)) return `[${payload.map(payloadStructure).join(",")}]`;
|
|
259
|
+
if (typeof payload === "object") {
|
|
260
|
+
const record = payload as Record<string, unknown>;
|
|
261
|
+
return `{${Object.keys(record)
|
|
262
|
+
.sort()
|
|
263
|
+
.map(key => `${key}:${payloadStructure(record[key])}`)
|
|
264
|
+
.join(",")}}`;
|
|
265
|
+
}
|
|
266
|
+
return typeof payload;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function asNonNegativeNumber(value: unknown): number {
|
|
270
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function captureUsage(message: AssistantMessage): BenchCacheUsage {
|
|
274
|
+
const usage = message.usage;
|
|
275
|
+
return {
|
|
276
|
+
inputTokens: asNonNegativeNumber(usage.input),
|
|
277
|
+
outputTokens: asNonNegativeNumber(usage.output),
|
|
278
|
+
cacheReadTokens: asNonNegativeNumber(usage.cacheRead),
|
|
279
|
+
cacheWriteTokens: asNonNegativeNumber(usage.cacheWrite),
|
|
280
|
+
totalTokens: asNonNegativeNumber(usage.totalTokens),
|
|
281
|
+
cost: asNonNegativeNumber(usage.cost?.total),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function cacheObservations(capture: CacheRequestCapture): CacheObservation[] {
|
|
286
|
+
const observations: CacheObservation[] = [];
|
|
287
|
+
if ((capture.usage?.cacheReadTokens ?? 0) > 0) observations.push("prompt_cache_read_observed");
|
|
288
|
+
if ((capture.usage?.cacheWriteTokens ?? 0) > 0) observations.push("prompt_cache_write_observed");
|
|
289
|
+
if (capture.responseCacheHit) observations.push("response_cache_hit_observed");
|
|
290
|
+
return observations.length > 0 ? observations : ["no_provider_proof"];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function cacheRunReport(
|
|
294
|
+
phase: BenchCacheRunReport["phase"],
|
|
295
|
+
result: BenchRunResult,
|
|
296
|
+
capture: CacheRequestCapture,
|
|
297
|
+
): BenchCacheRunReport {
|
|
298
|
+
return {
|
|
299
|
+
phase,
|
|
300
|
+
result,
|
|
301
|
+
usage: capture.usage,
|
|
302
|
+
requestIdObserved: capture.requestIdObserved,
|
|
303
|
+
observations: cacheObservations(capture),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function truncateUtf8ByteLength(bytes: Uint8Array, maxBytes: number): number {
|
|
308
|
+
const end = Math.min(bytes.byteLength, maxBytes);
|
|
309
|
+
if (end === 0) return 0;
|
|
310
|
+
|
|
311
|
+
let sequenceStart = end - 1;
|
|
312
|
+
while (sequenceStart > 0 && (bytes[sequenceStart]! & 0b1100_0000) === 0b1000_0000) sequenceStart--;
|
|
313
|
+
|
|
314
|
+
const leadingByte = bytes[sequenceStart]!;
|
|
315
|
+
const sequenceLength =
|
|
316
|
+
leadingByte <= 0b0111_1111
|
|
317
|
+
? 1
|
|
318
|
+
: leadingByte >= 0b1100_0010 && leadingByte <= 0b1101_1111
|
|
319
|
+
? 2
|
|
320
|
+
: leadingByte >= 0b1110_0000 && leadingByte <= 0b1110_1111
|
|
321
|
+
? 3
|
|
322
|
+
: leadingByte >= 0b1111_0000 && leadingByte <= 0b1111_0100
|
|
323
|
+
? 4
|
|
324
|
+
: 1;
|
|
325
|
+
return sequenceLength > end - sequenceStart ? sequenceStart : end;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function readBoundedUtf8File(path: string, maxBytes: number): Promise<string> {
|
|
329
|
+
const file = Bun.file(path);
|
|
330
|
+
const bytes = new Uint8Array(await file.slice(0, maxBytes).arrayBuffer());
|
|
331
|
+
const end = truncateUtf8ByteLength(bytes, maxBytes);
|
|
332
|
+
return UTF8_DECODER.decode(bytes.subarray(0, end));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function truncateUtf8(text: string, maxBytes: number): string {
|
|
336
|
+
const bytes = UTF8_ENCODER.encode(text);
|
|
337
|
+
const end = truncateUtf8ByteLength(bytes, maxBytes);
|
|
338
|
+
return end === bytes.byteLength ? text : UTF8_DECODER.decode(bytes.subarray(0, end));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function generatedCachePrefix(bytes: number): string {
|
|
342
|
+
return truncateUtf8(CACHE_PREFIX_CHUNK.repeat(Math.ceil(bytes / CACHE_PREFIX_CHUNK_BYTES)), bytes);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function renderCacheBenchmarkPrefix(prefix: string, namespace: string): string {
|
|
346
|
+
const rendered = prompt.render(cachePrefixTemplate, {
|
|
347
|
+
prefix: CACHE_PREFIX_PLACEHOLDER,
|
|
348
|
+
namespace,
|
|
349
|
+
});
|
|
350
|
+
if (!rendered.includes(CACHE_PREFIX_PLACEHOLDER)) {
|
|
351
|
+
throw new Error("Cache benchmark prefix template is missing its raw prefix placeholder");
|
|
352
|
+
}
|
|
353
|
+
// Render the static wrapper first, then inject caller bytes so prompt
|
|
354
|
+
// normalization cannot trim spaces or collapse blank lines in prefix files.
|
|
355
|
+
// Function replacer: a prefix containing `$&`/`$'`/`` $` `` must not be
|
|
356
|
+
// expanded as a string-replacement pattern.
|
|
357
|
+
return rendered.replace(CACHE_PREFIX_PLACEHOLDER, () => prefix);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function resolveCachePrefix(
|
|
361
|
+
flags: BenchCommandArgs["flags"],
|
|
362
|
+
readTextFile: (path: string, maxBytes: number) => Promise<string>,
|
|
363
|
+
): Promise<string> {
|
|
364
|
+
const bytes = normalizePositiveInteger("cache-prefix-bytes", flags.cachePrefixBytes, DEFAULT_CACHE_PREFIX_BYTES);
|
|
365
|
+
const prefix = flags.cachePrefixFile
|
|
366
|
+
? await readTextFile(flags.cachePrefixFile, bytes)
|
|
367
|
+
: generatedCachePrefix(bytes);
|
|
368
|
+
return truncateUtf8(prefix, bytes);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function cacheBenchmarkMessages(stablePrefix: string, suffix: string): Context["messages"] {
|
|
372
|
+
const timestamp = Date.now();
|
|
373
|
+
return [
|
|
374
|
+
{ role: "user", content: stablePrefix, timestamp, attribution: "user" },
|
|
375
|
+
{ role: "user", content: suffix, timestamp, attribution: "user" },
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function runWithConcurrency<T>(
|
|
380
|
+
count: number,
|
|
381
|
+
concurrency: number,
|
|
382
|
+
run: (index: number) => Promise<T>,
|
|
383
|
+
): Promise<T[]> {
|
|
384
|
+
const results = new Array<T>(count);
|
|
385
|
+
let next = 0;
|
|
386
|
+
const worker = async (): Promise<void> => {
|
|
387
|
+
while (next < count) {
|
|
388
|
+
const index = next++;
|
|
389
|
+
results[index] = await run(index);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
await Promise.all(Array.from({ length: Math.min(count, concurrency) }, worker));
|
|
393
|
+
return results;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function formatCacheCost(cost: number): string {
|
|
397
|
+
if (cost < 0.01) return `$${cost.toFixed(4)}`;
|
|
398
|
+
if (cost < 1) return `$${cost.toFixed(3)}`;
|
|
399
|
+
return `$${cost.toFixed(2)}`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function formatCachePairLine(pair: BenchCachePairReport, index: number, total: number): string {
|
|
403
|
+
const formatPhase = (run: BenchCacheRunReport, alreadyWarm = false) => {
|
|
404
|
+
if (!run.result.ok) {
|
|
405
|
+
return `${run.phase} failed: ${truncateToWidth(replaceTabs(run.result.error), ERROR_WIDTH)}`;
|
|
406
|
+
}
|
|
407
|
+
const usage = run.usage;
|
|
408
|
+
return `${run.phase}${alreadyWarm ? " (already warm)" : ""} ${run.observations.join(", ")} ${chalk.dim("input")} ${usage?.inputTokens ?? 0} ${chalk.dim("cache-read")} ${usage?.cacheReadTokens ?? 0} ${chalk.dim("cache-write")} ${usage?.cacheWriteTokens ?? 0} ${chalk.dim("output")} ${usage?.outputTokens ?? run.result.outputTokens} ${chalk.dim("total")} ${usage?.totalTokens ?? 0} ${chalk.dim("cost")} ${formatCacheCost(usage?.cost ?? 0)} ${chalk.dim("TTFT")} ${formatMs(run.result.ttftMs)} ${chalk.dim("duration")} ${formatMs(run.result.durationMs)} ${chalk.dim("throughput")} ${run.result.tokensPerSecond.toFixed(1)}/s`;
|
|
409
|
+
};
|
|
410
|
+
return ` ${chalk.dim(`pair ${index + 1}/${total}`)} ${formatPhase(pair.cold, pair.coldAlreadyWarm)}; ${formatPhase(pair.warm)}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
187
413
|
interface BenchRequestOptions {
|
|
188
414
|
apiKey: ApiKeyResolver;
|
|
189
415
|
sessionId: string;
|
|
190
416
|
prompt: string;
|
|
417
|
+
/** Native OMP messages; cache mode splits the stable prefix from the suffix. */
|
|
418
|
+
contextMessages?: Context["messages"];
|
|
191
419
|
maxTokens: number;
|
|
192
420
|
/** Explicit effort from a `:level` selector suffix; absent = provider default. */
|
|
193
421
|
reasoning?: Effort;
|
|
@@ -195,6 +423,9 @@ interface BenchRequestOptions {
|
|
|
195
423
|
disableReasoning?: boolean;
|
|
196
424
|
/** Requested service tier passed to `streamSimple`; absent omits the option. The provider layer applies scope/support gating before it reaches the wire. */
|
|
197
425
|
serviceTier?: ServiceTier;
|
|
426
|
+
promptCacheKey?: string;
|
|
427
|
+
statefulResponses?: false;
|
|
428
|
+
cacheCapture?: CacheRequestCapture;
|
|
198
429
|
}
|
|
199
430
|
|
|
200
431
|
async function runBenchRequest(
|
|
@@ -211,7 +442,9 @@ async function runBenchRequest(
|
|
|
211
442
|
// Codex's Responses endpoint 400s with "Instructions are required" when no
|
|
212
443
|
// system prompt is present — same guard as eval's completion bridge.
|
|
213
444
|
systemPrompt: ["You are a helpful assistant."],
|
|
214
|
-
messages:
|
|
445
|
+
messages: options.contextMessages ?? [
|
|
446
|
+
{ role: "user", content: options.prompt, timestamp: Date.now(), attribution: "user" },
|
|
447
|
+
],
|
|
215
448
|
};
|
|
216
449
|
const stream = streamFn(model, context, {
|
|
217
450
|
apiKey: options.apiKey,
|
|
@@ -221,6 +454,22 @@ async function runBenchRequest(
|
|
|
221
454
|
? Math.min(options.maxTokens, model.maxTokens)
|
|
222
455
|
: options.maxTokens,
|
|
223
456
|
reasoning: options.reasoning,
|
|
457
|
+
promptCacheKey: options.promptCacheKey,
|
|
458
|
+
statefulResponses: options.statefulResponses,
|
|
459
|
+
onPayload: options.cacheCapture
|
|
460
|
+
? payload => {
|
|
461
|
+
options.cacheCapture!.payloadStructure = payloadStructure(payload);
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
: undefined,
|
|
465
|
+
onResponse: options.cacheCapture
|
|
466
|
+
? response => {
|
|
467
|
+
options.cacheCapture!.requestIdObserved = Boolean(response.requestId);
|
|
468
|
+
options.cacheCapture!.responseCacheHit = RESPONSE_CACHE_STATUS_HEADERS.some(
|
|
469
|
+
header => response.headers[header]?.trim().toLowerCase() === "hit",
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
: undefined,
|
|
224
473
|
disableReasoning: options.disableReasoning,
|
|
225
474
|
serviceTier: options.serviceTier,
|
|
226
475
|
providerSessionState,
|
|
@@ -265,6 +514,7 @@ async function runBenchRequest(
|
|
|
265
514
|
error: `provider returned no output (0 tokens, empty stream; stop reason: ${message.stopReason ?? "unknown"})`,
|
|
266
515
|
};
|
|
267
516
|
}
|
|
517
|
+
if (options.cacheCapture) options.cacheCapture.usage = captureUsage(message);
|
|
268
518
|
return {
|
|
269
519
|
ok: true,
|
|
270
520
|
ttftMs,
|
|
@@ -480,14 +730,48 @@ function resolveBenchModels(
|
|
|
480
730
|
}
|
|
481
731
|
return resolved;
|
|
482
732
|
}
|
|
733
|
+
function assertCacheModeSupported(targets: BenchTarget[]): void {
|
|
734
|
+
if (targets.some(({ model }) => model.api === "openai-codex-responses")) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
"--cache is not supported for openai-codex-responses because Codex WebSocket chaining cannot produce independent prompt-cache pairs",
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
483
741
|
export async function runBenchCommand(command: BenchCommandArgs, deps: BenchDependencies = {}): Promise<BenchSummary> {
|
|
484
|
-
const
|
|
485
|
-
const
|
|
742
|
+
const cacheMode = command.flags.cache === true;
|
|
743
|
+
const cacheFlagsUsed =
|
|
744
|
+
command.flags.cachePrefixFile !== undefined ||
|
|
745
|
+
command.flags.cachePrefixBytes !== undefined ||
|
|
746
|
+
command.flags.cachePairs !== undefined ||
|
|
747
|
+
command.flags.cacheConcurrency !== undefined;
|
|
748
|
+
if (!cacheMode && cacheFlagsUsed) throw new Error("Cache flags require --cache");
|
|
749
|
+
if (cacheMode && command.flags.runs !== undefined)
|
|
750
|
+
throw new Error("Use --cache-pairs instead of --runs with --cache");
|
|
751
|
+
if (cacheMode && command.flags.prompt !== undefined) throw new Error("--cache builds its own stable-prefix prompts");
|
|
752
|
+
if (cacheMode && (command.flags.par ?? 1) > 1) {
|
|
753
|
+
throw new Error("--par cannot parallelize cold/warm pairs; use --cache-concurrency instead");
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const cachePairs = cacheMode
|
|
757
|
+
? normalizePositiveInteger("cache-pairs", command.flags.cachePairs, DEFAULT_CACHE_PAIRS)
|
|
758
|
+
: undefined;
|
|
759
|
+
const cacheConcurrency = cacheMode
|
|
760
|
+
? normalizePositiveInteger("cache-concurrency", command.flags.cacheConcurrency, DEFAULT_CACHE_CONCURRENCY)
|
|
761
|
+
: undefined;
|
|
762
|
+
const runs = cacheMode ? cachePairs! * 2 : normalizePositiveInteger("runs", command.flags.runs, DEFAULT_RUNS);
|
|
763
|
+
const maxTokens = normalizePositiveInteger(
|
|
764
|
+
"max-tokens",
|
|
765
|
+
command.flags.maxTokens,
|
|
766
|
+
cacheMode ? DEFAULT_CACHE_MAX_TOKENS : DEFAULT_MAX_TOKENS,
|
|
767
|
+
);
|
|
486
768
|
const par =
|
|
487
769
|
command.flags.par !== undefined ? normalizePositiveInteger("par", command.flags.par, DEFAULT_PAR) : DEFAULT_PAR;
|
|
488
|
-
const
|
|
770
|
+
const benchmarkPrompt = command.flags.prompt?.trim() || BENCH_PROMPT;
|
|
489
771
|
const json = command.flags.json === true;
|
|
490
772
|
const randomSessionId = deps.randomSessionId ?? (() => Bun.randomUUIDv7());
|
|
773
|
+
const readTextFile = deps.readTextFile ?? readBoundedUtf8File;
|
|
774
|
+
const cachePrefix = cacheMode ? await resolveCachePrefix(command.flags, readTextFile) : undefined;
|
|
491
775
|
const writeStdout = deps.writeStdout ?? ((text: string) => process.stdout.write(text));
|
|
492
776
|
const writeStderr = deps.writeStderr ?? ((text: string) => process.stderr.write(text));
|
|
493
777
|
const setExitCode =
|
|
@@ -505,6 +789,7 @@ export async function runBenchCommand(command: BenchCommandArgs, deps: BenchDepe
|
|
|
505
789
|
const runtime = await (deps.createRuntime ?? createDefaultRuntime)();
|
|
506
790
|
try {
|
|
507
791
|
const targets = resolveBenchModels(command.models, runtime.modelRegistry, runtime.settings, writeStderr);
|
|
792
|
+
if (cacheMode) assertCacheModeSupported(targets);
|
|
508
793
|
// Explicit `--service-tier` (a single value broadcast across families) wins;
|
|
509
794
|
// otherwise fall back to the configured per-family `tier.*` settings. Each
|
|
510
795
|
// model resolves its own family's tier below before reaching the wire.
|
|
@@ -538,14 +823,103 @@ export async function runBenchCommand(command: BenchCommandArgs, deps: BenchDepe
|
|
|
538
823
|
};
|
|
539
824
|
results.push(failure);
|
|
540
825
|
if (!json) writeStdout(`${formatRunLine(failure, 0, runs)}\n`);
|
|
541
|
-
|
|
826
|
+
const report = buildModelReport(selector, model, thinking, results);
|
|
827
|
+
if (cacheMode) report.cachePairs = [];
|
|
828
|
+
reports.push(report);
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const serviceTier = resolveModelServiceTier(serviceTierByFamily, model);
|
|
833
|
+
if (cacheMode) {
|
|
834
|
+
const pairs = await runWithConcurrency(
|
|
835
|
+
cachePairs!,
|
|
836
|
+
cacheConcurrency!,
|
|
837
|
+
async (pairIndex): Promise<BenchCachePairReport> => {
|
|
838
|
+
const cacheNamespace = randomSessionId();
|
|
839
|
+
const promptCacheKey = `bench-cache:${cacheNamespace}`;
|
|
840
|
+
const stablePrefix = renderCacheBenchmarkPrefix(cachePrefix!, cacheNamespace);
|
|
841
|
+
const coldSuffix = prompt.render(cacheSuffixTemplate, { variant: "A" }).trim();
|
|
842
|
+
const warmSuffix = prompt.render(cacheSuffixTemplate, { variant: "B" }).trim();
|
|
843
|
+
const coldCapture: CacheRequestCapture = {
|
|
844
|
+
requestIdObserved: false,
|
|
845
|
+
responseCacheHit: false,
|
|
846
|
+
};
|
|
847
|
+
// Keep the gateway's credential selection stable for both phases.
|
|
848
|
+
// Provider-session state is still recreated by runBenchRequest and
|
|
849
|
+
// stateful Responses chaining is disabled below.
|
|
850
|
+
const credentialAffinitySessionId = pairIndex === 0 ? testSessionId : randomSessionId();
|
|
851
|
+
const credentialResolver = runtime.modelRegistry.resolver(model, credentialAffinitySessionId);
|
|
852
|
+
const coldResult = await runBenchRequest(
|
|
853
|
+
model,
|
|
854
|
+
{
|
|
855
|
+
apiKey: credentialResolver,
|
|
856
|
+
sessionId: credentialAffinitySessionId,
|
|
857
|
+
prompt: coldSuffix,
|
|
858
|
+
contextMessages: cacheBenchmarkMessages(stablePrefix, coldSuffix),
|
|
859
|
+
maxTokens,
|
|
860
|
+
reasoning: toReasoningEffort(thinking),
|
|
861
|
+
disableReasoning: shouldDisableReasoning(thinking) ? true : undefined,
|
|
862
|
+
serviceTier,
|
|
863
|
+
promptCacheKey,
|
|
864
|
+
statefulResponses: false,
|
|
865
|
+
cacheCapture: coldCapture,
|
|
866
|
+
},
|
|
867
|
+
streamFn,
|
|
868
|
+
now,
|
|
869
|
+
);
|
|
870
|
+
const warmCapture: CacheRequestCapture = {
|
|
871
|
+
requestIdObserved: false,
|
|
872
|
+
responseCacheHit: false,
|
|
873
|
+
};
|
|
874
|
+
const warmResult = await runBenchRequest(
|
|
875
|
+
model,
|
|
876
|
+
{
|
|
877
|
+
apiKey: credentialResolver,
|
|
878
|
+
sessionId: credentialAffinitySessionId,
|
|
879
|
+
prompt: warmSuffix,
|
|
880
|
+
contextMessages: cacheBenchmarkMessages(stablePrefix, warmSuffix),
|
|
881
|
+
maxTokens,
|
|
882
|
+
reasoning: toReasoningEffort(thinking),
|
|
883
|
+
disableReasoning: shouldDisableReasoning(thinking) ? true : undefined,
|
|
884
|
+
serviceTier,
|
|
885
|
+
promptCacheKey,
|
|
886
|
+
statefulResponses: false,
|
|
887
|
+
cacheCapture: warmCapture,
|
|
888
|
+
},
|
|
889
|
+
streamFn,
|
|
890
|
+
now,
|
|
891
|
+
);
|
|
892
|
+
return {
|
|
893
|
+
cold: cacheRunReport("cold", coldResult, coldCapture),
|
|
894
|
+
warm: cacheRunReport("warm", warmResult, warmCapture),
|
|
895
|
+
coldAlreadyWarm: (coldCapture.usage?.cacheReadTokens ?? 0) > 0 || coldCapture.responseCacheHit,
|
|
896
|
+
stablePrefix: true,
|
|
897
|
+
suffixChanged: true,
|
|
898
|
+
promptCacheKeyStable: true,
|
|
899
|
+
statefulResponsesDisabled: true,
|
|
900
|
+
freshProviderSessionState: true,
|
|
901
|
+
payloadStructureStable:
|
|
902
|
+
coldCapture.payloadStructure === undefined || warmCapture.payloadStructure === undefined
|
|
903
|
+
? "unavailable"
|
|
904
|
+
: coldCapture.payloadStructure === warmCapture.payloadStructure,
|
|
905
|
+
};
|
|
906
|
+
},
|
|
907
|
+
);
|
|
908
|
+
for (const pair of pairs) results.push(pair.cold.result, pair.warm.result);
|
|
909
|
+
const report = buildModelReport(selector, model, thinking, results);
|
|
910
|
+
report.cachePairs = pairs;
|
|
911
|
+
reports.push(report);
|
|
912
|
+
if (!json) {
|
|
913
|
+
for (const [index, pair] of pairs.entries()) {
|
|
914
|
+
writeStdout(`${formatCachePairLine(pair, index, pairs.length)}\n`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
542
917
|
continue;
|
|
543
918
|
}
|
|
544
919
|
|
|
545
920
|
// We will launch up to `par` workers/requests concurrently.
|
|
546
921
|
// To keep output clean, non-JSON output emits entries in correct index order.
|
|
547
922
|
let nextToPrint = 0;
|
|
548
|
-
|
|
549
923
|
const runWorker = async (index: number) => {
|
|
550
924
|
const sessionId = index === 0 ? testSessionId : randomSessionId();
|
|
551
925
|
const result = await runBenchRequest(
|
|
@@ -553,56 +927,48 @@ export async function runBenchCommand(command: BenchCommandArgs, deps: BenchDepe
|
|
|
553
927
|
{
|
|
554
928
|
apiKey: runtime.modelRegistry.resolver(model, sessionId),
|
|
555
929
|
sessionId,
|
|
556
|
-
prompt,
|
|
930
|
+
prompt: benchmarkPrompt,
|
|
557
931
|
maxTokens,
|
|
558
932
|
reasoning: toReasoningEffort(thinking),
|
|
559
933
|
disableReasoning: shouldDisableReasoning(thinking) ? true : undefined,
|
|
560
|
-
serviceTier
|
|
934
|
+
serviceTier,
|
|
561
935
|
},
|
|
562
936
|
streamFn,
|
|
563
937
|
now,
|
|
564
938
|
);
|
|
565
939
|
results[index] = result;
|
|
566
940
|
};
|
|
567
|
-
|
|
568
|
-
// Concurrency-limited running pool
|
|
569
941
|
const queue = Array.from({ length: runs }, (_, i) => i);
|
|
570
942
|
const activeWorkers: Promise<void>[] = [];
|
|
571
|
-
|
|
572
943
|
const processNext = async (): Promise<void> => {
|
|
573
944
|
if (queue.length === 0) return;
|
|
574
945
|
const index = queue.shift()!;
|
|
575
|
-
|
|
576
|
-
// Pre-print a status update if requested and interactive
|
|
577
|
-
if (!json && interactive) {
|
|
578
|
-
writeStdout(chalk.dim(` … run ${index + 1}/${runs} streaming\n`));
|
|
579
|
-
}
|
|
580
|
-
|
|
946
|
+
if (!json && interactive) writeStdout(chalk.dim(` … run ${index + 1}/${runs} streaming\n`));
|
|
581
947
|
await runWorker(index);
|
|
582
|
-
|
|
583
|
-
// Attempt to print completed results that are in-order
|
|
584
948
|
if (!json) {
|
|
585
949
|
while (nextToPrint < runs && results[nextToPrint] !== undefined) {
|
|
586
|
-
|
|
587
|
-
writeStdout(`${formatRunLine(res, nextToPrint, runs)}\n`);
|
|
950
|
+
writeStdout(`${formatRunLine(results[nextToPrint], nextToPrint, runs)}\n`);
|
|
588
951
|
nextToPrint++;
|
|
589
952
|
}
|
|
590
953
|
}
|
|
591
|
-
|
|
592
954
|
await processNext();
|
|
593
955
|
};
|
|
594
|
-
|
|
595
|
-
for (let w = 0; w < Math.min(par, runs); w++) {
|
|
596
|
-
activeWorkers.push(processNext());
|
|
597
|
-
}
|
|
956
|
+
for (let worker = 0; worker < Math.min(par, runs); worker++) activeWorkers.push(processNext());
|
|
598
957
|
await Promise.all(activeWorkers);
|
|
599
958
|
reports.push(buildModelReport(selector, model, thinking, results));
|
|
600
959
|
}
|
|
601
960
|
const failures = reports.reduce((sum, report) => sum + report.results.filter(result => !result.ok).length, 0);
|
|
602
|
-
const summary: BenchSummary = {
|
|
961
|
+
const summary: BenchSummary = {
|
|
962
|
+
runs,
|
|
963
|
+
maxTokens,
|
|
964
|
+
models: reports,
|
|
965
|
+
failures,
|
|
966
|
+
serviceTierByFamily,
|
|
967
|
+
...(cacheMode ? { cache: { pairs: cachePairs!, concurrency: cacheConcurrency! } } : {}),
|
|
968
|
+
};
|
|
603
969
|
if (json) {
|
|
604
970
|
writeStdout(`${JSON.stringify(summary, null, 2)}\n`);
|
|
605
|
-
} else if (reports.length > 1 || runs > 1) {
|
|
971
|
+
} else if (!cacheMode && (reports.length > 1 || runs > 1)) {
|
|
606
972
|
writeStdout(`\n${formatBenchTable(summary)}`);
|
|
607
973
|
}
|
|
608
974
|
if (failures > 0) setExitCode(1);
|
package/src/cli/flag-tables.ts
CHANGED
|
@@ -117,6 +117,9 @@ export const STRING_SETTERS: Record<string, StringSetter> = {
|
|
|
117
117
|
"--config": (result, value) => {
|
|
118
118
|
result.config = [...(result.config ?? []), value];
|
|
119
119
|
},
|
|
120
|
+
"--add-dir": (result, value) => {
|
|
121
|
+
result.addDir = [...(result.addDir ?? []), value];
|
|
122
|
+
},
|
|
120
123
|
"--mode": (result, value) => {
|
|
121
124
|
if (value === "text" || value === "json" || value === "rpc" || value === "acp" || value === "rpc-ui") {
|
|
122
125
|
result.mode = value;
|
package/src/cli/gc-cli.ts
CHANGED
|
@@ -5,10 +5,10 @@ import { gunzipSync, gzipSync } from "node:zlib";
|
|
|
5
5
|
import { getAgentDir, getBlobsDir, getHistoryDbPath, getModelDbPath, getSessionsDir } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import { Settings } from "../config/settings";
|
|
7
7
|
import { getDefault } from "../config/settings-schema";
|
|
8
|
+
import { BLOB_HASH_RE } from "../session/blob-store";
|
|
8
9
|
import { listSessionsReadOnly, type SessionInfo, type SessionStatus } from "../session/session-listing";
|
|
9
10
|
import { FileSessionStorage } from "../session/session-storage";
|
|
10
11
|
|
|
11
|
-
const HASH_RE = /^[a-f0-9]{64}$/;
|
|
12
12
|
const BLOB_FILE_RE = /^([a-f0-9]{64})(?:\.[A-Za-z0-9][A-Za-z0-9._-]{0,31})?$/;
|
|
13
13
|
const BLOB_REF_RE = /\bblob:sha256:([a-f0-9]{64})\b/gi;
|
|
14
14
|
const JSONL_GLOB = new Bun.Glob("**/*.jsonl");
|
|
@@ -268,7 +268,7 @@ async function collectReferencedBlobHashes(sessionRoots: string[]): Promise<Set<
|
|
|
268
268
|
const text = await readTextIfPresent(file);
|
|
269
269
|
for (const match of text.matchAll(BLOB_REF_RE)) {
|
|
270
270
|
const hash = match[1]?.toLowerCase();
|
|
271
|
-
if (hash &&
|
|
271
|
+
if (hash && BLOB_HASH_RE.test(hash)) hashes.add(hash);
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
}
|
package/src/cli/setup-cli.ts
CHANGED
|
@@ -11,7 +11,6 @@ import { Settings, settings } from "../config/settings";
|
|
|
11
11
|
import { theme } from "../modes/theme/theme";
|
|
12
12
|
import { downloadSttModel, isSttModelCached } from "../stt/downloader";
|
|
13
13
|
import { isSttModelKey, STT_MODEL_OPTIONS } from "../stt/models";
|
|
14
|
-
import { detectRecorder, ensureRecorder } from "../stt/recorder";
|
|
15
14
|
import { downloadTtsModel, isTtsLocalModelKey, isTtsModelCached, TTS_LOCAL_MODEL_OPTIONS } from "../tts";
|
|
16
15
|
import { selectSetupModel } from "./setup-model-picker";
|
|
17
16
|
|
|
@@ -170,17 +169,6 @@ interface SpeechComponent {
|
|
|
170
169
|
|
|
171
170
|
function buildSpeechComponents(): SpeechComponent[] {
|
|
172
171
|
return [
|
|
173
|
-
{
|
|
174
|
-
name: "Recorder",
|
|
175
|
-
isReady: async () => detectRecorder() !== null,
|
|
176
|
-
status: async () => {
|
|
177
|
-
const recorder = detectRecorder();
|
|
178
|
-
return recorder ? `${recorder.tool} (${recorder.bin})` : "none — ffmpeg will be downloaded";
|
|
179
|
-
},
|
|
180
|
-
ensure: async onProgress => {
|
|
181
|
-
await ensureRecorder(onProgress);
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
172
|
{
|
|
185
173
|
name: "Speech-to-Text model",
|
|
186
174
|
isReady: () => isSttModelCached(settings.get("stt.modelName")),
|
|
@@ -316,7 +304,7 @@ ${chalk.bold("Usage:")}
|
|
|
316
304
|
|
|
317
305
|
${chalk.bold("Components:")}
|
|
318
306
|
python Verify a Python 3 interpreter is reachable for code execution
|
|
319
|
-
speech Pick
|
|
307
|
+
speech Pick and download speech-to-text and text-to-speech models
|
|
320
308
|
|
|
321
309
|
${chalk.bold("Options:")}
|
|
322
310
|
-c, --check Check if dependencies are installed without installing
|
|
@@ -325,7 +313,7 @@ ${chalk.bold("Options:")}
|
|
|
325
313
|
${chalk.bold("Examples:")}
|
|
326
314
|
${APP_NAME} setup Run the onboarding wizard
|
|
327
315
|
${APP_NAME} setup python Check Python execution dependencies
|
|
328
|
-
${APP_NAME} setup speech
|
|
316
|
+
${APP_NAME} setup speech Pick and download the STT and TTS models
|
|
329
317
|
${APP_NAME} setup speech --check Check if speech dependencies are available
|
|
330
318
|
${APP_NAME} setup python --check Check if Python execution is available
|
|
331
319
|
`);
|
package/src/cli/usage-cli.ts
CHANGED
|
@@ -208,14 +208,23 @@ function describeAmount(limit: UsageLimit): string {
|
|
|
208
208
|
const amount = limit.amount;
|
|
209
209
|
const parts: string[] = [];
|
|
210
210
|
const absoluteUnit = amount.unit !== "percent" && amount.unit !== "unknown";
|
|
211
|
+
const fraction = resolveUsedFraction(limit);
|
|
211
212
|
if (absoluteUnit && amount.used !== undefined && amount.limit !== undefined) {
|
|
212
213
|
parts.push(
|
|
213
214
|
`${formatUnitValue(amount.used, amount.unit)} / ${formatUnitValue(amount.limit, amount.unit)}${UNIT_SUFFIX[amount.unit]}`,
|
|
214
215
|
);
|
|
215
216
|
} else if (absoluteUnit && amount.remaining !== undefined) {
|
|
216
217
|
parts.push(`${formatUnitValue(amount.remaining, amount.unit)}${UNIT_SUFFIX[amount.unit]} left`);
|
|
218
|
+
} else if (
|
|
219
|
+
absoluteUnit &&
|
|
220
|
+
amount.used !== undefined &&
|
|
221
|
+
Number.isFinite(amount.used) &&
|
|
222
|
+
amount.limit === undefined &&
|
|
223
|
+
amount.remaining === undefined &&
|
|
224
|
+
fraction === undefined
|
|
225
|
+
) {
|
|
226
|
+
parts.push(`${formatUnitValue(amount.used, amount.unit)}${UNIT_SUFFIX[amount.unit]} used`);
|
|
217
227
|
}
|
|
218
|
-
const fraction = resolveUsedFraction(limit);
|
|
219
228
|
if (fraction !== undefined) {
|
|
220
229
|
parts.push(`${(fraction * 100).toFixed(1)}% used`);
|
|
221
230
|
} else if (amount.remainingFraction !== undefined) {
|