Skip to content

Commit 8229509

Browse files
Fix status usage for session-selected models
1 parent da92615 commit 8229509

4 files changed

Lines changed: 190 additions & 13 deletions

File tree

src/auto-reply/model-runtime.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,17 @@ export function resolveSelectedAndActiveModel(params: {
7878
selectedProvider: string;
7979
selectedModel: string;
8080
sessionEntry?: Pick<SessionEntry, "modelProvider" | "model">;
81+
parseSelectedProvider?: boolean;
8182
}): {
8283
selected: ModelRef;
8384
active: ModelRef;
8485
activeDiffers: boolean;
8586
} {
86-
const selected = normalizeModelRef(params.selectedModel, params.selectedProvider);
87+
const selected = normalizeModelRef(
88+
params.selectedModel,
89+
params.selectedProvider,
90+
params.parseSelectedProvider,
91+
);
8792
const runtimeModel = normalizeOptionalString(params.sessionEntry?.model);
8893
const runtimeProvider = normalizeOptionalString(params.sessionEntry?.modelProvider);
8994

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

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,164 @@ describe("buildStatusReply subagent summary", () => {
11921192
expect(providerUsageCall[0]?.providers).toEqual(["deepseek"]);
11931193
});
11941194

1195+
it("uses the session-selected model provider for /status usage", async () => {
1196+
const usageResetBase = Math.floor(Date.now() / 1000);
1197+
providerUsageMock.loadProviderUsageSummary.mockImplementation(
1198+
async ({ providers = [] } = {}) => ({
1199+
updatedAt: Date.now(),
1200+
providers: providers.map((provider) =>
1201+
provider === "openai"
1202+
? {
1203+
provider: "openai",
1204+
displayName: "OpenAI",
1205+
windows: [
1206+
{
1207+
label: "5h",
1208+
usedPercent: 9,
1209+
resetAt: (usageResetBase + 60 * 60) * 1000,
1210+
},
1211+
],
1212+
}
1213+
: {
1214+
provider,
1215+
displayName: "DeepSeek",
1216+
windows: [],
1217+
summary: "Balance ¥42.50",
1218+
},
1219+
),
1220+
}),
1221+
);
1222+
1223+
const text = await buildStatusText({
1224+
cfg: {
1225+
...baseCfg,
1226+
agents: {
1227+
defaults: {
1228+
model: "deepseek/deepseek-v4-flash",
1229+
},
1230+
},
1231+
},
1232+
sessionEntry: {
1233+
sessionId: "sess-status-session-selected-usage",
1234+
updatedAt: 0,
1235+
providerOverride: "openai",
1236+
modelOverride: "gpt-5.5",
1237+
},
1238+
sessionKey: "agent:main:main",
1239+
parentSessionKey: "agent:main:main",
1240+
sessionScope: "per-sender",
1241+
statusChannel: "telegram",
1242+
provider: "deepseek",
1243+
model: "deepseek-v4-flash",
1244+
contextTokens: 1_000_000,
1245+
resolvedFastMode: false,
1246+
resolvedVerboseLevel: "off",
1247+
resolvedReasoningLevel: "off",
1248+
resolveDefaultThinkingLevel: async () => undefined,
1249+
isGroup: false,
1250+
defaultGroupActivation: () => "mention",
1251+
modelAuthOverride: "oauth (openai:status)",
1252+
activeModelAuthOverride: "oauth (openai:status)",
1253+
});
1254+
1255+
const normalized = normalizeTestText(text);
1256+
expect(normalized).toContain("Session selected: openai/gpt-5.5");
1257+
expect(normalized).toContain("Configured default: deepseek/deepseek-v4-flash");
1258+
expect(normalized).toContain("Usage: 5h 91% left");
1259+
expect(normalized).not.toContain("Usage: Balance ¥42.50");
1260+
expect(providerUsageMock.loadProviderUsageSummary).toHaveBeenCalledWith(
1261+
expect.objectContaining({ providers: ["openai"] }),
1262+
);
1263+
});
1264+
1265+
it("uses provider-qualified model overrides for /status usage lookup", async () => {
1266+
await withTempHome(
1267+
async (dir) => {
1268+
saveStatusTestAuthProfile({ dir, profileId: "openai:status", provider: "openai" });
1269+
1270+
const usageResetBase = Math.floor(Date.now() / 1000);
1271+
providerUsageMock.loadProviderUsageSummary.mockImplementation(
1272+
async ({ providers = [] } = {}) => ({
1273+
updatedAt: Date.now(),
1274+
providers: providers.map((provider) =>
1275+
provider === "openai"
1276+
? {
1277+
provider: "openai",
1278+
displayName: "OpenAI",
1279+
windows: [
1280+
{
1281+
label: "5h",
1282+
usedPercent: 9,
1283+
resetAt: (usageResetBase + 60 * 60) * 1000,
1284+
},
1285+
],
1286+
}
1287+
: {
1288+
provider,
1289+
displayName: "DeepSeek",
1290+
windows: [],
1291+
summary: "Balance ¥42.50",
1292+
},
1293+
),
1294+
}),
1295+
);
1296+
1297+
const text = await buildStatusText({
1298+
cfg: {
1299+
...baseCfg,
1300+
models: {
1301+
providers: {
1302+
openai: {
1303+
models: [{ ...codexStatusModel, contextWindow: 258_000, contextTokens: 258_000 }],
1304+
},
1305+
},
1306+
},
1307+
agents: {
1308+
defaults: {
1309+
model: "deepseek/deepseek-v4-flash",
1310+
},
1311+
},
1312+
auth: {
1313+
order: {
1314+
openai: ["openai:status"],
1315+
},
1316+
},
1317+
},
1318+
sessionEntry: {
1319+
sessionId: "sess-status-qualified-session-selected-usage",
1320+
updatedAt: 0,
1321+
modelOverride: "openai/gpt-5.5",
1322+
},
1323+
sessionKey: "agent:main:main",
1324+
parentSessionKey: "agent:main:main",
1325+
sessionScope: "per-sender",
1326+
statusChannel: "telegram",
1327+
provider: "deepseek",
1328+
model: "deepseek-v4-flash",
1329+
contextTokens: 1_000_000,
1330+
resolvedFastMode: false,
1331+
resolvedVerboseLevel: "off",
1332+
resolvedReasoningLevel: "off",
1333+
resolveDefaultThinkingLevel: async () => undefined,
1334+
isGroup: false,
1335+
defaultGroupActivation: () => "mention",
1336+
});
1337+
1338+
const normalized = normalizeTestText(text);
1339+
expect(normalized).toContain("Session selected: openai/gpt-5.5");
1340+
expect(normalized).toContain("Configured default: deepseek/deepseek-v4-flash");
1341+
expect(normalized).toContain("oauth (openai:status)");
1342+
expect(normalized).toContain("Context: ?/258k");
1343+
expect(normalized).toContain("Usage: 5h 91% left");
1344+
expect(normalized).not.toContain("Usage: Balance ¥42.50");
1345+
expect(providerUsageMock.loadProviderUsageSummary).toHaveBeenCalledWith(
1346+
expect.objectContaining({ providers: ["openai"] }),
1347+
);
1348+
},
1349+
{ env: { OPENAI_API_KEY: undefined } },
1350+
);
1351+
});
1352+
11951353
it("uses Codex OAuth auth labels for explicit OpenAI OpenClaw auth order", async () => {
11961354
await withTempHome(
11971355
async (dir) => {

src/status/status-message.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -580,11 +580,17 @@ export function buildStatusMessage(args: StatusArgs): string {
580580
});
581581
const selectedProvider = entry?.providerOverride ?? resolved.provider ?? DEFAULT_PROVIDER;
582582
const selectedModel = entry?.modelOverride ?? resolved.model ?? DEFAULT_MODEL;
583+
const parseSelectedProvider = Boolean(
584+
entry?.modelOverride?.trim() && !entry?.providerOverride?.trim(),
585+
);
583586
const modelRefs = resolveSelectedAndActiveModel({
584587
selectedProvider,
585588
selectedModel,
586589
sessionEntry: entry,
590+
parseSelectedProvider,
587591
});
592+
const selectedLookupProvider = modelRefs.selected.provider || selectedProvider;
593+
const selectedLookupModel = modelRefs.selected.model || selectedModel;
588594
const initialFallbackState = resolveActiveFallbackState({
589595
selectedModelRef: modelRefs.selected.label || "unknown",
590596
activeModelRef: modelRefs.active.label || "unknown",
@@ -705,8 +711,8 @@ export function buildStatusMessage(args: StatusArgs): string {
705711
const runtimeDiffersFromSelected = activeModelLabel !== (modelRefs.selected.label || "unknown");
706712
const selectedContextTokens = resolveContextTokensForModel({
707713
cfg: contextConfig,
708-
provider: selectedProvider,
709-
model: selectedModel,
714+
provider: selectedLookupProvider,
715+
model: selectedLookupModel,
710716
allowAsyncLoad: false,
711717
});
712718
const explicitRuntimeContextTokens =
@@ -727,8 +733,8 @@ export function buildStatusMessage(args: StatusArgs): string {
727733
const channelModelNote = resolveChannelModelNote({
728734
config: args.config,
729735
entry,
730-
selectedProvider,
731-
selectedModel,
736+
selectedProvider: selectedLookupProvider,
737+
selectedModel: selectedLookupModel,
732738
parentSessionKey: args.parentSessionKey,
733739
});
734740
const persistedContextTokens =
@@ -968,7 +974,7 @@ export function buildStatusMessage(args: StatusArgs): string {
968974
{ config: args.config },
969975
);
970976
const selectedAuthMode =
971-
normalizeAuthMode(args.modelAuth) ?? resolveModelAuthMode(selectedProvider, args.config);
977+
normalizeAuthMode(args.modelAuth) ?? resolveModelAuthMode(selectedLookupProvider, args.config);
972978
const rawSelectedAuthLabelValue =
973979
selectedAuthMode && selectedAuthMode !== "unknown"
974980
? (args.modelAuth ?? selectedAuthMode)

src/status/status-text.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -299,27 +299,35 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
299299
params.workspaceDir ??
300300
sessionEntry?.spawnedWorkspaceDir ??
301301
resolveAgentWorkspaceDir(cfg, statusAgentId);
302+
const selectedProvider = sessionEntry?.providerOverride?.trim() ?? provider;
303+
const selectedModel = sessionEntry?.modelOverride?.trim() ?? model;
304+
const parseSelectedProvider = Boolean(
305+
sessionEntry?.modelOverride?.trim() && !sessionEntry?.providerOverride?.trim(),
306+
);
302307
const modelRefs = resolveSelectedAndActiveModel({
303-
selectedProvider: provider,
304-
selectedModel: model,
308+
selectedProvider,
309+
selectedModel,
305310
sessionEntry,
311+
parseSelectedProvider,
306312
});
313+
const selectedLookupProvider = modelRefs.selected.provider || selectedProvider || provider;
314+
const selectedLookupModel = modelRefs.selected.model || selectedModel || model;
307315
const effectiveHarness =
308316
params.resolvedHarness ??
309317
(await resolveStatusHarnessId({
310318
cfg,
311-
provider,
312-
model,
319+
provider: selectedLookupProvider,
320+
model: selectedLookupModel,
313321
agentId: statusAgentId,
314322
sessionKey,
315323
sessionEntry,
316324
}));
317325
const selectedStatusProvider = resolveStatusRuntimeProvider({
318-
provider,
326+
provider: selectedLookupProvider,
319327
effectiveHarness,
320328
});
321329
const selectedAuthProviders = listOpenAIAuthProfileProvidersForAgentRuntime({
322-
provider,
330+
provider: selectedLookupProvider,
323331
harnessRuntime: effectiveHarness,
324332
config: cfg,
325333
});
@@ -528,7 +536,7 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
528536
const selectedContextTokens = resolveStatusRuntimeContextTokens({
529537
cfg,
530538
provider: selectedStatusProvider,
531-
model,
539+
model: modelRefs.selected.model || selectedLookupModel,
532540
});
533541
const runtimeSnapshotHasFallbackProvenance =
534542
!modelRefs.activeDiffers ||

0 commit comments

Comments
 (0)