Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f3b970e
test(server): cover transcript usage diagnostics
juliusmarminge Oct 19, 2026
13f1c91
fix(server): retain transcript usage diagnostics
juliusmarminge Oct 19, 2026
ae206d2
test(server): cover Codex counter reconciliation and cache upgrades
juliusmarminge Oct 19, 2026
07209b8
fix(server): reconcile Codex request usage with cumulative counters
juliusmarminge Oct 19, 2026
c9fef5c
test(server): cover stale Codex compaction estimates
juliusmarminge Oct 19, 2026
4b1a9fa
fix(server): skip stale Codex compaction estimates
juliusmarminge Oct 19, 2026
7aab5bf
test(server): define historical Codex usage recovery contract
juliusmarminge Oct 19, 2026
280db2e
fix(server): resolve Codex usage tiers from attempt history
juliusmarminge Oct 19, 2026
8c62e59
perf(server): route Codex usage history through the reader worker
juliusmarminge Oct 19, 2026
e51af66
test(server): include usage index in migration expectation
juliusmarminge Oct 19, 2026
a9b4b85
test(server): cover initial Codex usage tier recovery
juliusmarminge Oct 19, 2026
88c5115
fix(server): recover missing initial Codex usage tiers
juliusmarminge Oct 19, 2026
f5c8fb5
test(server): preserve Pi positions in v6 usage caches
juliusmarminge Oct 19, 2026
e5ef040
fix(server): preserve compatible v6 usage cache positions
juliusmarminge Oct 19, 2026
ba9e9ec
Merge remote-tracking branch 'origin/main' into HEAD
juliusmarminge Oct 20, 2026
a73fdeb
Merge remote-tracking branch 'origin/main' into HEAD
juliusmarminge Oct 20, 2026
022c86a
Merge remote-tracking branch 'origin/main' into HEAD
juliusmarminge Oct 20, 2026
ee0b490
fix(web): use current remote open target argument
juliusmarminge Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/server/src/provider/Drivers/CodexDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as Schema from "effect/Schema";
import { HttpClient } from "effect/http";
import { ChildProcessSpawner } from "effect/process";

import type * as CodexUsageHistory from "../../usage/CodexUsageHistory.ts";
import { makeCodexTextGeneration } from "../../textGeneration/CodexTextGeneration.ts";
import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
Expand Down Expand Up @@ -97,7 +98,11 @@ export type CodexDriverEnv =
| ServerEnvironment.ServerEnvironmentIdentity
| CodexInstallation.CodexInstallation;

