# Files changed · PR \#17865 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/17865/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(codex\): restore reasoning controls for ChatGPT account models](https://git.cafe/pingdotgg/t3code/pull/17865.md)

Version: 8

Head commit: dde935ccdcdc7bd2ed6f03ba4a32015c3a2b1bb8

Base commit: 4af66184ef832c4939f331725585dc70f0c8a3c4

Comparison base: 4af66184ef832c4939f331725585dc70f0c8a3c4

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/server/src/provider/CodexChatGptModels.test.ts

Status: modified; +152; −0

```diff
@@ -1,9 +1,161 @@
 import { assert, it } from "@effect/vitest";
+import type { ServerProviderModel } from "@t3tools/contracts";
+import {
+  buildExplicitProviderOptionSelectionsFromDescriptors,
+  getProviderOptionDescriptors,
+} from "@t3tools/shared/model";
 import * as Effect from "effect/Effect";
 import { HttpClient, HttpClientResponse } from "effect/http";
 import { chatGptModels } from "./CodexChatGptModels.ts";
+import { mapCodexModelCapabilities } from "./CodexProvider.ts";
+
+const accountModel = {
+  slug: "gpt-6-astra",
+  display_name: "GPT-6-Astra",
+  visibility: "list",
+  default_reasoning_level: "low",
+  supported_reasoning_levels: [{ effort: "low" }, { effort: "medium" }, { effort: "high" }],
+};
+
+const catalogClient = (models: ReadonlyArray<unknown>) =>
+  HttpClient.make((request) =>
+    Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ models }))),
+  );
 
+it.effect("restores account reasoning controls with catalog defaults and explicit selections", () =>
+  Effect.gen(function* () {
+    const models = yield* chatGptModels("account-a", []).pipe(
+      Effect.provideService(HttpClient.HttpClient, catalogClient([accountModel])),
+    );
+    const caps = models[0]!.capabilities!;
+    assert.deepStrictEqual(caps.optionDescriptors, [
+      {
+        id: "reasoningEffort",
+        label: "Reasoning",
+        type: "select",
+        options: [
+          { id: "low", label: "Low" },
+          { id: "medium", label: "Medium", isDefault: true },
+          { id: "high", label: "High" },
+        ],
+        currentValue: "medium",
+      },
+    ]);
+    assert.isUndefined(
+      buildExplicitProviderOptionSelectionsFromDescriptors(
+        getProviderOptionDescriptors({ caps }),
+        undefined,
+      ),
+    );
+    const selections = [{ id: "reasoningEffort", value: "high" }];
+    assert.deepStrictEqual(
+      buildExplicitProviderOptionSelectionsFromDescriptors(
+        getProviderOptionDescriptors({ caps, selections }),
+        selections,
+      ),
+      selections,
+    );
+  }),
+);
+
 it.effect(
+  "fills only missing reasoning controls and preserves native Astra defaults and options",
+  () =>
+    Effect.gen(function* () {
+      const capabilities = mapCodexModelCapabilities({
+        model: accountModel.slug,
+        id: accountModel.slug,
+        displayName: accountModel.display_name,
+        description: "Test model",
+        hidden: false,
+        isDefault: false,
+        defaultReasoningEffort: "low",
+        supportedReasoningEfforts: accountModel.supported_reasoning_levels.map(({ effort }) => ({
+          reasoningEffort: effort,
+          description: effort,
+        })),
+        additionalSpeedTiers: ["fast"],
+      });
+      assert.strictEqual(capabilities.optionDescriptors![0]!.currentValue, "medium");
+      const otherOptions = capabilities.optionDescriptors!.filter(
+        ({ id }) => id !== "reasoningEffort",
+      );
+      assert.isNotEmpty(otherOptions);
+      const native: ServerProviderModel[] = [
+        { slug: accountModel.slug, name: "Native Astra", isCustom: false, capabilities },
+        {
+          slug: "missing-reasoning",
+          name: "Native model",
+          isCustom: false,
+          capabilities: { optionDescriptors: otherOptions },
+        },
+      ];
+      const models = yield* chatGptModels("account-a", native).pipe(
+        Effect.provideService(
+          HttpClient.HttpClient,
+          catalogClient([accountModel, { ...accountModel, slug: "missing-reasoning" }]),
+        ),
+      );
+      assert.deepStrictEqual(models[0]!.capabilities, capabilities);
+      const descriptors = models[1]!.capabilities!.optionDescriptors!;
+      assert.strictEqual(
+        descriptors.find(({ id }) => id === "reasoningEffort")?.currentValue,
+        "low",
+      );
+      assert.deepStrictEqual(
+        descriptors.filter(({ id }) => id !== "reasoningEffort"),
+        otherOptions,
+      );
+    }),
+);
+
+it.effect(
+  "isolates malformed optional metadata without dropping visible models or inventing defaults",
+  () =>
+    Effect.gen(function* () {
+      const models = yield* chatGptModels("account-a", []).pipe(
+        Effect.provideService(
+          HttpClient.HttpClient,
+          catalogClient([
+            accountModel,
+            {
+              ...accountModel,
+              slug: "invalid-levels",
+              supported_reasoning_levels: [{ effort: null }],
+            },
+            {
+              ...accountModel,
+              slug: "invalid-default",
+              default_reasoning_level: {},
+              supported_reasoning_levels: [{ effort: "future-effort" }],
+            },
+            {
+              ...accountModel,
+              slug: "hidden",
+              visibility: "hidden",
+              supported_reasoning_levels: null,
+            },
+          ]),
+        ),
+      );
+      assert.deepStrictEqual(
+        models.map(({ slug }) => slug),
+        [accountModel.slug, "invalid-levels", "invalid-default"],
+      );
+      assert.strictEqual(models[0]!.capabilities!.optionDescriptors![0]!.currentValue, "medium");
+      assert.isNull(models[1]!.capabilities);
+      assert.deepStrictEqual(models[2]!.capabilities!.optionDescriptors, [
+        {
+          id: "reasoningEffort",
+          label: "Reasoning",
+          type: "select",
+          options: [{ id: "future-effort", label: "future-effort" }],
+        },
+      ]);
+    }),
+);
+
+it.effect(
   "uses each selected profile's token and the server's visible catalog order and names",
   () =>
     Effect.gen(function* () {
```

