@opengeni/codex 6.7.0 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  export type CodexEntitlementRejectionEvidence =
2
2
  /** HTTP 400 with no body at all: ambiguous until the plan is re-checked. */
3
3
  "empty_body"
4
- /** The provider explicitly named the plan, subscription, or model access. */
4
+ /** The provider explicitly named the plan, subscription, or the account's model access. */
5
5
  | "plan_entitlement";
6
6
  export type CodexEntitlementRejection = {
7
7
  status: 400 | 403 | 429;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/codex",
3
- "version": "6.7.0",
3
+ "version": "6.8.0",
4
4
  "description": "ChatGPT/Codex subscription auth + transport: device-code login, token refresh, and the Responses-backend fetch. Pure HTTP + transforms; no database dependency.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -41,8 +41,8 @@
41
41
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
42
42
  },
43
43
  "dependencies": {
44
- "@opengeni/contracts": "6.7.0",
45
- "@opengeni/network": "6.7.0",
44
+ "@opengeni/contracts": "6.8.0",
45
+ "@opengeni/network": "6.8.0",
46
46
  "zod": "^4.2.1"
47
47
  },
48
48
  "engines": {
@@ -17,7 +17,7 @@ import {
17
17
  export type CodexEntitlementRejectionEvidence =
18
18
  /** HTTP 400 with no body at all: ambiguous until the plan is re-checked. */
19
19
  | "empty_body"
20
- /** The provider explicitly named the plan, subscription, or model access. */
20
+ /** The provider explicitly named the plan, subscription, or the account's model access. */
21
21
  | "plan_entitlement";
22
22
 
23
23
  export type CodexEntitlementRejection = {
@@ -39,6 +39,18 @@ const CODEX_PLAN_ENTITLEMENT_CODES = new Set([
39
39
  const PLAN_ENTITLEMENT_MESSAGE =
40
40
  /\b(?:not (?:available|included|supported|enabled) (?:on|in|for|with) (?:your|this|the|a) (?:current )?(?:chatgpt )?(?:plan|subscription|tier)|upgrade (?:your (?:chatgpt )?(?:plan|subscription)|to (?:a |the )?(?:paid|plus|pro|team|business|enterprise)\b)|(?:plan|subscription|tier) (?:does not|doesn't) (?:include|support|allow)|requires? (?:a |an )?(?:paid|plus|pro|team|business|enterprise|higher) (?:chatgpt )?(?:plan|subscription|tier))/i;
41
41
 
42
+ /**
43
+ * The Codex backend's refusal of a model this ChatGPT account cannot use
44
+ * (its plan or workspace settings): HTTP 400 "The 'gpt-6-luna' model is not
45
+ * supported when using Codex with a ChatGPT account." (openai/codex#47784).
46
+ * The account may serve other models, so this is model access, not a broken
47
+ * credential. A 404 "Model not found" is deliberately not included: it also
48
+ * means a client below the model's minimum version or a rollout in progress
49
+ * (openai/codex#35904), so it keeps its ordinary visible turn error instead
50
+ * of a per-account cooldown that blames the plan.
51
+ */
52
+ const MODEL_ACCESS_MESSAGE = /\bmodel is not supported when using codex with a chatgpt account\b/i;
53
+
42
54
  function stringField(value: unknown): string {
43
55
  return typeof value === "string" ? value : "";
44
56
  }
@@ -71,6 +83,14 @@ export function classifyCodexEntitlementRejection(
71
83
  ? (value.error as Record<string, unknown>)
72
84
  : null;
73
85
  const status = Number(value.status ?? value.statusCode ?? body?.status);
86
+ const message = [
87
+ stringField(value.message),
88
+ stringField(body?.message),
89
+ stringField(body?.detail),
90
+ ].join(" ");
91
+ if (status === 400 && MODEL_ACCESS_MESSAGE.test(message)) {
92
+ return { status, evidence: "plan_entitlement" };
93
+ }
74
94
  if (status === 400 || status === 403 || status === 429) {
75
95
  const codes = [value.code, value.type, body?.code, body?.type]
76
96
  .map((field) => stringField(field).toLowerCase())
@@ -79,7 +99,6 @@ export function classifyCodexEntitlementRejection(
79
99
  return { status, evidence: "plan_entitlement" };
80
100
  }
81
101
  if (!usageLimit) {
82
- const message = [stringField(value.message), stringField(body?.message)].join(" ");
83
102
  if (status !== 429 && PLAN_ENTITLEMENT_MESSAGE.test(message)) {
84
103
  return { status, evidence: "plan_entitlement" };
85
104
  }