export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv, Path.Path> = {
export const CodexDriver: ProviderDriver<
CodexSettings,
CodexDriverEnv,
Path.Path | CodexUsageHistory.CodexUsageHistory
> = {
driverKind: DRIVER_KIND,
metadata: {
displayName: "Codex",
Expand Down
69 changes: 69 additions & 0 deletions apps/server/src/provider/Drivers/codexUsage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,75 @@ describe("parseCodexLine", () => {
expect(speedAfter(settings({ model: "gpt-6-astra" }), 4)).toBe("standard");
});

it("retains initial tier evidence only for the own OpenAI session and exact context until native settings arrive", () => {
const meta = (id: string, model_provider?: string) =>
JSON.stringify({ type: "session_meta", payload: { id, model_provider } });
const context = (turn_id?: string, model?: string) =>
JSON.stringify({ type: "turn_context", payload: { turn_id, model } });
for (const provider of ["openai", "t3", "custom", undefined]) {
const state = initialCodexScanState();
parseCodexLine(meta("own-session", provider), state);
parseCodexLine(meta("ancestor", provider === "openai" ? "custom" : "openai"), state);
parseCodexLine(context("first-turn", "gpt-6-astra"), state);
const first = parseCodexLine(tokenCount(100, 0, 1, 0), state);
expect(first).toMatchObject({
sessionId: "own-session",
model: "gpt-6-astra",
speed: "standard",
});
if (provider === "openai") {
expect(first).toHaveProperty("unresolvedServiceTierTurnId", "first-turn");
} else {
expect(first).not.toHaveProperty("unresolvedServiceTierTurnId");
}

parseCodexLine(context(undefined, "gpt-6-astra"), state);
expect(parseCodexLine(tokenCount(100, 0, 2, 0), state)).not.toHaveProperty(
"unresolvedServiceTierTurnId",
);
parseCodexLine(context("missing-model"), state);
expect(parseCodexLine(tokenCount(100, 0, 3, 0), state)).not.toHaveProperty(
"unresolvedServiceTierTurnId",
);
parseCodexLine(context("next-turn", "gpt-6-astra"), state);
const next = parseCodexLine(tokenCount(100, 0, 4, 0), state);
if (provider === "openai") {
expect(next).toHaveProperty("unresolvedServiceTierTurnId", "next-turn");
} else {
expect(next).not.toHaveProperty("unresolvedServiceTierTurnId");
}
}

for (const service_tier of ["standard", "", undefined, "priority", "ultrafast"]) {
const state = initialCodexScanState();
parseCodexLine(meta("own-session", "openai"), state);
parseCodexLine(context("first-turn", "gpt-6-astra"), state);
expect(parseCodexLine(tokenCount(100, 0, 1, 0), state)).toHaveProperty(
"unresolvedServiceTierTurnId",
"first-turn",
);
parseCodexLine(
JSON.stringify({
type: "event_msg",
payload: { type: "thread_settings_applied", thread_settings: { service_tier } },
}),
state,
);
for (const output of [2, 3]) {
parseCodexLine(context(`native-turn-${output}`, "gpt-6-astra"), state);
const record = parseCodexLine(tokenCount(100, 0, output, 0), state);
expect(record?.speed).toBe(
service_tier === "priority"
? "fast"
: service_tier === "ultrafast"
? "ultrafast"
: "standard",
);
expect(record).not.toHaveProperty("unresolvedServiceTierTurnId");
}
}
});

// A forked/subagent rollout opens with the parent's history copied in and
// every line re-stamped to the fork instant, then the ancestors' session
// metas. Counting those again multiplied usage ~1.85x on real data (#5758).
Expand Down
58 changes: 57 additions & 1 deletion apps/server/src/provider/Drivers/codexUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@t3tools/provider-core/server/usage";
import type { UsageTokenTotals } from "@t3tools/contracts";

import * as CodexUsageHistory from "../../usage/CodexUsageHistory.ts";
import { resolveCodexHomeLayout } from "./CodexHomeLayout.ts";

const TokenCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
Expand Down Expand Up @@ -72,6 +73,9 @@ export const CodexScanState = Schema.Struct({
lastCumulativeUsage: Schema.mutableKey(Schema.NullOr(CodexTokenUsage)),
malformedRecords: Schema.mutableKey(TokenCount),
sawSessionMeta: Schema.mutableKey(Schema.Boolean),
modelProvider: Schema.mutableKey(Schema.String),
turnId: Schema.mutableKey(Schema.String),
sawThreadSettings: Schema.mutableKey(Schema.Boolean),
suppressingForkCopies: Schema.mutableKey(Schema.Boolean),
forkCopyAnchorMs: Schema.mutableKey(Schema.Finite),
});
Expand All @@ -86,6 +90,9 @@ export function initialCodexScanState(): CodexScanState {
lastCumulativeUsage: null,
malformedRecords: 0,
sawSessionMeta: false,
modelProvider: "",
turnId: "",
sawThreadSettings: false,
suppressingForkCopies: false,
forkCopyAnchorMs: 0,
};
Expand Down Expand Up @@ -141,6 +148,9 @@ function parseCodexRecord(parsed: unknown, state: CodexScanState): UsageRecord |
state.sawSessionMeta = true;
const id = payloadRecord["id"] ?? payloadRecord["session_id"];
if (typeof id === "string") state.sessionId = id;
if (typeof payloadRecord["model_provider"] === "string") {
state.modelProvider = payloadRecord["model_provider"];
}
const metaTimestampMs = parseTimestampMs(record["timestamp"]);
if (metaTimestampMs !== null && isForkedSessionMeta(payloadRecord)) {
state.suppressingForkCopies = true;
Expand All @@ -151,10 +161,18 @@ function parseCodexRecord(parsed: unknown, state: CodexScanState): UsageRecord |

if (record["type"] === "turn_context") {
if (typeof payloadRecord["model"] === "string") state.model = payloadRecord["model"];
// Model carry is useful for usage, but history recovery needs this context's own identity.
state.turnId =
typeof payloadRecord["turn_id"] === "string" &&
typeof payloadRecord["model"] === "string" &&
payloadRecord["model"].length > 0
? payloadRecord["turn_id"]
: "";
return null;
}

if (payloadType === "thread_settings_applied") {
state.sawThreadSettings = true;
const settings = payloadRecord["thread_settings"];
if (typeof settings === "object" && settings !== null) {
state.speed = codexSpeed((settings as Record<string, unknown>)["service_tier"]);
Expand Down Expand Up @@ -270,6 +288,12 @@ function parseCodexRecord(parsed: unknown, state: CodexScanState): UsageRecord |
totals,
reportedCostUsd: null,
speed: state.speed,
...(!state.sawThreadSettings &&
state.modelProvider === "openai" &&
state.sessionId.length > 0 &&
state.turnId.length > 0
? { unresolvedServiceTierTurnId: state.turnId }
: {}),
dedupeKey: null,
};
}
Expand All @@ -295,6 +319,8 @@ export const codexUsageFormat: TranscriptUsageFormat<CodexScanState> = {
type: true,
id: true,
session_id: true,
model_provider: true,
turn_id: true,
model: true,
thread_settings: { service_tier: true },
forked_from_id: true,
Expand All @@ -313,10 +339,40 @@ export const codexUsageFormat: TranscriptUsageFormat<CodexScanState> = {
sharedSessionsAcrossFiles: true,
};

export const codexUsageReader: ProviderUsageReader<CodexSettings, Path.Path> = {
export const codexUsageReader: ProviderUsageReader<
CodexSettings,
Path.Path | CodexUsageHistory.CodexUsageHistory
> = {
kind: "transcripts",
provider: "codex",
format: codexUsageFormat,
resolveServiceTiers: Effect.fn("codexUsageReader.resolveServiceTiers")(function* (records) {
const history = yield* CodexUsageHistory.CodexUsageHistory;
const requests = new Map<CodexUsageHistory.CodexUsageHistoryRequest, UsageRecord>();
for (const record of records) {
if (record.unresolvedServiceTierTurnId === undefined) continue;
requests.set(
{
sessionId: record.sessionId,
nativeTurnId: record.unresolvedServiceTierTurnId,
model: record.model,
},
record,
);
}
const resolved = yield* history.resolve([...requests.keys()]).pipe(
Effect.catchTags({
CodexUsageHistoryReadError: () =>
Effect.succeed(new Map<CodexUsageHistory.CodexUsageHistoryRequest, UsageSpeed>()),
}),
);
const speeds = new Map<UsageRecord, UsageSpeed>();
for (const [request, speed] of resolved) {
const record = requests.get(request);
if (record !== undefined) speeds.set(record, speed);
}
return speeds;
}),
directories: Effect.fn("codexUsageReader.directories")(function* ({ config, environment }) {
if (config === undefined) return [];
const path = yield* Path.Path;
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ import * as CursorAccountReader from "@t3tools/provider-cursor/server/CursorAcco
import * as CursorKeychain from "@t3tools/provider-cursor/server/CursorKeychain";
import * as CursorUsageAccounts from "@t3tools/provider-cursor/server/CursorUsageAccounts";
import * as UsageService from "./usage/UsageService.ts";
import * as CodexUsageHistory from "./usage/CodexUsageHistory.ts";
import * as RuntimeLayer from "./orchestration-v2/runtimeLayer.ts";
import * as ProjectStore from "./orchestration-v2/ProjectStore.ts";
import * as ThreadSearch from "./orchestration-v2/ThreadSearch.ts";
Expand Down Expand Up @@ -265,6 +266,7 @@ const layerUsage = UsageService.layer.pipe(
Layer.provide(
Layer.mergeAll(
AntigravityUsage.layer,
CodexUsageHistory.layer,
CursorUsageAccounts.layer.pipe(
Layer.provide(CursorAccountReader.layer.pipe(Layer.provide(CursorKeychain.layer))),
),
Expand Down
6 changes: 4 additions & 2 deletions apps/server/src/usage/CodexUsageHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getCodexServiceTierOptionValue } from "../codexModelOptions.ts";

export class CodexUsageHistoryReadError extends Schema.TaggedError<CodexUsageHistoryReadError>()(
"CodexUsageHistoryReadError",
{ cause: Schema.Defect() },
{ requestedTurnCount: Schema.Number, cause: Schema.Defect() },
) {
override get message(): string {
return "Could not read historical Codex usage tiers.";
Expand Down Expand Up @@ -84,7 +84,9 @@ const make = Effect.gen(function* () {
)
`.pipe(
NodeSqliteClient.readOnly,
Effect.mapError((cause) => new CodexUsageHistoryReadError({ cause })),
Effect.mapError(
(cause) => new CodexUsageHistoryReadError({ requestedTurnCount: requests.length, cause }),
),
);

// Keep duplicates ambiguous even when an attempt is missing or its destination differs.
Expand Down
Loading
Loading