@@ -2,9 +2,11 @@ import { and, eq } from "drizzle-orm";
22import { listVisibleProviders , type DB } from "@intx/db" ;
33import { principal , tenant } from "@intx/db/schema" ;
44import type { ModelChoice } from "@corbits-builder/schemas/models" ;
5+ import type { AvailabilityState } from "../availability.ts" ;
56import {
67 MODEL_CHOICE_REFUSALS ,
78 frozenModelChoice ,
9+ pickedModelChoice ,
810 providerOfRow ,
911 resolveModelChoice ,
1012 type ModelDirectory ,
@@ -19,16 +21,28 @@ import { userPrincipalIn } from "./authorization.ts";
1921import { ProjectError } from "./errors.ts" ;
2022import type { Actor } from "../lib/actor.ts" ;
2123
22- /** The provider entries a tenant can run on: each enabled connection with a credential. */
24+ /** The provider entries a tenant can run on: each enabled connection with a non-refused credential. */
2325export async function connectedProviders (
2426 db : DB [ "db" ] ,
2527 directory : ModelDirectory ,
2628 tenantId : string ,
29+ probe ?: ( tenantId : string , providerId : string ) => Promise < AvailabilityState > ,
2730) : Promise < string [ ] > {
28- const ids = ( await listVisibleProviders ( db , tenantId ) )
29- . filter ( ( { row } ) => row . credentialId !== null && ! row . disabled )
30- . map ( ( { row } ) => providerOfRow ( directory , row ) ?. id )
31- . filter ( ( id ) => id !== undefined ) ;
31+ const visible = ( await listVisibleProviders ( db , tenantId ) ) . filter (
32+ ( { row } ) => row . credentialId !== null && ! row . disabled ,
33+ ) ;
34+ const ids : string [ ] = [ ] ;
35+ for ( const { row } of visible ) {
36+ const entry = providerOfRow ( directory , row ) ;
37+ if ( ! entry ) continue ;
38+ if (
39+ probe &&
40+ entry . kind !== "custom" &&
41+ ( await probe ( tenantId , row . id ) ) === "refused"
42+ )
43+ continue ;
44+ ids . push ( entry . id ) ;
45+ }
3246 return [ ...new Set ( ids ) ] ;
3347}
3448
@@ -62,9 +76,15 @@ export async function projectModelChoice(
6276 actor : Actor ,
6377 projectId : string ,
6478 directory : ModelDirectory ,
79+ probe ?: ( tenantId : string , providerId : string ) => Promise < AvailabilityState > ,
6580) : Promise < ModelChoice > {
6681 const { models } = await loadSettings ( db , actor . tenantId , projectId ) ;
67- const connected = await connectedProviders ( db , directory , actor . tenantId ) ;
82+ const connected = await connectedProviders (
83+ db ,
84+ directory ,
85+ actor . tenantId ,
86+ probe ,
87+ ) ;
6888 const kept = frozenModelChoice ( directory , models . value , connected ) ;
6989 if ( kept ) return kept ;
7090 const resolved = resolveModelChoice (
@@ -94,3 +114,49 @@ export async function projectModelChoice(
94114 ) ;
95115 return resolved . choice ;
96116}
117+
118+ /** Switches a project onto one of the caller's runnable profiles. */
119+ export async function switchProjectModelChoice (
120+ db : DB [ "db" ] ,
121+ actor : Actor ,
122+ projectId : string ,
123+ profile : string ,
124+ directory : ModelDirectory ,
125+ probe ?: ( tenantId : string , providerId : string ) => Promise < AvailabilityState > ,
126+ ) : Promise < ModelChoice > {
127+ const { models } = await loadSettings ( db , actor . tenantId , projectId ) ;
128+ const personal = await personalChoice ( db , actor ) ;
129+ const picked =
130+ pickedModelChoice ( personal , profile ) ??
131+ pickedModelChoice ( models . value , profile ) ;
132+ if ( ! picked )
133+ throw new ProjectError ( 400 , "model_profile_unknown" , { profile } ) ;
134+ const connected = await connectedProviders (
135+ db ,
136+ directory ,
137+ actor . tenantId ,
138+ probe ,
139+ ) ;
140+ const frozen = frozenModelChoice ( directory , picked , connected ) ;
141+ if ( ! frozen )
142+ throw new ProjectError ( 409 , "model_provider_missing" , {
143+ message : MODEL_CHOICE_REFUSALS . model_provider_missing ,
144+ } ) ;
145+ const scopeKey = `project:${ projectId } ` ;
146+ const fields = await loadSettingsAt ( db , actor . tenantId , scopeKey ) ;
147+ await db
148+ . update ( settingsRecord )
149+ . set ( {
150+ fields : {
151+ ...fields ,
152+ models : { value : frozen , version : fields . models . version + 1 } ,
153+ } ,
154+ } )
155+ . where (
156+ and (
157+ eq ( settingsRecord . tenantId , actor . tenantId ) ,
158+ eq ( settingsRecord . scopeKey , scopeKey ) ,
159+ ) ,
160+ ) ;
161+ return frozen ;
162+ }
0 commit comments