## apps/server/src/provider/CodexChatGptModels.ts

Status: modified; +47; −11

```diff
@@ -1,7 +1,13 @@
 import type { ServerProviderModel } from "@t3tools/contracts";
+import { createModelCapabilities } from "@t3tools/shared/model";
 import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
 import * as Schema from "effect/Schema";
 import { HttpClient, HttpClientRequest } from "effect/http";
+import {
+  codexDefaultReasoningEffortOverride,
+  mapCodexReasoningEffortDescriptor,
+} from "./CodexProvider.ts";
 
 export class ChatGptCatalogError extends Schema.TaggedError<ChatGptCatalogError>()(
   "ChatGptCatalogError",
@@ -14,10 +20,16 @@
       slug: Schema.NonEmptyString,
       display_name: Schema.NonEmptyString,
       visibility: Schema.String,
+      default_reasoning_level: Schema.optionalKey(Schema.Unknown),
+      supported_reasoning_levels: Schema.optionalKey(Schema.Unknown),
     }),
   ),
 });
 
+const decodeReasoningLevels = Schema.decodeUnknownOption(
+  Schema.Array(Schema.Struct({ effort: Schema.NonEmptyString })),
+);
+
 export const chatGptModels = Effect.fn("chatGptModels")(function* (
   accessToken: string,
   nativeModels: ReadonlyArray<ServerProviderModel>,
@@ -32,15 +44,39 @@
   const catalog = yield* response.json.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Catalog)));
   return catalog.models
     .filter((model) => model.visibility === "list")
+    .map((model) => {
+      const native = nativeModels.find((candidate) => candidate.slug === model.slug);
+      let capabilities = native?.capabilities ?? null;
-    .map(
-      (model) =>
-        ({
-          ...nativeModels.find((native) => native.slug === model.slug),
-          capabilities:
-            nativeModels.find((native) => native.slug === model.slug)?.capabilities ?? null,
-          slug: model.slug,
-          name: model.display_name,
+      if (
+        !capabilities?.optionDescriptors?.some((descriptor) => descriptor.id === "reasoningEffort")
+      ) {
-          isCustom: false,
-        }) satisfies ServerProviderModel,
-    );
+        const reasoningLevels = Option.getOrElse(
+          decodeReasoningLevels(model.supported_reasoning_levels),
+          () => [],
+        );
+        const defaultReasoningEffort =
+          codexDefaultReasoningEffortOverride(model.slug) ??
+          (typeof model.default_reasoning_level === "string"
+            ? model.default_reasoning_level
+            : undefined);
+        const reasoningDescriptor = mapCodexReasoningEffortDescriptor({
+          ...(defaultReasoningEffort === undefined ? {} : { defaultReasoningEffort }),
+          supportedReasoningEfforts: reasoningLevels.map((level) => ({
+            reasoningEffort: level.effort,
+          })),
+        });
+        if (reasoningDescriptor) {
+          capabilities = createModelCapabilities({
+            optionDescriptors: [reasoningDescriptor, ...(capabilities?.optionDescriptors ?? [])],
+          });
+        }
+      }
+      return {
+        ...native,
+        capabilities,
+        slug: model.slug,
+        name: model.display_name,
+        isCustom: false,
+      } satisfies ServerProviderModel;
+    });
 }, Effect.timeout("15 seconds"));
```

