| author | |
| committer | |
| log | 95139254ba98fb8ad12a001ebcbc9d4b833ead19 |
| tree | 29d8996da2ab9f222e56756e6c2d6f96bfa527f2 |
| parent | d6aa179ad2afe0424e1410cbafbb119bbd5f7809 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 107 insertions(+), 10 deletions(-)
packages/effect-codex-app-server/scripts/generate.ts+33-10| ... | @@ -194,17 +194,28 @@ const Codex0150DefinitionSchemas: Record<string, Schema.Json> = { | ... | @@ -194,17 +194,28 @@ const Codex0150DefinitionSchemas: Record<string, Schema.Json> = { |
| 194 | }, | 194 | }, |
| 195 | }; | 195 | }; |
| 196 | 196 | ||
| 197 | // Pinned protocol JSON omits later CodexErrorInfo variants. Keep historical | ||
| 198 | // thread payloads decodable; do not fold unknown values into "other". | ||
| 199 | const CodexErrorInfoCompatibilityValues = [ | ||
| 200 | "rateLimitExceeded", | ||
| 201 | "misalignmentPolicyViolation", | ||
| 202 | ] as const; | ||
| 203 | |||
| 204 | const CodexErrorInfoCompatibilityExports = new Set([ | ||
| 205 | "V2ThreadReadResponse", | ||
| 206 | "V2ThreadResumeResponse", | ||
| 207 | "V2ThreadRollbackResponse", | ||
| 208 | "V2ThreadForkResponse", | ||
| 209 | "V2TurnCompletedNotification", | ||
| 210 | ]); | ||
| 211 | |||
| 197 | function applyCodex0151DefinitionCompatibility( | 212 | function applyCodex0151DefinitionCompatibility( |
| 198 | exportName: string, | 213 | exportName: string, |
| 199 | definitionName: string, | 214 | definitionName: string, |
| 200 | definitionSchema: Schema.Json, | 215 | definitionSchema: Schema.Json, |
| 201 | ): Schema.Json { | 216 | ): Schema.Json { |
| 202 | const isThreadResponse = | ||
| 203 | exportName === "V2ThreadReadResponse" || | ||
| 204 | exportName === "V2ThreadResumeResponse" || | ||
| 205 | exportName === "V2ThreadRollbackResponse"; | ||
| 206 | if ( | 217 | if ( |
| 207 | !isThreadResponse || | 218 | !CodexErrorInfoCompatibilityExports.has(exportName) || |
| 208 | definitionName !== "CodexErrorInfo" || | 219 | definitionName !== "CodexErrorInfo" || |
| 209 | typeof definitionSchema !== "object" | 220 | typeof definitionSchema !== "object" |
| 210 | ) { | 221 | ) { |
| ... | @@ -215,16 +226,28 @@ function applyCodex0151DefinitionCompatibility( | ... | @@ -215,16 +226,28 @@ function applyCodex0151DefinitionCompatibility( |
| 215 | readonly oneOf?: ReadonlyArray<{ readonly enum?: ReadonlyArray<string> }>; | 226 | readonly oneOf?: ReadonlyArray<{ readonly enum?: ReadonlyArray<string> }>; |
| 216 | }; | 227 | }; |
| 217 | const [firstVariant, ...remainingVariants] = schema.oneOf ?? []; | 228 | const [firstVariant, ...remainingVariants] = schema.oneOf ?? []; |
| 218 | if (!firstVariant?.enum || firstVariant.enum.includes("rateLimitExceeded")) { | 229 | const currentEnum = firstVariant?.enum; |
| 230 | if (!currentEnum) { | ||
| 231 | return definitionSchema; | ||
| 232 | } | ||
| 233 | |||
| 234 | const missingValues = CodexErrorInfoCompatibilityValues.filter( | ||
| 235 | (value) => !currentEnum.includes(value), | ||
| 236 | ); | ||
| 237 | if (missingValues.length === 0) { | ||
| 219 | return definitionSchema; | 238 | return definitionSchema; |
| 220 | } | 239 | } |
| 221 | 240 | ||
| 241 | const enumValues = [...currentEnum]; | ||
| 242 | const otherIndex = enumValues.indexOf("other"); | ||
| 243 | const nextEnum = | ||
| 244 | otherIndex === -1 | ||
| 245 | ? [...enumValues, ...missingValues] | ||
| 246 | : [...enumValues.slice(0, otherIndex), ...missingValues, ...enumValues.slice(otherIndex)]; | ||
| 247 | |||
| 222 | return { | 248 | return { |
| 223 | ...definitionSchema, | 249 | ...definitionSchema, |
| 224 | oneOf: [ | 250 | oneOf: [{ ...firstVariant, enum: nextEnum }, ...remainingVariants], |
| 225 | { ...firstVariant, enum: [...firstVariant.enum, "rateLimitExceeded"] }, | ||
| 226 | ...remainingVariants, | ||
| 227 | ], | ||
| 228 | }; | 251 | }; |
| 229 | } | 252 | } |
| 230 | 253 |
packages/effect-codex-app-server/src/_generated/schema.gen.ts+14| ... | @@ -15947,6 +15947,8 @@ export type V2ThreadForkResponse__CodexErrorInfo = | ... | @@ -15947,6 +15947,8 @@ export type V2ThreadForkResponse__CodexErrorInfo = |
| 15947 | | "badRequest" | 15947 | | "badRequest" |
| 15948 | | "threadRollbackFailed" | 15948 | | "threadRollbackFailed" |
| 15949 | | "sandboxError" | 15949 | | "sandboxError" |
| 15950 | | "rateLimitExceeded" | ||
| 15951 | | "misalignmentPolicyViolation" | ||
| 15950 | | "other" | 15952 | | "other" |
| 15951 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | 15953 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } |
| 15952 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } | 15954 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } |
| ... | @@ -15970,6 +15972,8 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( | ... | @@ -15970,6 +15972,8 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( |
| 15970 | "badRequest", | 15972 | "badRequest", |
| 15971 | "threadRollbackFailed", | 15973 | "threadRollbackFailed", |
| 15972 | "sandboxError", | 15974 | "sandboxError", |
| 15975 | "rateLimitExceeded", | ||
| 15976 | "misalignmentPolicyViolation", | ||
| 15973 | "other", | 15977 | "other", |
| 15974 | ]), | 15978 | ]), |
| 15975 | Schema.Struct({ | 15979 | Schema.Struct({ |
| ... | @@ -16855,6 +16859,7 @@ export type V2ThreadReadResponse__CodexErrorInfo = | ... | @@ -16855,6 +16859,7 @@ export type V2ThreadReadResponse__CodexErrorInfo = |
| 16855 | | "threadRollbackFailed" | 16859 | | "threadRollbackFailed" |
| 16856 | | "sandboxError" | 16860 | | "sandboxError" |
| 16857 | | "rateLimitExceeded" | 16861 | | "rateLimitExceeded" |
| 16862 | | "misalignmentPolicyViolation" | ||
| 16858 | | "other" | 16863 | | "other" |
| 16859 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | 16864 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } |
| 16860 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } | 16865 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } |
| ... | @@ -16879,6 +16884,7 @@ export const V2ThreadReadResponse__CodexErrorInfo = Schema.Union( | ... | @@ -16879,6 +16884,7 @@ export const V2ThreadReadResponse__CodexErrorInfo = Schema.Union( |
| 16879 | "threadRollbackFailed", | 16884 | "threadRollbackFailed", |
| 16880 | "sandboxError", | 16885 | "sandboxError", |
| 16881 | "rateLimitExceeded", | 16886 | "rateLimitExceeded", |
| 16887 | "misalignmentPolicyViolation", | ||
| 16882 | "other", | 16888 | "other", |
| 16883 | ]), | 16889 | ]), |
| 16884 | Schema.Struct({ | 16890 | Schema.Struct({ |
| ... | @@ -17210,6 +17216,7 @@ export type V2ThreadResumeResponse__CodexErrorInfo = | ... | @@ -17210,6 +17216,7 @@ export type V2ThreadResumeResponse__CodexErrorInfo = |
| 17210 | | "threadRollbackFailed" | 17216 | | "threadRollbackFailed" |
| 17211 | | "sandboxError" | 17217 | | "sandboxError" |
| 17212 | | "rateLimitExceeded" | 17218 | | "rateLimitExceeded" |
| 17219 | | "misalignmentPolicyViolation" | ||
| 17213 | | "other" | 17220 | | "other" |
| 17214 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | 17221 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } |
| 17215 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } | 17222 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } |
| ... | @@ -17234,6 +17241,7 @@ export const V2ThreadResumeResponse__CodexErrorInfo = Schema.Union( | ... | @@ -17234,6 +17241,7 @@ export const V2ThreadResumeResponse__CodexErrorInfo = Schema.Union( |
| 17234 | "threadRollbackFailed", | 17241 | "threadRollbackFailed", |
| 17235 | "sandboxError", | 17242 | "sandboxError", |
| 17236 | "rateLimitExceeded", | 17243 | "rateLimitExceeded", |
| 17244 | "misalignmentPolicyViolation", | ||
| 17237 | "other", | 17245 | "other", |
| 17238 | ]), | 17246 | ]), |
| 17239 | Schema.Struct({ | 17247 | Schema.Struct({ |
| ... | @@ -17490,6 +17498,7 @@ export type V2ThreadRollbackResponse__CodexErrorInfo = | ... | @@ -17490,6 +17498,7 @@ export type V2ThreadRollbackResponse__CodexErrorInfo = |
| 17490 | | "threadRollbackFailed" | 17498 | | "threadRollbackFailed" |
| 17491 | | "sandboxError" | 17499 | | "sandboxError" |
| 17492 | | "rateLimitExceeded" | 17500 | | "rateLimitExceeded" |
| 17501 | | "misalignmentPolicyViolation" | ||
| 17493 | | "other" | 17502 | | "other" |
| 17494 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | 17503 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } |
| 17495 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } | 17504 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } |
| ... | @@ -17514,6 +17523,7 @@ export const V2ThreadRollbackResponse__CodexErrorInfo = Schema.Union( | ... | @@ -17514,6 +17523,7 @@ export const V2ThreadRollbackResponse__CodexErrorInfo = Schema.Union( |
| 17514 | "threadRollbackFailed", | 17523 | "threadRollbackFailed", |
| 17515 | "sandboxError", | 17524 | "sandboxError", |
| 17516 | "rateLimitExceeded", | 17525 | "rateLimitExceeded", |
| 17526 | "misalignmentPolicyViolation", | ||
| 17517 | "other", | 17527 | "other", |
| 17518 | ]), | 17528 | ]), |
| 17519 | Schema.Struct({ | 17529 | Schema.Struct({ |
| ... | @@ -18713,6 +18723,8 @@ export type V2TurnCompletedNotification__CodexErrorInfo = | ... | @@ -18713,6 +18723,8 @@ export type V2TurnCompletedNotification__CodexErrorInfo = |
| 18713 | | "badRequest" | 18723 | | "badRequest" |
| 18714 | | "threadRollbackFailed" | 18724 | | "threadRollbackFailed" |
| 18715 | | "sandboxError" | 18725 | | "sandboxError" |
| 18726 | | "rateLimitExceeded" | ||
| 18727 | | "misalignmentPolicyViolation" | ||
| 18716 | | "other" | 18728 | | "other" |
| 18717 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | 18729 | | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } |
| 18718 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } | 18730 | | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } |
| ... | @@ -18736,6 +18748,8 @@ export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union( | ... | @@ -18736,6 +18748,8 @@ export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union( |
| 18736 | "badRequest", | 18748 | "badRequest", |
| 18737 | "threadRollbackFailed", | 18749 | "threadRollbackFailed", |
| 18738 | "sandboxError", | 18750 | "sandboxError", |
| 18751 | "rateLimitExceeded", | ||
| 18752 | "misalignmentPolicyViolation", | ||
| 18739 | "other", | 18753 | "other", |
| 18740 | ]), | 18754 | ]), |
| 18741 | Schema.Struct({ | 18755 | Schema.Struct({ |
packages/effect-codex-app-server/src/schema.test.ts+60| ... | @@ -7,6 +7,9 @@ const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse); | ... | @@ -7,6 +7,9 @@ const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse); |
| 7 | const isThreadReadResponse = Schema.is(CodexSchema.V2ThreadReadResponse); | 7 | const isThreadReadResponse = Schema.is(CodexSchema.V2ThreadReadResponse); |
| 8 | const isThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse); | 8 | const isThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse); |
| 9 | const isThreadRollbackResponse = Schema.is(CodexSchema.V2ThreadRollbackResponse); | 9 | const isThreadRollbackResponse = Schema.is(CodexSchema.V2ThreadRollbackResponse); |
| 10 | const isThreadForkResponse = Schema.is(CodexSchema.V2ThreadForkResponse); | ||
| 11 | const isTurnCompletedNotification = Schema.is(CodexSchema.V2TurnCompletedNotification); | ||
| 12 | const decodeThreadResumeResponse = Schema.decodeUnknownSync(CodexSchema.V2ThreadResumeResponse); | ||
| 10 | 13 | ||
| 11 | it("keeps async questions in live notifications and thread history", () => { | 14 | it("keeps async questions in live notifications and thread history", () => { |
| 12 | const item = { | 15 | const item = { |
| ... | @@ -141,6 +144,63 @@ it("accepts Codex rate limit errors for thread responses", () => { | ... | @@ -141,6 +144,63 @@ it("accepts Codex rate limit errors for thread responses", () => { |
| 141 | assert.equal(isThreadRollbackResponse({ thread: failedThread }), true); | 144 | assert.equal(isThreadRollbackResponse({ thread: failedThread }), true); |
| 142 | }); | 145 | }); |
| 143 | 146 | ||
| 147 | it("accepts Codex misalignment policy errors for thread responses", () => { | ||
| 148 | const failedThread = { | ||
| 149 | cliVersion: "0.150.0", | ||
| 150 | createdAt: 0, | ||
| 151 | cwd: "/tmp/project", | ||
| 152 | ephemeral: false, | ||
| 153 | id: "thread-1", | ||
| 154 | modelProvider: "openai", | ||
| 155 | preview: "", | ||
| 156 | sessionId: "session-1", | ||
| 157 | source: "cli", | ||
| 158 | status: { type: "idle" }, | ||
| 159 | turns: [ | ||
| 160 | { | ||
| 161 | error: { | ||
| 162 | codexErrorInfo: "misalignmentPolicyViolation", | ||
| 163 | message: "Misalignment policy violation", | ||
| 164 | }, | ||
| 165 | id: "turn-1", | ||
| 166 | items: [], | ||
| 167 | status: "failed", | ||
| 168 | }, | ||
| 169 | ], | ||
| 170 | updatedAt: 0, | ||
| 171 | }; | ||
| 172 | const resumeLikeResponse = { | ||
| 173 | approvalPolicy: "never", | ||
| 174 | approvalsReviewer: "user", | ||
| 175 | cwd: "/tmp/project", | ||
| 176 | model: "gpt-5.6-sol", | ||
| 177 | modelProvider: "openai", | ||
| 178 | sandbox: { type: "dangerFullAccess" }, | ||
| 179 | thread: failedThread, | ||
| 180 | }; | ||
| 181 | assert.equal(isThreadReadResponse({ thread: failedThread }), true); | ||
| 182 | assert.equal(isThreadResumeResponse(resumeLikeResponse), true); | ||
| 183 | assert.equal(isThreadRollbackResponse({ thread: failedThread }), true); | ||
| 184 | assert.equal(isThreadForkResponse(resumeLikeResponse), true); | ||
| 185 | const decodedResume = decodeThreadResumeResponse(resumeLikeResponse); | ||
| 186 | assert.equal(decodedResume.thread.turns[0]?.error?.codexErrorInfo, "misalignmentPolicyViolation"); | ||
| 187 | assert.equal( | ||
| 188 | isTurnCompletedNotification({ | ||
| 189 | threadId: "thread-1", | ||
| 190 | turn: { | ||
| 191 | error: { | ||
| 192 | codexErrorInfo: "misalignmentPolicyViolation", | ||
| 193 | message: "Misalignment policy violation", | ||
| 194 | }, | ||
| 195 | id: "turn-1", | ||
| 196 | items: [], | ||
| 197 | status: "failed", | ||
| 198 | }, | ||
| 199 | }), | ||
| 200 | true, | ||
| 201 | ); | ||
| 202 | }); | ||
| 203 | |||
| 144 | it("accepts Codex 0.150 account plan values", () => { | 204 | it("accepts Codex 0.150 account plan values", () => { |
| 145 | const planTypes = [ | 205 | const planTypes = [ |
| 146 | "self_serve_business_prolite", | 206 | "self_serve_business_prolite", |