Skip to content

fix(config): accept model thinkingLevelMap in provider config schema (#91011) [AI-assisted]#91045

Closed
harjothkhara wants to merge 2 commits into
openclaw:mainfrom
harjothkhara:fix/foundry-thinking-level-map-schema
Closed

fix(config): accept model thinkingLevelMap in provider config schema (#91011) [AI-assisted]#91045
harjothkhara wants to merge 2 commits into
openclaw:mainfrom
harjothkhara:fix/foundry-thinking-level-map-schema

Conversation

@harjothkhara

@harjothkhara harjothkhara commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

The strict Zod schema that validates persisted provider model entries (models.providers.*.models[]) did not allow a thinkingLevelMap field, even though it is a first-class part of the model definition (present on the runtime type and the read-path TypeBox schema). As a result, any config write that includes a model thinkingLevelMap failed .strict() validation and the atomic write rolled back.

This bites the built-in Microsoft Foundry Entra ID onboarding: its auth writer emits thinkingLevelMap on each model entry, so onboarding passes the connection test and then can't persist — leaving the provider unconfigured with no supported way to finish (#91011).

Root cause

  • thinkingLevelMap is part of the model definition: runtime type ThinkingLevelMap (packages/llm-core/src/types.ts) and the read-path schema ThinkingLevelMapSchema (src/agents/sessions/model-registry.ts).
  • But the write-path Zod schema ModelDefinitionSchema (src/config/zod-schema.core.ts), which validates models.providers.*.models[] on save, is .strict() and was missing the key → unrecognized key → the atomic config write rolls back. (normalizeResolvedModel re-adds the field in memory at request time, so it never surfaces on the read path — only on write.)

Changes

  • src/config/zod-schema.core.ts — add ModelThinkingLevelMapSchema (mirrors the canonical 7 ModelThinkingLevel keys exactly: off/minimal/low/medium/high/xhigh/max, values string | null, strict) and wire it into ModelDefinitionSchema. Add bidirectional AssertAssignable guards tying z.infer<…> to the canonical ThinkingLevelMap type so the two can't drift.
  • src/config/zod-schema.models.test.ts — schema accepts a provider model with thinkingLevelMap.
  • extensions/microsoft-foundry/index.test.ts — Foundry auth writer emits the thinkingLevelMap shape on its model entry.
  • docs/.generated/config-baseline.sha256 — regenerated config doc baseline to reflect the new schema field (pnpm config:docs:gen).

One logical change; no new dependency.

Verification (local, Node 22)

node scripts/run-vitest.mjs src/config/zod-schema.models.test.ts        # 2/2 pass
node scripts/run-vitest.mjs extensions/microsoft-foundry/index.test.ts  # 41/41 pass
pnpm tsgo:core                                                          # exit 0 (type guards compile)
pnpm config:schema:check                                                # ok
pnpm config:docs:check                                                  # OK (baseline regenerated)

Real behavior proof

Reproduced end-to-end through the real openclaw config validate command (no Azure/Entra account required — the bug is the config write/validate path).

Behavior addressed: persisted provider model config containing thinkingLevelMap was rejected by strict validation, rolling back the atomic write (blocks Microsoft Foundry Entra onboarding from persisting).

Real environment tested: local OpenClaw CLI built from source on macOS arm64, Node v22.22.3, run against an isolated config file via OPENCLAW_CONFIG_PATH.

Exact steps or command run after this patch: wrote /tmp/openclaw-foundry-repro.json with a microsoft-foundry provider model carrying a thinkingLevelMap (the shape the Entra onboarding writer emits), then ran the real CLI command:

OPENCLAW_CONFIG_PATH=/tmp/openclaw-foundry-repro.json pnpm openclaw config validate

Config file used:

{
  "models": {
    "providers": {
      "microsoft-foundry": {
        "baseUrl": "https://example.services.ai.azure.com/openai/v1",
        "api": "openai-completions",
        "models": [
          { "id": "gpt-5.4", "name": "gpt-5.4", "reasoning": true,
            "thinkingLevelMap": { "off": "none", "minimal": null, "low": "low", "medium": "medium", "high": "high" } }
        ]
      }
    }
  }
}

Before evidence: on main (unfixed), the same command rejected the config and exited 1:

OpenClaw config is invalid: /tmp/openclaw-foundry-repro.json
  × models.providers.microsoft-foundry.models.0: Invalid input
Run `openclaw doctor --fix` to repair, or fix the keys above manually.
# exit code 1

(Same rejection the issue reporter saw as Unrecognized key: "thinkingLevelMap".)

Evidence after fix: terminal output of the same command on this branch:

Config valid: /tmp/openclaw-foundry-repro.json
# exit code 0

Observed result after fix: the config with a model thinkingLevelMap now passes validation and persists; the onboarding write no longer rolls back.

What was not tested: the live Azure Foundry Entra managed-identity onboarding flow (no Entra resource available). Validated through the config write/validate path that the onboarding writer feeds, which is exactly where the rollback occurred.

AI-assisted

This change was AI-assisted. It was implemented by one model and independently reviewed by a different model (code read end-to-end; findings applied: removed a test boundary-escaping import, dropped a non-canonical schema key, added the type guards, regenerated the config baseline). I understand the code and ran the proof above myself. Prompts/session log available on request.

Closes #91011

Notes

Net: +72 / −3 across 4 files (1 schema, 2 tests, 1 generated config baseline).

@openclaw-barnacle openclaw-barnacle Bot added size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 7, 2026
@clawsweeper

clawsweeper Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as superseded: the same Foundry Entra config-schema bug is already covered by a viable earlier PR with stronger validator-level regression coverage, and this branch adds a slightly stricter value schema than the established ThinkingLevelMap contract.

Canonical path: Review and land the canonical duplicate PR, or an equivalent patch that accepts the existing ThinkingLevelMap contract without narrowing values and includes validator-level positive and negative regression coverage.

So I’m closing this here because the remaining work is already tracked in the canonical issue.

Review details

Best possible solution:

Review and land the canonical duplicate PR, or an equivalent patch that accepts the existing ThinkingLevelMap contract without narrowing values and includes validator-level positive and negative regression coverage.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main's Foundry writer persists thinkingLevelMap, and the write path validates through a strict schema that lacks that key; the PR body also includes before/after terminal output from openclaw config validate.

Is this the best way to solve the issue?

No. Accepting the field is the right repair, but this branch is not the best landing path because the earlier open PR fixes the same gap without stricter values and with better validator coverage.

Security review:

Security review cleared: The diff only changes config schema/tests and a generated baseline hash; it adds no dependency, workflow, secret, install, or code-execution surface.

AGENTS.md: found and applied where relevant.

What I checked:

  • Current main write schema is the failing surface: ModelDefinitionSchema is strict and currently lists model fields without thinkingLevelMap, so a persisted provider model carrying that key is rejected before write-back succeeds. (src/config/zod-schema.core.ts:350, 919befbbb63c)
  • The model contract already includes thinkingLevelMap: ModelDefinitionConfig already exposes thinkingLevelMap?: ThinkingLevelMap, so the write schema is behind the existing model config type. (src/config/types.models.ts:193, 919befbbb63c)
  • The canonical value contract allows string or null: ThinkingLevelMap is Partial<Record<ModelThinkingLevel, string | null>>; the type does not require non-empty strings. (packages/llm-core/src/types.ts:40, 919befbbb63c)
  • Read-path schema already accepts the field: The TypeBox model registry schema includes the same seven thinkingLevelMap keys with Type.String() or Type.Null() values. (src/agents/sessions/model-registry.ts:88, 919befbbb63c)
  • Foundry onboarding writer emits the rejected key: buildFoundryProviderConfig persists capabilities.thinkingLevelMap into provider model entries, which is the write-path collision reported by the linked issue. (extensions/microsoft-foundry/shared.ts:381, 919befbbb63c)
  • Config writes validate through the strict raw schema: updateConfig validates the persist candidate with validateConfigObjectRawWithPlugins and throws on the first validation issue, matching the reported rollback behavior. (src/config/io.ts:2255, 919befbbb63c)

Likely related people:

  • Vincent Koc: Blame and current checkout history carry the config schema, model types, TypeBox registry schema, and Foundry writer lines through commit 39e27c82763d423de9094e4cc926a4293f4d9799; the history is grafted, so this is a routing signal rather than blame. (role: recent area contributor; confidence: medium; commits: 39e27c82763d; files: src/config/zod-schema.core.ts, src/config/types.models.ts, packages/llm-core/src/types.ts)
  • MetaX e|acc: Commit a16dd967da51dd6ab21bcfc0468f298b8e765077 added the Microsoft Foundry provider, Entra auth surface, and related tests touched by this bug. (role: introduced Foundry provider behavior; confidence: high; commits: a16dd967da51; files: extensions/microsoft-foundry/shared.ts, extensions/microsoft-foundry/provider.ts, extensions/microsoft-foundry/index.test.ts)
  • wsyjh8: The earlier open PR implements the same schema repair with broader validator regression tests and is the safer current landing path. (role: canonical duplicate PR author; confidence: medium; commits: 60fb8c6bdc7c; files: src/config/zod-schema.core.ts, src/config/config.schema-regressions.test.ts, extensions/microsoft-foundry/index.test.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 919befbbb63c.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P1 High-priority user-facing bug, regression, or broken workflow. labels Jun 7, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label Jun 7, 2026
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. labels Jun 7, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 7, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: sufficient ClawSweeper judged the real behavior proof convincing. labels Jun 7, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 7, 2026
@harjothkhara

Copy link
Copy Markdown
Contributor Author

Closing as superseded, per @clawsweeper's review.

The same Foundry thinkingLevelMap write-schema gap (#91011) is covered by the canonical PR #91037, which accepts the existing ThinkingLevelMap (string | null) contract without narrowing values and carries broader validator-level positive/negative regression coverage. This branch added a slightly stricter value schema, so deferring to #91037 is the cleaner landing path.

Thanks for the review — happy to help get #91037 over the line instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: S status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Foundry Entra ID onboarding fails to save with "Unrecognized key: thinkingLevelMap"

1 participant