## apps/server/src/provider/CodexProvider.ts

Status: modified; +24; −14

```diff
@@ -165,12 +165,12 @@
   return codexModelFamily(model) === "gpt-6-astra" ? "medium" : undefined;
 }
 
+export function mapCodexReasoningEffortDescriptor(model: {
+  readonly supportedReasoningEfforts: ReadonlyArray<{ readonly reasoningEffort: string }>;
-export function mapCodexModelCapabilities(
-  model: CodexSchema.V2ModelListResponse__Model,
-): ModelCapabilities {
+  readonly defaultReasoningEffort?: string;
+}) {
   const reasoningOptions = model.supportedReasoningEfforts.map(({ reasoningEffort }) =>
+    reasoningEffort === model.defaultReasoningEffort
-    reasoningEffort ===
-    (codexDefaultReasoningEffortOverride(model.model) ?? model.defaultReasoningEffort)
       ? {
           id: reasoningEffort,
           label: reasoningEffortLabel(reasoningEffort),
@@ -181,7 +181,25 @@
           label: reasoningEffortLabel(reasoningEffort),
         },
   );
+  if (reasoningOptions.length === 0) return undefined;
   const defaultReasoning = reasoningOptions.find((option) => option.isDefault)?.id;
+  return {
+    id: "reasoningEffort",
+    label: "Reasoning",
+    type: "select" as const,
+    options: reasoningOptions,
+    ...(defaultReasoning ? { currentValue: defaultReasoning } : {}),
+  };
+}
+
+export function mapCodexModelCapabilities(
+  model: CodexSchema.V2ModelListResponse__Model,
+): ModelCapabilities {
+  const reasoningDescriptor = mapCodexReasoningEffortDescriptor({
+    supportedReasoningEfforts: model.supportedReasoningEfforts,
+    defaultReasoningEffort:
+      codexDefaultReasoningEffortOverride(model.model) ?? model.defaultReasoningEffort,
+  });
   const serviceTiers =
     model.serviceTiers && model.serviceTiers.length > 0
       ? model.serviceTiers
@@ -198,15 +216,7 @@
   const defaultServiceTier = catalogDefaultServiceTier ?? DEFAULT_SERVICE_TIER_ID;
   const optionDescriptors: ProviderOptionDescriptor[] = [];
 
+  if (reasoningDescriptor) optionDescriptors.push(reasoningDescriptor);
-  if (reasoningOptions.length > 0) {
-    optionDescriptors.push({
-      id: "reasoningEffort",
-      label: "Reasoning",
-      type: "select",
-      options: reasoningOptions,
-      ...(defaultReasoning ? { currentValue: defaultReasoning } : {}),
-    });
-  }
   if (serviceTiers.length > 0) {
     optionDescriptors.push({
       id: "serviceTier",
```
