Skip to content

Commit ebc2e10

Browse files
authored
Treat fast mode as opt-in so a turn that never asked for it is not billed against a tier the organization may have no quota for (#26)
1 parent 2c660da commit ebc2e10

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

src/harness/pi-harness.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,22 @@ const FAST_MODE_BETA = "fast-mode-2026-02-01";
10341034

10351035
export { modelSupportsFastMode } from "../model/pi-models.ts";
10361036

1037+
/**
1038+
* Whether a turn should run in fast mode.
1039+
*
1040+
* Fast mode is OPT-IN: only an explicit `true` selects it. An unset `fastMode` means the
1041+
* caller expressed no preference, and treating that as "yes" bills the turn against a tier
1042+
* it never asked for — or fails it outright on an organization with no fast-mode quota,
1043+
* where the provider answers `rate_limit_error: … 0 fast mode input tokens per minute`.
1044+
*
1045+
* Only the web UI ever sets the field today, so every other entry point (CLI, API clients,
1046+
* integrations) leaves it undefined. `claude-harness` already reads it as opt-in
1047+
* (`turn.fastMode && …`); this keeps both harnesses agreeing on the same default.
1048+
*/
1049+
export function wantsFastMode(fastMode: boolean | undefined, modelId: string | undefined): boolean {
1050+
return fastMode === true && modelSupportsFastMode(modelId);
1051+
}
1052+
10371053
export const TURN_PROVIDER_EFFORT_ALIASES: Record<string, string | null> = {
10381054
max: "max",
10391055
ultracode: "max",
@@ -1447,7 +1463,7 @@ export function createPiHarness(opts?: PiHarnessOptions): Harness {
14471463
entry.ref.toolApprovalGate = turn.toolApprovalGate;
14481464

14491465
const desiredModelId = turn.model ?? resolveModelId(turn.scopeLabel);
1450-
const wantFast = turn.fastMode !== false && modelSupportsFastMode(desiredModelId);
1466+
const wantFast = wantsFastMode(turn.fastMode, desiredModelId);
14511467
const current = entry.agentSession.model as { id?: string; headers?: Record<string, string> } | undefined;
14521468
const currentFast = Boolean(current?.headers?.["anthropic-beta"]?.includes(FAST_MODE_BETA));
14531469
if (current?.id !== desiredModelId || currentFast !== wantFast) {
@@ -1707,7 +1723,7 @@ export function createPiHarness(opts?: PiHarnessOptions): Harness {
17071723
console.error(
17081724
`[pi] provider refusal — retrying on fallback model ${fromId} -> ${fallbackId} session=${turn.session.id}: ${refusal}`,
17091725
);
1710-
const wantFast = turn.fastMode !== false && modelSupportsFastMode(fallbackId);
1726+
const wantFast = wantsFastMode(turn.fastMode, fallbackId);
17111727
await entry.agentSession.setModel(wantFast ? withFastModeHeaders(fallback) : fallback);
17121728
const active = entry.agentSession.model as { headers?: Record<string, string> } | undefined;
17131729
entry.ref.fast = Boolean(active?.headers?.["anthropic-beta"]?.includes(FAST_MODE_BETA));

test/pi-harness-fast-mode.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { test } from "node:test";
22
import assert from "node:assert/strict";
3-
import { applyFastSpeed, modelSupportsFastMode, TURN_PROVIDER_EFFORT_ALIASES } from "../src/harness/pi-harness.ts";
3+
import {
4+
applyFastSpeed,
5+
modelSupportsFastMode,
6+
wantsFastMode,
7+
TURN_PROVIDER_EFFORT_ALIASES,
8+
} from "../src/harness/pi-harness.ts";
49
import { defaultInteractiveThinkingLevel } from "../src/model/pi-models.ts";
510

611
test("modelSupportsFastMode allows only the documented direct Opus ids", () => {
@@ -50,3 +55,18 @@ test("defaultInteractiveThinkingLevel keeps human turns light by provider", () =
5055
assert.equal(defaultInteractiveThinkingLevel({ provider: "anthropic", api: "anthropic-messages" }), "low");
5156
assert.equal(defaultInteractiveThinkingLevel({ provider: "openai", api: "openai-responses" }), "auto");
5257
});
58+
59+
test("fast mode is opt-in: only an explicit true selects it", () => {
60+
// A turn that never mentions fastMode has expressed no preference. Reading that as "yes"
61+
// bills it against a tier nobody asked for, and on an organization with no fast-mode
62+
// quota the provider rejects every such turn outright.
63+
assert.equal(wantsFastMode(undefined, "claude-opus-5"), false, "unset must not select fast mode");
64+
assert.equal(wantsFastMode(false, "claude-opus-5"), false);
65+
assert.equal(wantsFastMode(true, "claude-opus-5"), true, "an explicit opt-in is honoured");
66+
});
67+
68+
test("an explicit opt-in still cannot select fast mode on a model that lacks it", () => {
69+
assert.equal(wantsFastMode(true, "claude-sonnet-5"), false);
70+
assert.equal(wantsFastMode(true, undefined), false);
71+
assert.equal(wantsFastMode(true, ""), false);
72+
});

0 commit comments

Comments
 (0)