@@ -3,12 +3,17 @@ import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
33import { beforeEach , describe , expect , it , vi } from "vitest" ;
44import { getAccessTokenResultAsync } from "./cli.js" ;
55import plugin from "./index.js" ;
6- import { buildFoundryConnectionTest , isValidTenantIdentifier } from "./onboard.js" ;
6+ import {
7+ buildFoundryConnectionTest ,
8+ isValidTenantIdentifier ,
9+ selectFoundryDeployment ,
10+ } from "./onboard.js" ;
711import { resetFoundryRuntimeAuthCaches } from "./runtime.js" ;
812import {
913 buildFoundryAuthResult ,
1014 isAnthropicFoundryDeployment ,
1115 normalizeFoundryEndpoint ,
16+ partitionFoundryDeployments ,
1217 requiresFoundryMaxCompletionTokens ,
1318 supportsFoundryImageInput ,
1419 usesFoundryResponsesByDefault ,
@@ -723,6 +728,100 @@ describe("microsoft-foundry plugin", () => {
723728 } ) ;
724729} ) ;
725730
731+ describe ( "partitionFoundryDeployments" , ( ) => {
732+ it ( "keeps GPT deployments and skips Claude in mixed resources" , ( ) => {
733+ const { supported, anthropic } = partitionFoundryDeployments ( [
734+ { name : "prod-gpt" , modelName : "gpt-5.4" } ,
735+ { name : "prod-claude" , modelName : "claude-opus-4-6" } ,
736+ { name : "prod-mini" , modelName : "gpt-4o-mini" } ,
737+ ] ) ;
738+ expect ( supported . map ( ( d ) => d . name ) ) . toEqual ( [ "prod-gpt" , "prod-mini" ] ) ;
739+ expect ( anthropic . map ( ( d ) => d . name ) ) . toEqual ( [ "prod-claude" ] ) ;
740+ } ) ;
741+
742+ it ( "returns empty supported when only Anthropic deployments exist" , ( ) => {
743+ const { supported, anthropic } = partitionFoundryDeployments ( [
744+ { name : "only-claude" , modelName : "claude-3.5-sonnet" } ,
745+ ] ) ;
746+ expect ( supported ) . toEqual ( [ ] ) ;
747+ expect ( anthropic . map ( ( d ) => d . name ) ) . toEqual ( [ "only-claude" ] ) ;
748+ } ) ;
749+
750+ it ( "is a no-op for all-OpenAI resources" , ( ) => {
751+ const deployments = [
752+ { name : "prod-gpt" , modelName : "gpt-5.4" } ,
753+ { name : "prod-mini" , modelName : "gpt-4o-mini" } ,
754+ ] ;
755+ const { supported, anthropic } = partitionFoundryDeployments ( deployments ) ;
756+ expect ( supported ) . toEqual ( deployments ) ;
757+ expect ( anthropic ) . toEqual ( [ ] ) ;
758+ } ) ;
759+ } ) ;
760+
761+ describe ( "selectFoundryDeployment" , ( ) => {
762+ function makeCtx ( overrides : { selectValue ?: string } = { } ) {
763+ const noteCalls : Array < { message : string ; title : string } > = [ ] ;
764+ const selectCalls : Array < { options : Array < { value : string } > } > = [ ] ;
765+ const ctx = {
766+ prompter : {
767+ note : vi . fn ( async ( message : string , title : string ) => {
768+ noteCalls . push ( { message, title } ) ;
769+ } ) ,
770+ select : vi . fn ( async ( params : { options : Array < { value : string } > } ) => {
771+ selectCalls . push ( { options : params . options } ) ;
772+ return overrides . selectValue ?? params . options [ 0 ] ?. value ;
773+ } ) ,
774+ } ,
775+ } as never ;
776+ return { ctx, noteCalls, selectCalls } ;
777+ }
778+
779+ const fakeResource = {
780+ id : "/sub/x/rg/y/account/z" ,
781+ accountName : "foundry-resource" ,
782+ kind : "AIServices" as const ,
783+ resourceGroup : "rg" ,
784+ endpoint : "https://example.services.ai.azure.com" ,
785+ projects : [ ] ,
786+ } ;
787+
788+ it ( "persists only supported deployments for mixed GPT+Claude resources" , async ( ) => {
789+ const { ctx, selectCalls, noteCalls } = makeCtx ( { selectValue : "prod-gpt" } ) ;
790+ const result = await selectFoundryDeployment ( ctx , fakeResource , [
791+ { name : "prod-gpt" , modelName : "gpt-5.4" , state : "Succeeded" } ,
792+ { name : "prod-claude" , modelName : "claude-opus-4-6" , state : "Succeeded" } ,
793+ { name : "prod-mini" , modelName : "gpt-4o-mini" , state : "Succeeded" } ,
794+ ] ) ;
795+
796+ expect ( result . supported . map ( ( d ) => d . name ) ) . toEqual ( [ "prod-gpt" , "prod-mini" ] ) ;
797+ expect ( result . selected . name ) . toBe ( "prod-gpt" ) ;
798+ expect ( selectCalls [ 0 ] ?. options . map ( ( o ) => o . value ) ) . toEqual ( [ "prod-gpt" , "prod-mini" ] ) ;
799+ expect ( noteCalls . some ( ( n ) => n . title === "Unsupported Deployments" ) ) . toBe ( true ) ;
800+ } ) ;
801+
802+ it ( "throws an actionable error when only Anthropic deployments exist" , async ( ) => {
803+ const { ctx, noteCalls } = makeCtx ( ) ;
804+ await expect (
805+ selectFoundryDeployment ( ctx , fakeResource , [
806+ { name : "only-claude" , modelName : "claude-3.5-sonnet" , state : "Succeeded" } ,
807+ ] ) ,
808+ ) . rejects . toThrow ( / O n l y A n t h r o p i c d e p l o y m e n t s / ) ;
809+ expect ( noteCalls . some ( ( n ) => n . title === "Unsupported Deployments" ) ) . toBe ( true ) ;
810+ } ) ;
811+
812+ it ( "leaves all-OpenAI resources unchanged" , async ( ) => {
813+ const { ctx, selectCalls, noteCalls } = makeCtx ( { selectValue : "prod-mini" } ) ;
814+ const result = await selectFoundryDeployment ( ctx , fakeResource , [
815+ { name : "prod-gpt" , modelName : "gpt-5.4" , state : "Succeeded" } ,
816+ { name : "prod-mini" , modelName : "gpt-4o-mini" , state : "Succeeded" } ,
817+ ] ) ;
818+ expect ( result . supported . map ( ( d ) => d . name ) ) . toEqual ( [ "prod-gpt" , "prod-mini" ] ) ;
819+ expect ( result . selected . name ) . toBe ( "prod-mini" ) ;
820+ expect ( selectCalls ) . toHaveLength ( 1 ) ;
821+ expect ( noteCalls . some ( ( n ) => n . title === "Unsupported Deployments" ) ) . toBe ( false ) ;
822+ } ) ;
823+ } ) ;
824+
726825describe ( "isAnthropicFoundryDeployment" , ( ) => {
727826 it . each ( [ "claude-opus-4-6" , "Claude-Sonnet-4" , "claude-3.5-haiku" , "CLAUDE-instant" ] ) (
728827 "detects Anthropic model: %s" ,
0 commit comments