





















@@ -9,6 +9,7 @@ import {
99buildFoundryAuthResult,
1010normalizeFoundryEndpoint,
1111requiresFoundryMaxCompletionTokens,
12+supportsFoundryReasoningEffort,
1213supportsFoundryImageInput,
1314usesFoundryResponsesByDefault,
1415} from "./shared.js";
@@ -104,7 +105,9 @@ function buildFoundryModel(
104105name: string;
105106api: "openai-responses" | "openai-completions";
106107baseUrl: string;
108+reasoning: boolean;
107109input: Array<"text" | "image">;
110+compat: Record<string, unknown>;
108111}> = {},
109112) {
110113return {
@@ -591,6 +594,23 @@ describe("microsoft-foundry plugin", () => {
591594]);
592595});
593596597+it("marks newly selected Foundry reasoning deployments as reasoning-capable", async () => {
598+const provider = registerProvider();
599+const config = buildFoundryConfig({ models: [] });
600+601+await provider.onModelSelected?.({
602+ config,
603+model: "microsoft-foundry/gpt-5.4",
604+prompter: {} as never,
605+agentDir: "/tmp/test-agent",
606+});
607+608+const model = config.models?.providers?.["microsoft-foundry"]?.models[0];
609+expect(model?.id).toBe("gpt-5.4");
610+expect(model?.reasoning).toBe(true);
611+expect(model?.compat?.supportsReasoningEffort).toBe(true);
612+});
613+594614it("accepts tenant domains as valid tenant identifiers", () => {
595615expect(isValidTenantIdentifier("contoso.onmicrosoft.com")).toBe(true);
596616expect(isValidTenantIdentifier("00000000-0000-0000-0000-000000000000")).toBe(true);
@@ -605,8 +625,14 @@ describe("microsoft-foundry plugin", () => {
605625expect(usesFoundryResponsesByDefault("DeepSeek-V4-Flash")).toBe(true);
606626expect(usesFoundryResponsesByDefault("MAI-DS-R1")).toBe(false);
607627expect(requiresFoundryMaxCompletionTokens("gpt-5.4")).toBe(true);
628+expect(requiresFoundryMaxCompletionTokens("gpt-5-chat")).toBe(true);
608629expect(requiresFoundryMaxCompletionTokens("o3")).toBe(true);
609630expect(requiresFoundryMaxCompletionTokens("gpt-4o")).toBe(false);
631+expect(supportsFoundryReasoningEffort("gpt-5.4")).toBe(true);
632+expect(supportsFoundryReasoningEffort("gpt-5-chat")).toBe(false);
633+expect(supportsFoundryReasoningEffort("gpt-5.1-chat")).toBe(true);
634+expect(supportsFoundryReasoningEffort("o3")).toBe(true);
635+expect(supportsFoundryReasoningEffort("o1-mini")).toBe(false);
610636expect(supportsFoundryImageInput("gpt-5.4")).toBe(true);
611637expect(supportsFoundryImageInput("gpt-4o")).toBe(true);
612638expect(supportsFoundryImageInput("MAI-DS-R1")).toBe(false);
@@ -639,14 +665,17 @@ describe("microsoft-foundry plugin", () => {
639665id: "deployment-gpt5",
640666name: "gpt-5.4",
641667input: ["text"],
668+compat: { supportsStrictMode: false },
642669}),
643670});
644671645672expect(normalized?.name).toBe("gpt-5.4");
646673expect(normalized?.api).toBe("openai-responses");
674+expect(normalized?.reasoning).toBe(true);
647675expect(normalized?.input).toEqual(["text", "image"]);
648676expect(normalized?.baseUrl).toBe("https://example.services.ai.azure.com/openai/v1");
649677expect(normalized?.compat?.supportsStore).toBe(false);
678+expect(normalized?.compat?.supportsStrictMode).toBe(false);
650679expect(normalized?.compat?.maxTokensField).toBe("max_completion_tokens");
651680});
652681@@ -667,6 +696,45 @@ describe("microsoft-foundry plugin", () => {
667696expect(normalized?.input).toEqual(["text", "image"]);
668697});
669698699+it("preserves explicit reasoning capability for non-heuristic Foundry aliases", () => {
700+const provider = registerProvider();
701+702+const normalized = provider.normalizeResolvedModel?.({
703+provider: "microsoft-foundry",
704+modelId: "prod-primary",
705+model: buildFoundryModel({
706+id: "prod-primary",
707+name: "production alias",
708+api: "openai-completions",
709+reasoning: true,
710+}),
711+});
712+713+expect(normalized?.name).toBe("production alias");
714+expect(normalized?.reasoning).toBe(true);
715+expect(normalized?.compat?.supportsReasoningEffort).toBe(true);
716+expect(normalized?.compat?.maxTokensField).toBe("max_completion_tokens");
717+});
718+719+it("preserves explicit reasoning_effort opt-outs for Foundry aliases", () => {
720+const provider = registerProvider();
721+722+const normalized = provider.normalizeResolvedModel?.({
723+provider: "microsoft-foundry",
724+modelId: "prod-primary",
725+model: buildFoundryModel({
726+id: "prod-primary",
727+name: "production alias",
728+api: "openai-completions",
729+reasoning: true,
730+compat: { supportsReasoningEffort: false },
731+}),
732+});
733+734+expect(normalized?.reasoning).toBe(true);
735+expect(normalized?.compat?.supportsReasoningEffort).toBe(false);
736+});
737+670738it("writes Azure API key header overrides for API-key auth configs", () => {
671739const result = buildFoundryAuthResult({
672740profileId: "microsoft-foundry:default",
@@ -712,6 +780,128 @@ describe("microsoft-foundry plugin", () => {
712780expect(provider?.models[0]?.compat?.maxTokensField).toBe("max_completion_tokens");
713781});
714782783+it("marks Foundry chat models as not supporting reasoning_effort", () => {
784+const result = buildFoundryAuthResult({
785+profileId: "microsoft-foundry:default",
786+apiKey: "test-api-key",
787+endpoint: "https://example.services.ai.azure.com",
788+modelId: "gpt-4o-mini",
789+modelNameHint: "gpt-4o-mini",
790+api: "openai-completions",
791+authMethod: "api-key",
792+});
793+794+const provider = result.configPatch?.models?.providers?.["microsoft-foundry"];
795+expect(provider?.models[0]?.reasoning).toBe(false);
796+expect(provider?.models[0]?.compat?.supportsReasoningEffort).toBe(false);
797+expect(provider?.models[0]?.compat?.maxTokensField).toBe("max_tokens");
798+});
799+800+it("keeps Foundry chat reasoning_effort enabled for GPT-5 reasoning deployments", () => {
801+const result = buildFoundryAuthResult({
802+profileId: "microsoft-foundry:default",
803+apiKey: "test-api-key",
804+endpoint: "https://example.services.ai.azure.com",
805+modelId: "gpt-5.1-chat",
806+modelNameHint: "gpt-5.1-chat",
807+api: "openai-completions",
808+authMethod: "api-key",
809+});
810+811+const provider = result.configPatch?.models?.providers?.["microsoft-foundry"];
812+expect(provider?.models[0]?.reasoning).toBe(true);
813+expect(provider?.models[0]?.thinkingLevelMap?.minimal).toBe(null);
814+expect(provider?.models[0]?.thinkingLevelMap?.off).toBe("none");
815+expect(provider?.models[0]?.compat?.supportsReasoningEffort).toBe(true);
816+expect(provider?.models[0]?.compat?.supportedReasoningEfforts).toEqual([
817+"none",
818+"low",
819+"medium",
820+"high",
821+]);
822+expect(provider?.models[0]?.compat?.maxTokensField).toBe("max_completion_tokens");
823+});
824+825+it("records model-name reasoning effort limits for Foundry deployment aliases", () => {
826+const result = buildFoundryAuthResult({
827+profileId: "microsoft-foundry:default",
828+apiKey: "test-api-key",
829+endpoint: "https://example.services.ai.azure.com",
830+modelId: "deployment-codex-mini",
831+modelNameHint: "gpt-5.1-codex-mini",
832+api: "openai-completions",
833+authMethod: "api-key",
834+});
835+836+const provider = result.configPatch?.models?.providers?.["microsoft-foundry"];
837+expect(provider?.models[0]?.reasoning).toBe(true);
838+expect(provider?.models[0]?.compat?.supportsReasoningEffort).toBe(true);
839+expect(provider?.models[0]?.compat?.supportedReasoningEfforts).toEqual([
840+"none",
841+"low",
842+"medium",
843+"high",
844+]);
845+});
846+847+it("omits minimal from newer Foundry GPT-5.x reasoning effort metadata", () => {
848+const result = buildFoundryAuthResult({
849+profileId: "microsoft-foundry:default",
850+apiKey: "test-api-key",
851+endpoint: "https://example.services.ai.azure.com",
852+modelId: "gpt-5.2",
853+modelNameHint: "gpt-5.2",
854+api: "openai-completions",
855+authMethod: "api-key",
856+});
857+858+const provider = result.configPatch?.models?.providers?.["microsoft-foundry"];
859+expect(provider?.models[0]?.thinkingLevelMap?.minimal).toBe(null);
860+expect(provider?.models[0]?.compat?.supportedReasoningEfforts).toEqual([
861+"none",
862+"low",
863+"medium",
864+"high",
865+]);
866+});
867+868+it("omits minimal from Foundry GPT-5 Codex reasoning effort metadata", () => {
869+const result = buildFoundryAuthResult({
870+profileId: "microsoft-foundry:default",
871+apiKey: "test-api-key",
872+endpoint: "https://example.services.ai.azure.com",
873+modelId: "gpt-5-codex",
874+modelNameHint: "gpt-5-codex",
875+api: "openai-completions",
876+authMethod: "api-key",
877+});
878+879+const provider = result.configPatch?.models?.providers?.["microsoft-foundry"];
880+expect(provider?.models[0]?.thinkingLevelMap?.minimal).toBe(null);
881+expect(provider?.models[0]?.compat?.supportedReasoningEfforts).toEqual([
882+"low",
883+"medium",
884+"high",
885+]);
886+});
887+888+it("keeps Foundry gpt-5-chat deployments non-reasoning while using max_completion_tokens", () => {
889+const result = buildFoundryAuthResult({
890+profileId: "microsoft-foundry:default",
891+apiKey: "test-api-key",
892+endpoint: "https://example.services.ai.azure.com",
893+modelId: "gpt-5-chat",
894+modelNameHint: "gpt-5-chat",
895+api: "openai-completions",
896+authMethod: "api-key",
897+});
898+899+const provider = result.configPatch?.models?.providers?.["microsoft-foundry"];
900+expect(provider?.models[0]?.reasoning).toBe(false);
901+expect(provider?.models[0]?.compat?.supportsReasoningEffort).toBe(false);
902+expect(provider?.models[0]?.compat?.maxTokensField).toBe("max_completion_tokens");
903+});
904+715905it("keeps persisted response-mode routing for custom deployment aliases", async () => {
716906const provider = registerProvider();
717907const config: OpenClawConfig = {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。