Skip to content

Commit afa4474

Browse files
committed
fix: align codex status usage gates
1 parent 8b848cf commit afa4474

4 files changed

Lines changed: 89 additions & 56 deletions

File tree

src/auto-reply/reply/commands-status.test.ts

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,49 @@ describe("buildStatusReply subagent summary", () => {
12141214
],
12151215
});
12161216

1217-
await withTempHome(async () => {
1217+
await withEnvAsync({ OPENAI_API_KEY: undefined }, async () => {
1218+
await withTempHome(async () => {
1219+
const text = await buildStatusText({
1220+
cfg: {
1221+
...baseCfg,
1222+
agents: {
1223+
defaults: {
1224+
agentRuntime: { id: "codex" },
1225+
},
1226+
},
1227+
},
1228+
sessionEntry: {
1229+
sessionId: "sess-status-codex-no-profile",
1230+
updatedAt: 0,
1231+
},
1232+
sessionKey: "agent:main:main",
1233+
parentSessionKey: "agent:main:main",
1234+
sessionScope: "per-sender",
1235+
statusChannel: "mobilechat",
1236+
provider: "openai",
1237+
model: "gpt-5.5",
1238+
contextTokens: 32_000,
1239+
resolvedFastMode: false,
1240+
resolvedVerboseLevel: "off",
1241+
resolvedReasoningLevel: "off",
1242+
resolveDefaultThinkingLevel: async () => undefined,
1243+
isGroup: false,
1244+
defaultGroupActivation: () => "mention",
1245+
});
1246+
1247+
expect(normalizeTestText(text)).toContain("Usage: 5h 84% left");
1248+
const providerUsageCall = providerUsageMock.loadProviderUsageSummary.mock.calls.find(
1249+
([params]) => params?.providers?.includes("openai"),
1250+
);
1251+
expect(providerUsageCall?.[0]?.auth).toEqual(expectedCodexRuntimeUsageAuth);
1252+
});
1253+
});
1254+
});
1255+
1256+
it("does not load Codex synthetic usage for API-key-backed Codex status without overrides", async () => {
1257+
registerStatusCodexHarness();
1258+
1259+
await withEnvAsync({ OPENAI_API_KEY: "test-openai-key" }, async () => {
12181260
const text = await buildStatusText({
12191261
cfg: {
12201262
...baseCfg,
@@ -1225,7 +1267,7 @@ describe("buildStatusReply subagent summary", () => {
12251267
},
12261268
},
12271269
sessionEntry: {
1228-
sessionId: "sess-status-codex-no-profile",
1270+
sessionId: "sess-status-codex-api-key-no-overrides",
12291271
updatedAt: 0,
12301272
},
12311273
sessionKey: "agent:main:main",
@@ -1243,11 +1285,12 @@ describe("buildStatusReply subagent summary", () => {
12431285
defaultGroupActivation: () => "mention",
12441286
});
12451287

1246-
expect(normalizeTestText(text)).toContain("Usage: 5h 84% left");
1247-
const providerUsageCall = providerUsageMock.loadProviderUsageSummary.mock.calls.find(
1248-
([params]) => params?.providers?.includes("openai"),
1249-
);
1250-
expect(providerUsageCall?.[0]?.auth).toEqual(expectedCodexRuntimeUsageAuth);
1288+
expect(normalizeTestText(text)).toContain("api-key");
1289+
expect(
1290+
providerUsageMock.loadProviderUsageSummary.mock.calls.some(([params]) =>
1291+
params?.auth?.some((auth) => auth.hookProvider === "codex"),
1292+
),
1293+
).toBe(false);
12511294
});
12521295
});
12531296

src/commands/status-runtime-shared.ts

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
// Shared runtime probes used by status text and JSON commands.
22
// Heavy modules stay lazily loaded so fast status output avoids security/provider/gateway costs.
33

4-
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
5-
import { resolveDefaultAgentDir } from "../agents/agent-scope.js";
6-
import { resolveAgentHarnessPolicy } from "../agents/harness/policy.js";
7-
import { resolveModelAuthLabel } from "../agents/model-auth-label.js";
8-
import { resolveDefaultModelForAgent } from "../agents/model-selection.js";
9-
import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../agents/openai-routing.js";
104
import type { OpenClawConfig } from "../config/types.js";
115
import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
126
import { createLazyImportLoader } from "../shared/lazy-promise.js";
137
import {
148
buildCodexSyntheticUsageAuth,
159
mergeUsageSummaries,
10+
resolveUsageCredentialType,
1611
shouldUseCodexSyntheticUsageForRuntime,
1712
} from "../status/codex-synthetic-usage.js";
1813
import type { HealthSummary } from "./health.js";
@@ -43,27 +38,21 @@ function loadGatewayCallModule() {
4338
return gatewayCallModuleLoader.load();
4439
}
4540

46-
function resolveUsageCredentialType(authLabel?: string): "oauth" | "token" | "api_key" | undefined {
47-
const auth = normalizeOptionalLowercaseString(authLabel);
48-
if (!auth) {
49-
return undefined;
50-
}
51-
if (auth.startsWith("oauth")) {
52-
return "oauth";
53-
}
54-
if (auth.startsWith("token")) {
55-
return "token";
56-
}
57-
if (auth.startsWith("api-key") || auth.startsWith("api key")) {
58-
return "api_key";
59-
}
60-
return undefined;
61-
}
62-
63-
function shouldUseConfiguredCodexSyntheticUsage(params: {
41+
async function shouldUseConfiguredCodexSyntheticUsage(params: {
6442
config: OpenClawConfig;
6543
agentDir: string;
66-
}): boolean {
44+
}): Promise<boolean> {
45+
const [
46+
{ resolveAgentHarnessPolicy },
47+
{ resolveModelAuthLabel },
48+
{ resolveDefaultModelForAgent },
49+
{ listOpenAIAuthProfileProvidersForAgentRuntime },
50+
] = await Promise.all([
51+
import("../agents/harness/policy.js"),
52+
import("../agents/model-auth-label.js"),
53+
import("../agents/model-selection.js"),
54+
import("../agents/openai-routing.js"),
55+
]);
6756
const configuredDefault = resolveDefaultModelForAgent({
6857
cfg: params.config,
6958
allowPluginNormalization: false,
@@ -131,13 +120,15 @@ type StatusUsageSummaryOptions = {
131120
/** Loads provider usage for status output, defaulting to the config's default agent directory. */
132121
export async function resolveStatusUsageSummary(params: StatusUsageSummaryOptions) {
133122
const { loadProviderUsageSummary } = await loadProviderUsage();
134-
const agentDir = params.agentDir ?? resolveDefaultAgentDir(params.config);
123+
const agentDir =
124+
params.agentDir ??
125+
(await import("../agents/agent-scope.js")).resolveDefaultAgentDir(params.config);
135126
const usage = await loadProviderUsageSummary({
136127
timeoutMs: params.timeoutMs,
137128
config: params.config,
138129
agentDir,
139130
});
140-
if (!shouldUseConfiguredCodexSyntheticUsage({ config: params.config, agentDir })) {
131+
if (!(await shouldUseConfiguredCodexSyntheticUsage({ config: params.config, agentDir }))) {
141132
return usage;
142133
}
143134
const codexUsage = await loadProviderUsageSummary({

src/status/codex-synthetic-usage.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ export function shouldUseCodexSyntheticUsageForRuntime(params: {
3232
);
3333
}
3434

35+
export function resolveUsageCredentialType(
36+
authLabel?: string,
37+
): "oauth" | "token" | "api_key" | undefined {
38+
const auth = normalizeOptionalLowercaseString(authLabel);
39+
if (!auth) {
40+
return undefined;
41+
}
42+
if (auth.startsWith("oauth")) {
43+
return "oauth";
44+
}
45+
if (auth.startsWith("token")) {
46+
return "token";
47+
}
48+
if (auth.startsWith("api-key") || auth.startsWith("api key")) {
49+
return "api_key";
50+
}
51+
return undefined;
52+
}
53+
3554
function shouldPreferUsageSnapshot(snapshot: ProviderUsageSnapshot): boolean {
3655
return snapshot.windows.length > 0 || Boolean(snapshot.summary?.trim()) || !snapshot.error;
3756
}

src/status/status-text.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
} from "../tasks/task-status.js";
5151
import {
5252
buildCodexSyntheticUsageAuth,
53+
resolveUsageCredentialType,
5354
shouldUseCodexSyntheticUsageForRuntime,
5455
} from "./codex-synthetic-usage.js";
5556
import { resolveActiveFallbackState } from "./fallback-notice-state.js";
@@ -180,23 +181,6 @@ function shouldLoadUsageSummary(params: {
180181
);
181182
}
182183

183-
function resolveUsageCredentialType(authLabel?: string): "oauth" | "token" | "api_key" | undefined {
184-
const auth = normalizeOptionalLowercaseString(authLabel);
185-
if (!auth) {
186-
return undefined;
187-
}
188-
if (auth.startsWith("oauth")) {
189-
return "oauth";
190-
}
191-
if (auth.startsWith("token")) {
192-
return "token";
193-
}
194-
if (auth.startsWith("api-key") || auth.startsWith("api key")) {
195-
return "api_key";
196-
}
197-
return undefined;
198-
}
199-
200184
function resolveCodexSyntheticUsageAuthProfileId(params: {
201185
profileId: string | undefined;
202186
cfg: OpenClawConfig;
@@ -446,15 +430,11 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
446430
: selectedStatusProvider;
447431
const usageProvider = activeRuntimeIsAuthoritative ? activeProvider : selectedLookupProvider;
448432
const selectedUsageCredentialType = resolveUsageCredentialType(usageAuthLabel);
449-
const usageAuthOverrideProvided = activeRuntimeIsAuthoritative
450-
? Object.hasOwn(params, "activeModelAuthOverride")
451-
: Object.hasOwn(params, "modelAuthOverride");
452433
const useCodexSyntheticUsage =
453434
shouldUseCodexSyntheticUsageForRuntime({
454435
provider: usageStatusProvider,
455436
effectiveHarness,
456-
}) &&
457-
(!usageAuthOverrideProvided || selectedUsageCredentialType !== "api_key");
437+
}) && selectedUsageCredentialType !== "api_key";
458438
const codexUsageAuthProfileId = useCodexSyntheticUsage
459439
? resolveCodexSyntheticUsageAuthProfileId({
460440
profileId: sessionEntry?.authProfileOverride,

0 commit comments

Comments
 (0)