|
| 1 | +import { requestJson } from '@/lib/api/client/request' |
| 2 | +import { |
| 3 | + listManagedAgentOptionsContract, |
| 4 | + type ManagedAgentResource, |
| 5 | +} from '@/lib/api/contracts/managed-agents' |
| 6 | +import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared' |
| 7 | +import type { |
| 8 | + SelectorDefinition, |
| 9 | + SelectorKey, |
| 10 | + SelectorOption, |
| 11 | + SelectorQueryArgs, |
| 12 | +} from '@/hooks/selectors/types' |
| 13 | + |
| 14 | +/** |
| 15 | + * All four Managed Agent pickers read one route, distinguished only by `resource`. The route |
| 16 | + * decrypts the selected Claude Platform credential server-side, so the API key never reaches |
| 17 | + * the browser — which is why these cannot fall back to a plain client fetch. |
| 18 | + */ |
| 19 | +async function listResource( |
| 20 | + key: SelectorKey, |
| 21 | + resource: ManagedAgentResource, |
| 22 | + { context, signal }: SelectorQueryArgs |
| 23 | +): Promise<SelectorOption[]> { |
| 24 | + const credentialId = ensureCredential(context, key) |
| 25 | + const { options } = await requestJson(listManagedAgentOptionsContract, { |
| 26 | + query: { credentialId, resource }, |
| 27 | + signal, |
| 28 | + }) |
| 29 | + return options |
| 30 | +} |
| 31 | + |
| 32 | +function resourceSelector(key: SelectorKey, resource: ManagedAgentResource): SelectorDefinition { |
| 33 | + return { |
| 34 | + key, |
| 35 | + contracts: [listManagedAgentOptionsContract], |
| 36 | + staleTime: SELECTOR_STALE, |
| 37 | + getQueryKey: ({ context }: SelectorQueryArgs) => [ |
| 38 | + 'selectors', |
| 39 | + key, |
| 40 | + context.oauthCredential ?? 'none', |
| 41 | + ], |
| 42 | + enabled: ({ context }) => Boolean(context.oauthCredential), |
| 43 | + fetchList: (args: SelectorQueryArgs) => listResource(key, resource, args), |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +export const managedAgentSelectors = { |
| 48 | + 'managedAgent.agents': resourceSelector('managedAgent.agents', 'agents'), |
| 49 | + 'managedAgent.vaults': resourceSelector('managedAgent.vaults', 'vaults'), |
| 50 | + 'managedAgent.memoryStores': resourceSelector('managedAgent.memoryStores', 'memory-stores'), |
| 51 | + /** |
| 52 | + * Environments are filtered to the selected deployment mode: cloud and self-hosted expose |
| 53 | + * different fields (self-hosted rejects `resources`), so mixing them offers choices the rest |
| 54 | + * of the form cannot honour. An option whose type the API leaves unset is kept either way. |
| 55 | + */ |
| 56 | + 'managedAgent.environments': { |
| 57 | + key: 'managedAgent.environments', |
| 58 | + contracts: [listManagedAgentOptionsContract], |
| 59 | + staleTime: SELECTOR_STALE, |
| 60 | + getQueryKey: ({ context }: SelectorQueryArgs) => [ |
| 61 | + 'selectors', |
| 62 | + 'managedAgent.environments', |
| 63 | + context.oauthCredential ?? 'none', |
| 64 | + context.environmentType ?? 'any', |
| 65 | + ], |
| 66 | + enabled: ({ context }) => Boolean(context.oauthCredential), |
| 67 | + fetchList: async (args: SelectorQueryArgs) => { |
| 68 | + const options = await listResource('managedAgent.environments', 'environments', args) |
| 69 | + const mode = args.context.environmentType |
| 70 | + if (mode !== 'cloud' && mode !== 'self_hosted') return options |
| 71 | + return options.filter((option) => { |
| 72 | + const type = (option as { type?: string }).type |
| 73 | + return type === undefined || type === mode |
| 74 | + }) |
| 75 | + }, |
| 76 | + }, |
| 77 | +} satisfies Partial<Record<SelectorKey, SelectorDefinition>> |
0 commit comments