Skip to content

Commit 60fb8c6

Browse files
committed
fix(config): allow thinkingLevelMap in persisted model schema
The persisted zod ModelDefinitionSchema (src/config/zod-schema.core.ts) was strict and had no thinkingLevelMap key, while the rest of the contract already carried it: the TS type (ModelDefinitionConfig.thinkingLevelMap in types.models.ts), the runtime TypeBox schema (ThinkingLevelMapSchema in agents/sessions/model-registry.ts), and the foundry writer (buildFoundryThinkingLevelMap, 7 keys: off/minimal/low/medium/high/xhigh/max). On Microsoft Foundry Entra ID onboarding the writer's persisted model entry hit the strict schema, validation failed with Unrecognized key "thinkingLevelMap", and updateConfig atomically rolled the whole write back, so onboarding never completed. This is schema drift, not an illegal field. Fix: add an optional thinkingLevelMap to the zod ModelDefinitionSchema, mirroring the TypeBox key set 1:1 (string | null values) and keeping .strict(). The writer and normalizeResolvedModel are unchanged. docs/.generated/config-baseline.sha256 is a derived artifact of this schema change, regenerated via `pnpm config:docs:gen` (config:docs:check now passes); it was not hand-edited. Closes #91011
1 parent 4d142b1 commit 60fb8c6

4 files changed

Lines changed: 104 additions & 3 deletions

File tree

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
60c0700719fd2fe3f7cec4c35da10227b681d87ed1a3876ef830eb6bd80d43f2 config-baseline.json
2-
2ed21fa4a416ac2cec55eb2b6d1b11859aa04b40bd78c6ed9f3eb45b7240261c config-baseline.core.json
1+
a5a97a8b484acd13e68604037c8d8f448699700103c6ea2186f5914ad35a0623 config-baseline.json
2+
b0d668dbd794d2f54738152a4bcfd2a306c7954901e78d4dfbde7545a8301ce5 config-baseline.core.json
33
0637c9bdcb9517f56049dd786563366877458d35df575328a6b80a890c8bc915 config-baseline.channel.json
4-
e6a1d6f51f0d9c04bd92d51deebfaca8c7917dd28d7998d225c0074e0a095348 config-baseline.plugin.json
4+
f9d1f50bfa8403891e76cd99dc1357cdece4a71e8ae18a39b190c2a14e6f97b0 config-baseline.plugin.json

extensions/microsoft-foundry/index.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,38 @@ describe("microsoft-foundry plugin", () => {
823823
expect(provider?.models[0]?.compat?.maxTokensField).toBe("max_completion_tokens");
824824
});
825825

826+
it("emits only persisted-schema thinkingLevelMap level keys for Entra ID reasoning onboarding (openclaw#91011)", () => {
827+
// The persisted ModelDefinitionSchema only accepts these ModelThinkingLevel keys; if the writer
828+
// emits one outside the set, updateConfig rolls the Entra ID onboarding write back.
829+
const allowedThinkingLevels = new Set([
830+
"off",
831+
"minimal",
832+
"low",
833+
"medium",
834+
"high",
835+
"xhigh",
836+
"max",
837+
]);
838+
839+
const result = buildFoundryAuthResult({
840+
profileId: "microsoft-foundry:entra",
841+
apiKey: "__entra_id_dynamic__",
842+
endpoint: "https://example.services.ai.azure.com",
843+
modelId: "gpt-5.1-chat",
844+
modelNameHint: "gpt-5.1-chat",
845+
api: "openai-responses",
846+
authMethod: "entra-id",
847+
});
848+
849+
const thinkingLevelMap =
850+
result.configPatch?.models?.providers?.["microsoft-foundry"]?.models[0]?.thinkingLevelMap;
851+
expect(thinkingLevelMap).toBeDefined();
852+
for (const [level, value] of Object.entries(thinkingLevelMap ?? {})) {
853+
expect(allowedThinkingLevels.has(level)).toBe(true);
854+
expect(value === null || typeof value === "string").toBe(true);
855+
}
856+
});
857+
826858
it("records model-name reasoning effort limits for Foundry deployment aliases", () => {
827859
const result = buildFoundryAuthResult({
828860
profileId: "microsoft-foundry:default",

src/config/config.schema-regressions.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,4 +488,57 @@ describe("config schema regressions", () => {
488488

489489
expect(res.ok).toBe(true);
490490
});
491+
492+
it("accepts a microsoft-foundry model entry carrying thinkingLevelMap (openclaw#91011)", () => {
493+
// Foundry's writer (buildFoundryThinkingLevelMap) persists this during Entra ID onboarding; the
494+
// strict schema used to reject thinkingLevelMap, so updateConfig rolled the whole write back.
495+
const res = validateConfigObject({
496+
models: {
497+
providers: {
498+
"microsoft-foundry": {
499+
models: [
500+
{
501+
id: "gpt-5.1-chat",
502+
name: "gpt-5.1-chat",
503+
api: "openai-responses",
504+
reasoning: true,
505+
thinkingLevelMap: {
506+
off: "none",
507+
minimal: null,
508+
low: "low",
509+
medium: "medium",
510+
high: "high",
511+
xhigh: null,
512+
max: null,
513+
},
514+
},
515+
],
516+
},
517+
},
518+
},
519+
});
520+
521+
expect(res.ok).toBe(true);
522+
});
523+
524+
it("rejects thinkingLevelMap keys outside the model thinking levels", () => {
525+
// "adaptive" is a valid agent thinkingDefault but not a ModelThinkingLevel; the map stays strict.
526+
const res = validateConfigObject({
527+
models: {
528+
providers: {
529+
"microsoft-foundry": {
530+
models: [
531+
{
532+
id: "gpt-5.1-chat",
533+
name: "gpt-5.1-chat",
534+
thinkingLevelMap: { adaptive: "high" },
535+
},
536+
],
537+
},
538+
},
539+
},
540+
});
541+
542+
expect(res.ok).toBe(false);
543+
});
491544
});

src/config/zod-schema.core.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,21 @@ const ModelMediaInputSchema = z
347347
})
348348
.strict();
349349

350+
// Mirrors the runtime ThinkingLevelMap contract (model-registry TypeBox schema). Persisted model
351+
// entries carry thinkingLevelMap, so the strict config schema must accept it or updateConfig rolls back.
352+
const ThinkingLevelMapValueSchema = z.string().nullable();
353+
const ThinkingLevelMapSchema = z
354+
.object({
355+
off: ThinkingLevelMapValueSchema.optional(),
356+
minimal: ThinkingLevelMapValueSchema.optional(),
357+
low: ThinkingLevelMapValueSchema.optional(),
358+
medium: ThinkingLevelMapValueSchema.optional(),
359+
high: ThinkingLevelMapValueSchema.optional(),
360+
xhigh: ThinkingLevelMapValueSchema.optional(),
361+
max: ThinkingLevelMapValueSchema.optional(),
362+
})
363+
.strict();
364+
350365
const ModelDefinitionSchema = z
351366
.object({
352367
id: z.string().min(1),
@@ -384,6 +399,7 @@ const ModelDefinitionSchema = z
384399
contextWindow: z.number().positive().optional(),
385400
contextTokens: z.number().int().positive().optional(),
386401
maxTokens: z.number().positive().optional(),
402+
thinkingLevelMap: ThinkingLevelMapSchema.optional(),
387403
params: z.record(z.string(), z.unknown()).optional(),
388404
agentRuntime: ModelAgentRuntimePolicySchema,
389405
headers: z.record(z.string(), z.string()).optional(),

0 commit comments

Comments
 (0)