Skip to content

Commit de82ec8

Browse files
authored
Merge pull request #128 from pylon-code/upstream/2026-08-28-codex-0150-plans
fix(codex): accept Codex 0.150 account plans
2 parents 3d6035f + 2c7ec99 commit de82ec8

5 files changed

Lines changed: 183 additions & 4 deletions

File tree

‎apps/server/src/provider/Layers/CodexProvider.test.ts‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { assert, it } from "@effect/vitest";
22

3-
import { applyPreferredCodexDefaultModel, mapCodexModelCapabilities } from "./CodexProvider.ts";
3+
import * as CodexSchema from "effect-codex-app-server/schema";
4+
5+
import {
6+
applyPreferredCodexDefaultModel,
7+
codexAccountAuthLabel,
8+
mapCodexModelCapabilities,
9+
} from "./CodexProvider.ts";
410

511
it("maps current Codex model capability fields", () => {
612
const capabilities = mapCodexModelCapabilities({
@@ -144,3 +150,46 @@ it("ignores custom models that shadow a preferred slug", () => {
144150

145151
assert.deepStrictEqual(models.find((model) => model.isDefault)?.slug, "gpt-5.4");
146152
});
153+
154+
it("labels every account plan Codex can report", () => {
155+
// The switch ends in `satisfies never`, so this table and the generated
156+
// `PlanType` literals have to stay in step: adding a literal without a case
157+
// fails typecheck, and reordering a case into the wrong group fails here.
158+
const labels: ReadonlyArray<readonly [string, string]> = [
159+
["free", "ChatGPT Free Subscription"],
160+
["go", "ChatGPT Go Subscription"],
161+
["plus", "ChatGPT Plus Subscription"],
162+
["pro", "ChatGPT Pro 20x Subscription"],
163+
["prolite", "ChatGPT Pro 5x Subscription"],
164+
["team", "ChatGPT Team Subscription"],
165+
["self_serve_business_prolite", "ChatGPT Business Subscription"],
166+
["self_serve_business_usage_based", "ChatGPT Business Subscription"],
167+
["business", "ChatGPT Business Subscription"],
168+
["ent26", "ChatGPT Enterprise Subscription"],
169+
["enterprise_cbp_automation", "ChatGPT Enterprise Subscription"],
170+
["enterprise_cbp_usage_based", "ChatGPT Enterprise Subscription"],
171+
["enterprise", "ChatGPT Enterprise Subscription"],
172+
["edu", "ChatGPT Edu Subscription"],
173+
["edu_plus", "ChatGPT Edu Subscription"],
174+
["edu_pro", "ChatGPT Edu Subscription"],
175+
["unknown", "ChatGPT Subscription"],
176+
];
177+
178+
for (const [planType, expected] of labels) {
179+
assert.strictEqual(
180+
codexAccountAuthLabel({
181+
email: "user@example.com",
182+
planType: planType as CodexSchema.V2GetAccountResponse__PlanType,
183+
type: "chatgpt",
184+
}),
185+
expected,
186+
);
187+
}
188+
});
189+
190+
it("labels non-ChatGPT account types", () => {
191+
assert.strictEqual(codexAccountAuthLabel({ type: "apiKey" }), "OpenAI API Key");
192+
assert.strictEqual(codexAccountAuthLabel({ type: "amazonBedrock" }), "Amazon Bedrock");
193+
assert.strictEqual(codexAccountAuthLabel(undefined), undefined);
194+
assert.strictEqual(codexAccountAuthLabel(null), undefined);
195+
});

‎apps/server/src/provider/Layers/CodexProvider.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ function reasoningEffortLabel(reasoningEffort: string): string {
9292
return REASONING_EFFORT_LABELS[reasoningEffort] ?? reasoningEffort;
9393
}
9494

95-
function codexAccountAuthLabel(account: CodexSchema.V2GetAccountResponse["account"]) {
95+
export function codexAccountAuthLabel(account: CodexSchema.V2GetAccountResponse["account"]) {
9696
if (!account) return undefined;
9797
if (account.type === "apiKey") return "OpenAI API Key";
9898
if (account.type === "amazonBedrock") return "Amazon Bedrock";
@@ -111,13 +111,18 @@ function codexAccountAuthLabel(account: CodexSchema.V2GetAccountResponse["accoun
111111
return "ChatGPT Pro 5x Subscription";
112112
case "team":
113113
return "ChatGPT Team Subscription";
114+
case "self_serve_business_prolite":
114115
case "self_serve_business_usage_based":
115116
case "business":
116117
return "ChatGPT Business Subscription";
118+
case "ent26":
119+
case "enterprise_cbp_automation":
117120
case "enterprise_cbp_usage_based":
118121
case "enterprise":
119122
return "ChatGPT Enterprise Subscription";
120123
case "edu":
124+
case "edu_plus":
125+
case "edu_pro":
121126
return "ChatGPT Edu Subscription";
122127
case "unknown":
123128
return "ChatGPT Subscription";

‎packages/effect-codex-app-server/scripts/generate.ts‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ const ManualSchemas: Record<string, Schema.Json> = {
145145
},
146146
};
147147

148-
// Codex 0.150 added these multi-agent values before our next full protocol
149-
// refresh. Keep every generated response namespace compatible with them.
148+
// Enum values Codex 0.150 added before our next full protocol refresh, across
149+
// both the multi-agent and account surfaces. Each entry replaces the upstream
150+
// definition wholesale, so keep it a superset of the pinned ref and keep every
151+
// generated response namespace compatible with it.
150152
const Codex0150DefinitionSchemas: Record<string, Schema.Json> = {
151153
CollabAgentTool: {
152154
type: "string",
@@ -166,6 +168,28 @@ const Codex0150DefinitionSchemas: Record<string, Schema.Json> = {
166168
type: "string",
167169
enum: ["inProgress", "completed", "failed", "interrupted"],
168170
},
171+
PlanType: {
172+
type: "string",
173+
enum: [
174+
"free",
175+
"go",
176+
"plus",
177+
"pro",
178+
"prolite",
179+
"team",
180+
"self_serve_business_prolite",
181+
"self_serve_business_usage_based",
182+
"business",
183+
"ent26",
184+
"enterprise_cbp_automation",
185+
"enterprise_cbp_usage_based",
186+
"enterprise",
187+
"edu",
188+
"edu_plus",
189+
"edu_pro",
190+
"unknown",
191+
],
192+
},
169193
SubAgentActivityKind: {
170194
type: "string",
171195
enum: ["started", "interacted", "interrupted", "completed"],

‎packages/effect-codex-app-server/src/_generated/schema.gen.ts‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,11 +2411,16 @@ export type ServerNotification__PlanType =
24112411
| "pro"
24122412
| "prolite"
24132413
| "team"
2414+
| "self_serve_business_prolite"
24142415
| "self_serve_business_usage_based"
24152416
| "business"
2417+
| "ent26"
2418+
| "enterprise_cbp_automation"
24162419
| "enterprise_cbp_usage_based"
24172420
| "enterprise"
24182421
| "edu"
2422+
| "edu_plus"
2423+
| "edu_pro"
24192424
| "unknown";
24202425
export const ServerNotification__PlanType = Schema.Literals([
24212426
"free",
@@ -2424,11 +2429,16 @@ export const ServerNotification__PlanType = Schema.Literals([
24242429
"pro",
24252430
"prolite",
24262431
"team",
2432+
"self_serve_business_prolite",
24272433
"self_serve_business_usage_based",
24282434
"business",
2435+
"ent26",
2436+
"enterprise_cbp_automation",
24292437
"enterprise_cbp_usage_based",
24302438
"enterprise",
24312439
"edu",
2440+
"edu_plus",
2441+
"edu_pro",
24322442
"unknown",
24332443
]);
24342444

@@ -3248,11 +3258,16 @@ export type V2AccountRateLimitsUpdatedNotification__PlanType =
32483258
| "pro"
32493259
| "prolite"
32503260
| "team"
3261+
| "self_serve_business_prolite"
32513262
| "self_serve_business_usage_based"
32523263
| "business"
3264+
| "ent26"
3265+
| "enterprise_cbp_automation"
32533266
| "enterprise_cbp_usage_based"
32543267
| "enterprise"
32553268
| "edu"
3269+
| "edu_plus"
3270+
| "edu_pro"
32563271
| "unknown";
32573272
export const V2AccountRateLimitsUpdatedNotification__PlanType = Schema.Literals([
32583273
"free",
@@ -3261,11 +3276,16 @@ export const V2AccountRateLimitsUpdatedNotification__PlanType = Schema.Literals(
32613276
"pro",
32623277
"prolite",
32633278
"team",
3279+
"self_serve_business_prolite",
32643280
"self_serve_business_usage_based",
32653281
"business",
3282+
"ent26",
3283+
"enterprise_cbp_automation",
32663284
"enterprise_cbp_usage_based",
32673285
"enterprise",
32683286
"edu",
3287+
"edu_plus",
3288+
"edu_pro",
32693289
"unknown",
32703290
]);
32713291

@@ -3336,11 +3356,16 @@ export type V2AccountUpdatedNotification__PlanType =
33363356
| "pro"
33373357
| "prolite"
33383358
| "team"
3359+
| "self_serve_business_prolite"
33393360
| "self_serve_business_usage_based"
33403361
| "business"
3362+
| "ent26"
3363+
| "enterprise_cbp_automation"
33413364
| "enterprise_cbp_usage_based"
33423365
| "enterprise"
33433366
| "edu"
3367+
| "edu_plus"
3368+
| "edu_pro"
33443369
| "unknown";
33453370
export const V2AccountUpdatedNotification__PlanType = Schema.Literals([
33463371
"free",
@@ -3349,11 +3374,16 @@ export const V2AccountUpdatedNotification__PlanType = Schema.Literals([
33493374
"pro",
33503375
"prolite",
33513376
"team",
3377+
"self_serve_business_prolite",
33523378
"self_serve_business_usage_based",
33533379
"business",
3380+
"ent26",
3381+
"enterprise_cbp_automation",
33543382
"enterprise_cbp_usage_based",
33553383
"enterprise",
33563384
"edu",
3385+
"edu_plus",
3386+
"edu_pro",
33573387
"unknown",
33583388
]);
33593389

@@ -4140,11 +4170,16 @@ export type V2GetAccountRateLimitsResponse__PlanType =
41404170
| "pro"
41414171
| "prolite"
41424172
| "team"
4173+
| "self_serve_business_prolite"
41434174
| "self_serve_business_usage_based"
41444175
| "business"
4176+
| "ent26"
4177+
| "enterprise_cbp_automation"
41454178
| "enterprise_cbp_usage_based"
41464179
| "enterprise"
41474180
| "edu"
4181+
| "edu_plus"
4182+
| "edu_pro"
41484183
| "unknown";
41494184
export const V2GetAccountRateLimitsResponse__PlanType = Schema.Literals([
41504185
"free",
@@ -4153,11 +4188,16 @@ export const V2GetAccountRateLimitsResponse__PlanType = Schema.Literals([
41534188
"pro",
41544189
"prolite",
41554190
"team",
4191+
"self_serve_business_prolite",
41564192
"self_serve_business_usage_based",
41574193
"business",
4194+
"ent26",
4195+
"enterprise_cbp_automation",
41584196
"enterprise_cbp_usage_based",
41594197
"enterprise",
41604198
"edu",
4199+
"edu_plus",
4200+
"edu_pro",
41614201
"unknown",
41624202
]);
41634203

@@ -4228,11 +4268,16 @@ export type V2GetAccountResponse__PlanType =
42284268
| "pro"
42294269
| "prolite"
42304270
| "team"
4271+
| "self_serve_business_prolite"
42314272
| "self_serve_business_usage_based"
42324273
| "business"
4274+
| "ent26"
4275+
| "enterprise_cbp_automation"
42334276
| "enterprise_cbp_usage_based"
42344277
| "enterprise"
42354278
| "edu"
4279+
| "edu_plus"
4280+
| "edu_pro"
42364281
| "unknown";
42374282
export const V2GetAccountResponse__PlanType = Schema.Literals([
42384283
"free",
@@ -4241,11 +4286,16 @@ export const V2GetAccountResponse__PlanType = Schema.Literals([
42414286
"pro",
42424287
"prolite",
42434288
"team",
4289+
"self_serve_business_prolite",
42444290
"self_serve_business_usage_based",
42454291
"business",
4292+
"ent26",
4293+
"enterprise_cbp_automation",
42464294
"enterprise_cbp_usage_based",
42474295
"enterprise",
42484296
"edu",
4297+
"edu_plus",
4298+
"edu_pro",
42494299
"unknown",
42504300
]);
42514301

‎packages/effect-codex-app-server/src/schema.test.ts‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import * as Schema from "effect/Schema";
33

44
import * as CodexSchema from "./schema.ts";
55

6+
const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse);
7+
const isAccountPlanType = Schema.is(CodexSchema.V2GetAccountResponse__PlanType);
8+
69
it("accepts Codex 0.150 multi-agent values", () => {
710
const schemas = [
811
CodexSchema.ServerNotification__SubAgentActivityKind,
@@ -71,3 +74,51 @@ it("accepts Codex 0.150 multi-agent values", () => {
7174

7275
assert.equal(Schema.is(CodexSchema.V2ThreadResumeResponse)(resumeResponse), true);
7376
});
77+
78+
it("accepts Codex 0.150 account plan values", () => {
79+
const planTypes = [
80+
"self_serve_business_prolite",
81+
"ent26",
82+
"enterprise_cbp_automation",
83+
"edu_plus",
84+
"edu_pro",
85+
];
86+
87+
// Every generated namespace that carries a plan, not just the one the account
88+
// probe reads: `account/rateLimits/read` decodes through
89+
// `V2GetAccountRateLimitsResponse`, and a namespace missed by a hand-edit to
90+
// the generated schema would otherwise ship green.
91+
const planTypeSchemas = [
92+
CodexSchema.ServerNotification__PlanType,
93+
CodexSchema.V2AccountRateLimitsUpdatedNotification__PlanType,
94+
CodexSchema.V2AccountUpdatedNotification__PlanType,
95+
CodexSchema.V2GetAccountRateLimitsResponse__PlanType,
96+
CodexSchema.V2GetAccountResponse__PlanType,
97+
];
98+
99+
for (const planType of planTypes) {
100+
for (const schema of planTypeSchemas) {
101+
assert.equal(Schema.is(schema)(planType), true);
102+
}
103+
104+
const accountResponse = {
105+
account: {
106+
email: "user@example.com",
107+
planType,
108+
type: "chatgpt",
109+
},
110+
requiresOpenaiAuth: true,
111+
};
112+
113+
assert.equal(isGetAccountResponse(accountResponse), true);
114+
}
115+
});
116+
117+
it("rejects account plans Codex has not published yet", () => {
118+
// Documents today's behaviour rather than endorsing it. `planType` is a closed
119+
// literal on a required field and `account/read` is a bare `yield*`, so an
120+
// unrecognized plan fails the whole Codex probe instead of degrading one
121+
// label. Widening the decode is a separate decision; this test will fail
122+
// loudly when that decision is made.
123+
assert.equal(isAccountPlanType("plan_from_the_future"), false);
124+
});

0 commit comments

Comments
 (0)