@undefineds.co/xpod 0.4.29 → 0.4.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/ApiServer.js +4 -0
- package/dist/api/ai-gateway/AiGatewayService.d.ts +55 -0
- package/dist/api/ai-gateway/AiGatewayService.js +145 -6
- package/dist/api/ai-gateway/auth/AiConnectionsInvocationKeyIssuer.js +5 -3
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +16 -49
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +6 -71
- package/dist/api/ai-gateway/auth/InvocationTokenAuthenticator.js +1 -0
- package/dist/api/ai-gateway/auth/InvocationTokenCodec.d.ts +4 -0
- package/dist/api/ai-gateway/auth/InvocationTokenCodec.js +8 -1
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.d.ts +3 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.js +23 -3
- package/dist/api/ai-gateway/connect/SessionImportProfiles.d.ts +9 -1
- package/dist/api/ai-gateway/connect/SessionImportProfiles.js +4 -3
- package/dist/api/ai-gateway/connect/index.d.ts +40 -0
- package/dist/api/ai-gateway/connect/index.js +194 -4
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +1 -1
- package/dist/api/ai-gateway/pod/OwnerPodAccess.js +5 -0
- package/dist/api/ai-gateway/pod/PodBaseUrlResolver.d.ts +11 -2
- package/dist/api/ai-gateway/pod/PodBaseUrlResolver.js +72 -10
- package/dist/api/ai-gateway/quota/ProviderQuotaAdapter.js +1 -1
- package/dist/api/ai-gateway/routing/ModelRouter.d.ts +7 -0
- package/dist/api/ai-gateway/routing/ModelRouter.js +13 -10
- package/dist/api/ai-gateway/service-access/AiConnectionsServiceAccess.d.ts +1 -5
- package/dist/api/ai-gateway/service-access/AiConnectionsServiceAccess.js +8 -63
- package/dist/api/auth/AuthContext.d.ts +4 -0
- package/dist/api/auth/ClientCredentialsAuthenticator.js +11 -5
- package/dist/api/auth/SolidSessionFactory.d.ts +50 -4
- package/dist/api/auth/SolidSessionFactory.js +114 -4
- package/dist/api/chatkit/pod-store.d.ts +8 -1
- package/dist/api/chatkit/pod-store.js +54 -3
- package/dist/api/container/common.js +34 -60
- package/dist/api/container/index.js +0 -3
- package/dist/api/container/routes.js +1 -8
- package/dist/api/container/types.d.ts +2 -9
- package/dist/api/container/types.js +0 -5
- package/dist/api/handlers/AiGatewayManagementHandler.d.ts +4 -12
- package/dist/api/handlers/AiGatewayManagementHandler.js +18 -258
- package/dist/api/middleware/AuthMiddleware.js +29 -1
- package/dist/api/runs/PiAgentRuntimeDriver.js +6 -1
- package/dist/cli/index.js +2 -1
- package/dist/identity/oidc/PodOwnershipResolver.js +16 -6
- package/dist/runtime/ingress-port.d.ts +9 -2
- package/dist/runtime/ingress-port.js +7 -2
- package/dist/runtime/local-secret-file.d.ts +1 -1
- package/dist/runtime/runner/node/CommunitySolidServerCssRunner.d.ts +3 -1
- package/dist/runtime/runner/node/CommunitySolidServerCssRunner.js +31 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +3 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +96 -67
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.d.ts +1 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +81 -23
- package/node_modules/@undefineds.co/ai-connections/dist/AiOfferingDetails.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiOfferingDetails.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderHeader.js +1 -1
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/fingerprint.d.ts +1 -0
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/fingerprint.js +7 -0
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/normalize.d.ts +1 -2
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/normalize.js +0 -33
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/request.d.ts +1 -1
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/request.js +68 -22
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/types.d.ts +37 -10
- package/node_modules/@undefineds.co/ai-connections/dist/controller.js +5 -54
- package/node_modules/@undefineds.co/ai-connections/dist/error-wording.js +7 -2
- package/node_modules/@undefineds.co/ai-connections/dist/service-access-resources.cjs +405 -0
- package/node_modules/@undefineds.co/ai-connections/dist/service-access-resources.d.ts +14 -0
- package/node_modules/@undefineds.co/ai-connections/dist/service-access-resources.js +36 -0
- package/node_modules/@undefineds.co/ai-connections/dist/service-access.js +5 -26
- package/node_modules/@undefineds.co/ai-connections/package.json +10 -2
- package/node_modules/@undefineds.co/extension-sdk/dist/solid-permissions.js +131 -13
- package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +7 -1
- package/node_modules/@undefineds.co/extension-sdk/package.json +1 -1
- package/node_modules/@undefineds.co/pod-collections/dist/index.cjs +38 -5
- package/node_modules/@undefineds.co/pod-collections/dist/mapping.d.ts +8 -0
- package/node_modules/@undefineds.co/pod-collections/dist/mapping.js +34 -0
- package/node_modules/@undefineds.co/pod-collections/dist/mutations.d.ts +2 -1
- package/node_modules/@undefineds.co/pod-collections/dist/mutations.js +6 -4
- package/node_modules/@undefineds.co/pod-collections/dist/sync.js +4 -3
- package/node_modules/@undefineds.co/pod-collections/package.json +1 -1
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.d.ts +1 -1
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.js +2 -2
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpViews.js +2 -2
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.d.ts +3 -3
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.js +5 -5
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.d.ts +5 -5
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.js +5 -5
- package/node_modules/@undefineds.co/shared-ui/dist/theme.css +32 -5
- package/node_modules/@undefineds.co/shared-ui/package.json +1 -1
- package/package.json +2 -2
- package/static/app/assets/main.css +1 -1
- package/static/app/assets/main.js +18 -18
- package/static/dashboard/assets/{AccountAuthBoundary-Dv52SZc6.js → AccountAuthBoundary-CuqXFEW4.js} +1 -1
- package/static/dashboard/assets/{DesktopOidcCallback-DaT_Rumf.js → DesktopOidcCallback-7i-yOvPW.js} +1 -1
- package/static/dashboard/assets/DevicePages-D1m_BNtX.js +2 -0
- package/static/dashboard/assets/ModelsPage-0qWGa5Y6.js +12 -0
- package/static/dashboard/assets/{PaneListHeader-DtvnRmFq.js → PaneListHeader-uWdPgJ0E.js} +1 -1
- package/static/dashboard/assets/{PodDeletionAuthorizationPanel-BJk_8rJq.js → PodDeletionAuthorizationPanel-C22bhZeD.js} +3 -3
- package/static/dashboard/assets/{PodPage-C4gzEspV.js → PodPage-Dyo3yCb-.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsPage-HaNrP1H1.js → SystemSettingsPage-CeX-oG_a.js} +1 -1
- package/static/dashboard/assets/{TasksPage-BdO5K2L7.js → TasksPage-B0EoyRYT.js} +1 -1
- package/static/dashboard/assets/{WebDesktopEntry-Cj9_ZfJU.js → WebDesktopEntry-DT44RrTa.js} +1 -1
- package/static/dashboard/assets/XpodAiConnectionsPodStore-EXCqZPmb.js +3 -0
- package/static/dashboard/assets/XpodShellApp-BEzi4f1j.css +1 -0
- package/static/dashboard/assets/XpodShellApp-DAFsxamX.js +4 -0
- package/static/dashboard/assets/{account-session-DlcKNG7k.js → account-session-rRAOF7nF.js} +1 -1
- package/static/dashboard/assets/{button-wslF55uZ.js → button-WVnQUNG-.js} +1 -1
- package/static/dashboard/assets/{collection-runtime-uxxgEu13.js → collection-runtime-BAJQ8Qub.js} +1 -1
- package/static/dashboard/assets/{createLucideIcon-BJQl82Qk.js → createLucideIcon-BWelSi3L.js} +1 -1
- package/static/dashboard/assets/dashboard-hSOBxPcP.css +1 -0
- package/static/dashboard/assets/{dashboard-Or0gnkou.js → dashboard-vmne1ZE0.js} +2 -2
- package/static/dashboard/assets/{index-BqFeOML9.js → index-HDQQ8asz.js} +1 -1
- package/static/dashboard/assets/{index-DgKn1twH.js → index-gtv-MAHl.js} +1 -1
- package/static/dashboard/assets/{label-CS6Sx6Zb.js → label-tKuCTJm2.js} +1 -1
- package/static/dashboard/assets/{list-UyhlCFKk.js → list-CKq3KHke.js} +1 -1
- package/static/dashboard/assets/{nav-item-style-CW1Nnorq.js → nav-item-style-B2gI7c52.js} +1 -1
- package/static/dashboard/assets/pod-collections-host-BqOAkacM.js +8 -0
- package/static/dashboard/assets/{safe-continuation-ClWAhcma.js → safe-continuation-BsKjDpAA.js} +1 -1
- package/static/dashboard/assets/{section-header-CEIEcco-.js → section-header-DM0JU-wk.js} +1 -1
- package/static/dashboard/assets/{setting-row-CaYv3dfF.js → setting-row-XiH7UjWy.js} +1 -1
- package/static/dashboard/assets/{xpod-remembered-login-BUF9fin1.js → xpod-remembered-login-nv1g1sfC.js} +4 -4
- package/static/dashboard/dashboard.html +2 -2
- package/static/settings/assets/{AccountAuthBoundary-Chqd38oU.js → AccountAuthBoundary-DAUR0omy.js} +1 -1
- package/static/settings/assets/{DesktopOidcCallback-VUrOSM_q.js → DesktopOidcCallback-BNZAZMWY.js} +1 -1
- package/static/settings/assets/DevicePages-BO9SrXRp.js +2 -0
- package/static/settings/assets/ModelsPage-DREEXdf3.js +12 -0
- package/static/settings/assets/{PaneListHeader-B5RkRqr4.js → PaneListHeader-CeUH0JjQ.js} +1 -1
- package/static/settings/assets/{PodDeletionAuthorizationPanel-DPM-5Izt.js → PodDeletionAuthorizationPanel-Ch4--l4b.js} +3 -3
- package/static/settings/assets/{PodPage-CxND6wwc.js → PodPage-CqJBj3GJ.js} +1 -1
- package/static/settings/assets/{SystemSettingsPage-C8EdMkW7.js → SystemSettingsPage-2LSjpqUa.js} +1 -1
- package/static/settings/assets/{TasksPage-DuqptEm8.js → TasksPage-DA5IRxlP.js} +1 -1
- package/static/settings/assets/{WebDesktopEntry-BRcyktkv.js → WebDesktopEntry-BVelF1q0.js} +1 -1
- package/static/settings/assets/XpodAiConnectionsPodStore-O5-2qBsx.js +3 -0
- package/static/settings/assets/XpodShellApp-BEzi4f1j.css +1 -0
- package/static/settings/assets/XpodShellApp-D1b3kqD0.js +4 -0
- package/static/settings/assets/XpodThemeProvider-hSOBxPcP.css +1 -0
- package/static/settings/assets/{XpodThemeProvider-DK3pIdRG.js → XpodThemeProvider-rUdAw66p.js} +2 -2
- package/static/settings/assets/{account-session-DJOZ03xr.js → account-session-BD1q8D_o.js} +1 -1
- package/static/settings/assets/auth-callback-PWS7okI8.js +1 -0
- package/static/settings/assets/{button-D3yW-2o1.js → button-ZJYFB218.js} +1 -1
- package/static/settings/assets/{collection-runtime-DfGo25mh.js → collection-runtime-C2FHCCXJ.js} +1 -1
- package/static/settings/assets/{createLucideIcon-CacwLsmB.js → createLucideIcon-B2wXvYTD.js} +1 -1
- package/static/settings/assets/{index-r5KRYsKC.js → index-JFAkaKZH.js} +1 -1
- package/static/settings/assets/{label-MJjUmvzG.js → label-BLrTAmGy.js} +1 -1
- package/static/settings/assets/{list-Ce-5k1MX.js → list-Cm96vvJO.js} +1 -1
- package/static/settings/assets/{nav-item-style-ZUbL8KOZ.js → nav-item-style-C2upI7sr.js} +1 -1
- package/static/settings/assets/pod-collections-host-BqOAkacM.js +8 -0
- package/static/settings/assets/{safe-continuation-vlBZFSlU.js → safe-continuation-CYaRj3rj.js} +1 -1
- package/static/settings/assets/{section-header-DGJdEvQw.js → section-header-BviL5H8E.js} +1 -1
- package/static/settings/assets/{setting-row-C7tuFiho.js → setting-row-BYikj0YO.js} +1 -1
- package/static/settings/assets/{settings-CyQPih1O.js → settings-CpoDG_Qr.js} +1 -1
- package/static/settings/assets/{xpod-remembered-login-D-TzzT5K.js → xpod-remembered-login-BJ4QUSXy.js} +4 -4
- package/static/settings/auth-callback.html +3 -3
- package/static/settings/settings.html +3 -3
- package/dist/api/ai-gateway/auth/GatewayApiKey.d.ts +0 -28
- package/dist/api/ai-gateway/auth/GatewayApiKey.js +0 -162
- package/dist/api/ai-gateway/auth/GatewayKeyLocatorCodec.d.ts +0 -26
- package/dist/api/ai-gateway/auth/GatewayKeyLocatorCodec.js +0 -97
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +0 -67
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +0 -340
- package/dist/runtime/gateway-locator-secret.d.ts +0 -5
- package/dist/runtime/gateway-locator-secret.js +0 -30
- package/static/dashboard/assets/DevicePages-HjclpIVm.js +0 -2
- package/static/dashboard/assets/ModelsPage-Be2TPd-x.js +0 -11
- package/static/dashboard/assets/XpodAiConnectionsPodStore-B7gmXBmC.js +0 -3
- package/static/dashboard/assets/XpodShellApp-Bwom2xL3.css +0 -1
- package/static/dashboard/assets/XpodShellApp-D_KY4nCW.js +0 -4
- package/static/dashboard/assets/dashboard-CbJ-JkrI.css +0 -1
- package/static/dashboard/assets/pod-collections-host-DjK2xf-b.js +0 -8
- package/static/settings/assets/DevicePages-C6qwvWwu.js +0 -2
- package/static/settings/assets/ModelsPage-Ry_dObRQ.js +0 -11
- package/static/settings/assets/XpodAiConnectionsPodStore-CyA_BJVC.js +0 -3
- package/static/settings/assets/XpodShellApp-Bwom2xL3.css +0 -1
- package/static/settings/assets/XpodShellApp-CsivHJl0.js +0 -4
- package/static/settings/assets/XpodThemeProvider-CbJ-JkrI.css +0 -1
- package/static/settings/assets/auth-callback-GIBYEz_7.js +0 -1
- package/static/settings/assets/pod-collections-host-DjK2xf-b.js +0 -8
|
@@ -3,7 +3,7 @@ import { type AiOfferingActionError, type AiOfferingQuotaState } from './AiCrede
|
|
|
3
3
|
export type { AiProviderDefinition } from './controller.js';
|
|
4
4
|
import type { AiProviderDefinition } from './controller.js';
|
|
5
5
|
export type ProviderConnectionState = 'unknown' | 'pending' | 'configured' | 'connected' | 'disconnected' | 'reauthRequired' | 'failed';
|
|
6
|
-
export declare function AiProviderCard({ definition, product, status, accountLabel, attempt, attemptOfferingId, apiKey, baseUrl, busy, disabled, error, quotas, models, verifyPending, onApiKeyChange, onBaseUrlChange, onBeginApiKey, onBeginOffering, onCancelConnect, onBeginBrowser, onSaveApiKey, onDisconnect, onCreateApiKeyCredential, onCreateLocalCredential, onUpdateCredential, onDeleteCredential, onTestCredential, onReorderCredentials, onRefreshQuota, onVerify, onAddModel, onEditModel, onDeleteModel, selectedModelIds, modelSelectionStatus, onModelSelectionChange, onDismissError, }: {
|
|
6
|
+
export declare function AiProviderCard({ definition, product, status, accountLabel, attempt, attemptOfferingId, apiKey, baseUrl, busy, disabled, developerMode, error, quotas, models, verifyPending, onApiKeyChange, onBaseUrlChange, onBeginApiKey, onBeginOffering, onCancelConnect, onBeginBrowser, onSaveApiKey, onDisconnect, onCreateApiKeyCredential, onCreateLocalCredential, onUpdateCredential, onDeleteCredential, onTestCredential, onReorderCredentials, onRefreshQuota, onVerify, onAddModel, onEditModel, onDeleteModel, selectedModelIds, modelSelectionStatus, onModelSelectionChange, onDismissError, }: {
|
|
7
7
|
definition: AiProviderDefinition;
|
|
8
8
|
product?: AiProviderSummary;
|
|
9
9
|
status: ProviderConnectionState;
|
|
@@ -14,6 +14,7 @@ export declare function AiProviderCard({ definition, product, status, accountLab
|
|
|
14
14
|
baseUrl?: string;
|
|
15
15
|
busy: boolean;
|
|
16
16
|
disabled?: boolean;
|
|
17
|
+
developerMode?: boolean;
|
|
17
18
|
error?: AiOfferingActionError;
|
|
18
19
|
quotas?: Partial<Record<string, AiOfferingQuotaState>>;
|
|
19
20
|
models: AiGatewayModel[];
|
|
@@ -8,7 +8,7 @@ import { AiProviderHeader } from './AiProviderHeader.js';
|
|
|
8
8
|
import { authorizationMethodsForOffering, isBrowserConnectMethod } from './authorization-methods.js';
|
|
9
9
|
import { AiCredentialPoolSection, } from './AiCredentialPoolSection.js';
|
|
10
10
|
import { AiModelSearchInput, AiModelRow, modelIconTokens, } from './AiModelCatalog.js';
|
|
11
|
-
export function AiProviderCard({ definition, product, status, accountLabel, attempt, attemptOfferingId, apiKey, baseUrl = '', busy, disabled = false, error, quotas, models, verifyPending = false, onApiKeyChange, onBaseUrlChange, onBeginApiKey, onBeginOffering, onCancelConnect, onBeginBrowser, onSaveApiKey, onDisconnect, onCreateApiKeyCredential, onCreateLocalCredential, onUpdateCredential, onDeleteCredential, onTestCredential, onReorderCredentials, onRefreshQuota, onVerify, onAddModel, onEditModel, onDeleteModel, selectedModelIds, modelSelectionStatus, onModelSelectionChange, onDismissError, }) {
|
|
11
|
+
export function AiProviderCard({ definition, product, status, accountLabel, attempt, attemptOfferingId, apiKey, baseUrl = '', busy, disabled = false, developerMode = false, error, quotas, models, verifyPending = false, onApiKeyChange, onBaseUrlChange, onBeginApiKey, onBeginOffering, onCancelConnect, onBeginBrowser, onSaveApiKey, onDisconnect, onCreateApiKeyCredential, onCreateLocalCredential, onUpdateCredential, onDeleteCredential, onTestCredential, onReorderCredentials, onRefreshQuota, onVerify, onAddModel, onEditModel, onDeleteModel, selectedModelIds, modelSelectionStatus, onModelSelectionChange, onDismissError, }) {
|
|
12
12
|
const workbench = product?.offerings.find((offering) => offering.consoleUrl
|
|
13
13
|
&& authorizationMethodsForOffering(offering).some(isBrowserConnectMethod))?.consoleUrl;
|
|
14
14
|
const isConfigured = status === 'configured';
|
|
@@ -44,7 +44,7 @@ export function AiProviderCard({ definition, product, status, accountLabel, atte
|
|
|
44
44
|
setLocalSelectedModelIds(nextModelIds);
|
|
45
45
|
onModelSelectionChange?.(definition.id, nextModelIds);
|
|
46
46
|
};
|
|
47
|
-
return (_jsx(TooltipProvider, { children: _jsxs("div", { className: "space-y-8", children: [_jsx(AiProviderHeader, { name: definition.name, mark: providerMark(definition.id), avatar: getProviderAvatar(definition.id), avatarBackground: getProviderAvatarBackground(definition.id), infoLabel: "\u63D0\u4F9B\u5546\u8BF4\u660E", infoLines: [definition.description, '连接凭据保存在当前 Pod,由 Pod 权限保护。'], link: { href: definition.homeUrl, label: '访问官网' }, links: workbench ? [{ href: workbench, label: '打开工作台' }] : [], badge: (_jsx(Badge, { variant: isConnected || isConfigured ? 'default' : 'secondary', children: connectionStatusLabel(status) })) }), _jsx(AiCredentialPoolSection, { definition: definition, product: product, status: status, accountLabel: accountLabel, attempt: attempt, attemptOfferingId: attemptOfferingId, apiKey: apiKey, baseUrl: baseUrl, busy: busy, disabled: disabled, error: error, suppressError: Boolean(catalogError), quotas: quotas, onApiKeyChange: onApiKeyChange, onBaseUrlChange: onBaseUrlChange, onBeginApiKey: onBeginApiKey, onBeginOffering: onBeginOffering, onCancelConnect: onCancelConnect, onBeginBrowser: onBeginBrowser, onSaveApiKey: onSaveApiKey, onDisconnect: onDisconnect, onCreateApiKeyCredential: product?.offerings.length ? onCreateApiKeyCredential : undefined, onCreateLocalCredential: product?.offerings.length ? onCreateLocalCredential : undefined, onUpdateCredential: onUpdateCredential, onDeleteCredential: onDeleteCredential, onTestCredential: onTestCredential, onReorderCredentials: onReorderCredentials, onRefreshQuota: onRefreshQuota, onDismissError: onDismissError }), _jsxs("section", { className: "space-y-8", children: [_jsxs("div", { "data-testid": "provider-models-header", className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [_jsxs("h3", { className: "flex items-center gap-2 text-sm font-medium text-foreground/90", children: [_jsx(Box, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u53EF\u7528\u6A21\u578B"] }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["\u5171 ", catalog.length, " \u00B7 \u5DF2\u52A0\u5165 ", selectedModelCount, " \u00B7 \u5DF2\u5931\u6548 ", unavailableModelCount] }), modelSelectionStatus === 'saving' ? (_jsxs("span", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Loader2, { "aria-hidden": "true", className: "h-3 w-3 animate-spin motion-reduce:animate-none" }), "\u4FDD\u5B58\u4E2D\u2026"] })) : null, modelSelectionStatus === 'saved' ? (_jsxs("span", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Check, { "aria-hidden": "true", className: "h-3 w-3" }), "\u5DF2\u4FDD\u5B58"] })) : null] }), _jsxs("div", { "data-testid": "provider-models-actions", className: "flex w-full flex-wrap items-center gap-2 sm:w-auto sm:flex-nowrap sm:justify-end", children: [(isConfigured || isConnected) && (onVerify || onAddModel) ? (_jsxs(_Fragment, { children: [onAddModel ? (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: disabled || busy, onClick: onAddModel, children: [_jsx(Plus, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "\u6DFB\u52A0\u6A21\u578B"] })) : null, onVerify && models.length > 0 ? (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: disabled || busy || verifyPending, onClick: onVerify, children: [verifyPending
|
|
47
|
+
return (_jsx(TooltipProvider, { children: _jsxs("div", { className: "space-y-8", children: [_jsx(AiProviderHeader, { name: definition.name, mark: providerMark(definition.id), avatar: getProviderAvatar(definition.id), avatarBackground: getProviderAvatarBackground(definition.id), infoLabel: "\u63D0\u4F9B\u5546\u8BF4\u660E", infoLines: [definition.description, '连接凭据保存在当前 Pod,由 Pod 权限保护。'], link: { href: definition.homeUrl, label: '访问官网' }, links: workbench ? [{ href: workbench, label: '打开工作台' }] : [], badge: (_jsx(Badge, { variant: isConnected || isConfigured ? 'default' : 'secondary', children: connectionStatusLabel(status) })) }), _jsx(AiCredentialPoolSection, { definition: definition, product: product, status: status, accountLabel: accountLabel, attempt: attempt, attemptOfferingId: attemptOfferingId, apiKey: apiKey, baseUrl: baseUrl, busy: busy, disabled: disabled, developerMode: developerMode, error: error, suppressError: Boolean(catalogError), quotas: quotas, onApiKeyChange: onApiKeyChange, onBaseUrlChange: onBaseUrlChange, onBeginApiKey: onBeginApiKey, onBeginOffering: onBeginOffering, onCancelConnect: onCancelConnect, onBeginBrowser: onBeginBrowser, onSaveApiKey: onSaveApiKey, onDisconnect: onDisconnect, onCreateApiKeyCredential: product?.offerings.length ? onCreateApiKeyCredential : undefined, onCreateLocalCredential: product?.offerings.length ? onCreateLocalCredential : undefined, onUpdateCredential: onUpdateCredential, onDeleteCredential: onDeleteCredential, onTestCredential: onTestCredential, onReorderCredentials: onReorderCredentials, onRefreshQuota: onRefreshQuota, onDismissError: onDismissError }), _jsxs("section", { className: "space-y-8", children: [_jsxs("div", { "data-testid": "provider-models-header", className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [_jsxs("h3", { className: "flex items-center gap-2 text-sm font-medium text-foreground/90", children: [_jsx(Box, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u53EF\u7528\u6A21\u578B"] }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["\u5171 ", catalog.length, " \u00B7 \u5DF2\u52A0\u5165 ", selectedModelCount, " \u00B7 \u5DF2\u5931\u6548 ", unavailableModelCount] }), modelSelectionStatus === 'saving' ? (_jsxs("span", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Loader2, { "aria-hidden": "true", className: "h-3 w-3 animate-spin motion-reduce:animate-none" }), "\u4FDD\u5B58\u4E2D\u2026"] })) : null, modelSelectionStatus === 'saved' ? (_jsxs("span", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Check, { "aria-hidden": "true", className: "h-3 w-3" }), "\u5DF2\u4FDD\u5B58"] })) : null] }), _jsxs("div", { "data-testid": "provider-models-actions", className: "flex w-full flex-wrap items-center gap-2 sm:w-auto sm:flex-nowrap sm:justify-end", children: [(isConfigured || isConnected) && (onVerify || onAddModel) ? (_jsxs(_Fragment, { children: [onAddModel ? (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: disabled || busy, onClick: onAddModel, children: [_jsx(Plus, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "\u6DFB\u52A0\u6A21\u578B"] })) : null, onVerify && models.length > 0 ? (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: disabled || busy || verifyPending, onClick: onVerify, children: [verifyPending
|
|
48
48
|
? _jsx(Loader2, { "aria-hidden": "true", className: "h-3.5 w-3.5 animate-spin" })
|
|
49
49
|
: _jsx(RotateCw, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), verifyPending ? '同步中...' : '刷新模型'] })) : onVerify ? (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: disabled || busy || verifyPending, onClick: onVerify, children: [verifyPending
|
|
50
50
|
? _jsx(Loader2, { "aria-hidden": "true", className: "h-3.5 w-3.5 animate-spin" })
|
|
@@ -8,5 +8,5 @@ import { AiInfoTooltip } from './AiInfoTooltip.js';
|
|
|
8
8
|
* anatomy lives here once instead of being re-typed per page.
|
|
9
9
|
*/
|
|
10
10
|
export function AiProviderHeader({ name, mark, avatar, avatarBackground, infoLabel, infoLines, link, links = [], badge, }) {
|
|
11
|
-
return (_jsxs("header", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Avatar, { className: "h-9 w-9 shrink-0 rounded-lg border border-border/50 bg-muted/50 shadow-sm", style: avatarBackground ? { backgroundColor: avatarBackground } : undefined, children: [_jsx(AvatarImage, { src: avatar, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-lg bg-transparent text-sm font-bold uppercase text-muted-foreground", children: mark })] }), _jsxs("div", { className: "flex flex-col justify-center gap-0.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h2", { className: "text-base font-semibold leading-none tracking-tight text-foreground", children: name }), _jsx(AiInfoTooltip, { label: infoLabel, lines: infoLines })] }), _jsx("div", { role: "group", "aria-label": `${name}官方链接`, className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [link, ...links].map((item) => (_jsxs("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-0.5 text-[10px] leading-none text-muted-foreground transition-colors hover:text-primary", children: [item.label, " ", _jsx(ExternalLink, { "aria-hidden": "true", className: "h-2.5 w-2.5" })] }, item.href))) })] })] }), badge] }));
|
|
11
|
+
return (_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsxs(Avatar, { className: "h-9 w-9 shrink-0 rounded-lg border border-border/50 bg-muted/50 shadow-sm", style: avatarBackground ? { backgroundColor: avatarBackground } : undefined, children: [_jsx(AvatarImage, { src: avatar, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-lg bg-transparent text-sm font-bold uppercase text-muted-foreground", children: mark })] }), _jsxs("div", { className: "flex min-w-0 flex-col justify-center gap-0.5", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("h2", { className: "text-base font-semibold leading-none tracking-tight text-foreground", children: name }), _jsx(AiInfoTooltip, { label: infoLabel, lines: infoLines })] }), _jsx("div", { role: "group", "aria-label": `${name}官方链接`, className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [link, ...links].map((item) => (_jsxs("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-0.5 text-[10px] leading-none text-muted-foreground transition-colors hover:text-primary", children: [item.label, " ", _jsx(ExternalLink, { "aria-hidden": "true", className: "h-2.5 w-2.5" })] }, item.href))) })] })] }), badge ? _jsx("div", { className: "shrink-0 whitespace-nowrap", children: badge }) : null] }));
|
|
12
12
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function apiKeyFingerprint(apiKey: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export async function apiKeyFingerprint(apiKey) {
|
|
2
|
+
const subtle = globalThis.crypto?.subtle;
|
|
3
|
+
if (!subtle)
|
|
4
|
+
return undefined;
|
|
5
|
+
const digest = await subtle.digest('SHA-256', new TextEncoder().encode(apiKey));
|
|
6
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
7
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AiConnectAttempt, AiConnectionsCredential, AiConnectionsProvider, AiGatewayModel, AiGatewayCatalogModel, AiProviderAuthorizationMethodsSummary, AiProviderCredentialSummary, AiProviderSummary, CustomProviderModel,
|
|
1
|
+
import type { AiConnectAttempt, AiConnectionsCredential, AiConnectionsProvider, AiGatewayModel, AiGatewayCatalogModel, AiProviderAuthorizationMethodsSummary, AiProviderCredentialSummary, AiProviderSummary, CustomProviderModel, ProviderModelDiscovery } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Everything that turns untrusted API payloads, user input and failures into the
|
|
4
4
|
* shapes the rest of the app can rely on: proxy/pod URL normalising, error text,
|
|
@@ -38,7 +38,6 @@ export declare class AiConnectionsRequestError extends Error {
|
|
|
38
38
|
export declare function aiConnectionsErrorCode(payload: unknown): string | undefined;
|
|
39
39
|
export declare function readJson(response: Response): Promise<unknown>;
|
|
40
40
|
export declare function assertProvider(provider: string): asserts provider is AiConnectionsProvider;
|
|
41
|
-
export declare function parseGatewayKeyRecord(value: unknown): GatewayKeyRecord | undefined;
|
|
42
41
|
export declare function parseCustomModelList(value: unknown): CustomProviderModel[];
|
|
43
42
|
export declare function parseGatewayModel(value: unknown): AiGatewayModel | undefined;
|
|
44
43
|
export declare function parseGatewayCatalogModel(value: unknown): AiGatewayCatalogModel | undefined;
|
|
@@ -101,39 +101,6 @@ export function assertProvider(provider) {
|
|
|
101
101
|
throw new Error(`Unsupported AI provider: ${provider}`);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
export function parseGatewayKeyRecord(value) {
|
|
105
|
-
if (!isRecord(value)
|
|
106
|
-
|| typeof value.id !== 'string'
|
|
107
|
-
|| typeof value.owner !== 'string'
|
|
108
|
-
|| !Array.isArray(value.scopes)
|
|
109
|
-
|| !value.scopes.every((scope) => typeof scope === 'string')
|
|
110
|
-
|| typeof value.createdAt !== 'string') {
|
|
111
|
-
return undefined;
|
|
112
|
-
}
|
|
113
|
-
return compactObject({
|
|
114
|
-
id: value.id,
|
|
115
|
-
kind: value.kind === 'client-credentials' ? value.kind : undefined,
|
|
116
|
-
credentialResource: stringValue(value.credentialResource),
|
|
117
|
-
fingerprint: stringValue(value.fingerprint),
|
|
118
|
-
owner: value.owner,
|
|
119
|
-
scopes: value.scopes,
|
|
120
|
-
createdAt: value.createdAt,
|
|
121
|
-
expiresAt: stringValue(value.expiresAt),
|
|
122
|
-
lastUsedAt: stringValue(value.lastUsedAt),
|
|
123
|
-
disabledAt: stringValue(value.disabledAt),
|
|
124
|
-
revokedAt: stringValue(value.revokedAt),
|
|
125
|
-
name: stringValue(value.name),
|
|
126
|
-
maskedHint: stringValue(value.maskedHint),
|
|
127
|
-
plaintextAvailable: typeof value.plaintextAvailable === 'boolean' ? value.plaintextAvailable : undefined,
|
|
128
|
-
// Recorded by the server when the credential was issued/applied; the app
|
|
129
|
-
// needs them to revoke the CSS credential without a stored secret.
|
|
130
|
-
clientCredentialId: stringValue(value.clientCredentialId),
|
|
131
|
-
appliedTo: stringValue(value.appliedTo),
|
|
132
|
-
appliedOn: stringValue(value.appliedOn),
|
|
133
|
-
appliedAt: stringValue(value.appliedAt),
|
|
134
|
-
appliedClients: stringListValue(value.appliedClients),
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
104
|
export function parseCustomModelList(value) {
|
|
138
105
|
if (!Array.isArray(value)) {
|
|
139
106
|
throw new Error('AI Connection returned an invalid custom models response');
|
|
@@ -4,4 +4,4 @@ import type { AiConnectionsClient, CreateAiConnectionsClientInput } from './type
|
|
|
4
4
|
* fetch, with every response normalised by `normalize.ts`.
|
|
5
5
|
*/
|
|
6
6
|
export declare function resolveAiConnectionsApiBase(podBaseUrl: string): string;
|
|
7
|
-
export declare function createAiConnectionsClient({ webId, podBaseUrl, authenticatedFetch, }: CreateAiConnectionsClientInput): AiConnectionsClient;
|
|
7
|
+
export declare function createAiConnectionsClient({ webId, podBaseUrl, authenticatedFetch, clientCredentials, }: CreateAiConnectionsClientInput): AiConnectionsClient;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { apiKeyFingerprint } from './fingerprint.js';
|
|
2
|
+
import { AiConnectionsRequestError, assertProvider, compactObject, isDefined, isRecord, parseAuthorizationMethodsSummary, parseConnectAttempt, parseCredential, parseCustomModelList, parseGatewayModel, parseGatewayCatalogModel, parseModelDiscovery, parseProviderCredentialSummary, parseProviderSummaries, readJson, sanitizePublicObject, } from './normalize.js';
|
|
2
3
|
/**
|
|
3
4
|
* The HTTP client itself: one factory over the Pod base URL and an authenticated
|
|
4
5
|
* fetch, with every response normalised by `normalize.ts`.
|
|
@@ -10,8 +11,44 @@ export function resolveAiConnectionsApiBase(podBaseUrl) {
|
|
|
10
11
|
}
|
|
11
12
|
return parsed.origin;
|
|
12
13
|
}
|
|
13
|
-
export function createAiConnectionsClient({ webId, podBaseUrl, authenticatedFetch, }) {
|
|
14
|
+
export function createAiConnectionsClient({ webId, podBaseUrl, authenticatedFetch, clientCredentials, }) {
|
|
14
15
|
const apiBase = resolveAiConnectionsApiBase(podBaseUrl);
|
|
16
|
+
/**
|
|
17
|
+
* Xpod key management is Account client-credentials management. A client that
|
|
18
|
+
* was built without the capability fails closed: it must never fall back to
|
|
19
|
+
* the Gateway key routes that this product no longer has, and the contract
|
|
20
|
+
* layer carries the fact (`account_client_credentials_unavailable`) rather
|
|
21
|
+
* than the sentence the applet shows.
|
|
22
|
+
*/
|
|
23
|
+
const accountCredentials = () => {
|
|
24
|
+
if (!clientCredentials) {
|
|
25
|
+
throw new AiConnectionsRequestError({ code: 'account_client_credentials_unavailable' }, 0);
|
|
26
|
+
}
|
|
27
|
+
return clientCredentials;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The wrapper only exists in the session that issued it, so its digest is a
|
|
31
|
+
* session-local observation: refreshing the Account list must not erase the
|
|
32
|
+
* ability to verify the exact key just applied, and a reload must not invent
|
|
33
|
+
* one. Nothing here is persisted; the Account remains the only key index.
|
|
34
|
+
*/
|
|
35
|
+
const observedFingerprints = new Map();
|
|
36
|
+
const accountEntryRecord = (entry, options = {}) => ({
|
|
37
|
+
id: entry.clientId,
|
|
38
|
+
kind: 'client-credentials',
|
|
39
|
+
credentialResource: entry.resource,
|
|
40
|
+
clientCredentialId: entry.clientId,
|
|
41
|
+
// The digest of the applied wrapper (identical to what the native adapter
|
|
42
|
+
// reports), never the Account client id: the UI compares the two, so a
|
|
43
|
+
// browser-side id here would make every verify look like a key change.
|
|
44
|
+
...(options.fingerprint ? { fingerprint: options.fingerprint } : {}),
|
|
45
|
+
// Only the Account-verified WebID counts as ownership. An entry whose
|
|
46
|
+
// owner is unknown is never relabelled as the selected identity.
|
|
47
|
+
owner: entry.webId ?? webId,
|
|
48
|
+
name: options.name ?? entry.label ?? entry.clientId,
|
|
49
|
+
// The wrapper is only ever visible in the session that created it.
|
|
50
|
+
plaintextAvailable: false,
|
|
51
|
+
});
|
|
15
52
|
const request = async (path, method, body, context = {}) => {
|
|
16
53
|
const response = await authenticatedFetch(`${apiBase}${path}`, {
|
|
17
54
|
method,
|
|
@@ -19,6 +56,7 @@ export function createAiConnectionsClient({ webId, podBaseUrl, authenticatedFetc
|
|
|
19
56
|
mode: 'cors',
|
|
20
57
|
headers: {
|
|
21
58
|
accept: 'application/json',
|
|
59
|
+
'X-Xpod-Pod-Url': podBaseUrl,
|
|
22
60
|
...(body ? { 'content-type': 'application/json' } : {}),
|
|
23
61
|
},
|
|
24
62
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
@@ -81,30 +119,38 @@ export function createAiConnectionsClient({ webId, podBaseUrl, authenticatedFetc
|
|
|
81
119
|
return payload.data.map(parseGatewayCatalogModel).filter(isDefined);
|
|
82
120
|
},
|
|
83
121
|
async listGatewayKeys() {
|
|
84
|
-
const
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
|
|
122
|
+
const entries = await accountCredentials().list();
|
|
123
|
+
return entries
|
|
124
|
+
// The Account can own credentials for several WebIDs; this page only
|
|
125
|
+
// ever shows the selected authenticated identity's rows.
|
|
126
|
+
.filter((entry) => entry.webId === webId)
|
|
127
|
+
.map((entry) => accountEntryRecord(entry, { fingerprint: observedFingerprints.get(entry.clientId) }));
|
|
88
128
|
},
|
|
89
129
|
async createGatewayKey(input) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return record;
|
|
130
|
+
const issued = await accountCredentials().create({ name: input.name, webId });
|
|
131
|
+
const fingerprint = await apiKeyFingerprint(issued.apiKey);
|
|
132
|
+
if (fingerprint)
|
|
133
|
+
observedFingerprints.set(issued.clientId, fingerprint);
|
|
134
|
+
return {
|
|
135
|
+
plaintext: issued.apiKey,
|
|
136
|
+
record: {
|
|
137
|
+
...accountEntryRecord({ clientId: issued.clientId, label: input.name, resource: issued.resource, webId }, { name: input.name, fingerprint }),
|
|
138
|
+
// The wrapper exists right now, in this session: this row is not the
|
|
139
|
+
// "cannot show it again" case the plain list row is.
|
|
140
|
+
plaintextAvailable: undefined,
|
|
141
|
+
...(input.appliedTo ? { appliedTo: input.appliedTo } : {}),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
105
144
|
},
|
|
106
145
|
async deleteGatewayKey(keyId) {
|
|
107
|
-
|
|
146
|
+
const credentials = accountCredentials();
|
|
147
|
+
const target = (await credentials.list())
|
|
148
|
+
.find((entry) => entry.clientId === keyId && entry.webId === webId);
|
|
149
|
+
// The Account is the only key index, so a credential it no longer lists is
|
|
150
|
+
// a fact the caller reports, not a Pod record this client can recreate.
|
|
151
|
+
if (!target)
|
|
152
|
+
throw new AiConnectionsRequestError({ code: 'account_client_credential_not_found' }, 0);
|
|
153
|
+
await credentials.revoke({ clientId: target.clientId, resource: target.resource, webId });
|
|
108
154
|
},
|
|
109
155
|
async beginConnect(provider, mode, options) {
|
|
110
156
|
return await requestConnect(provider, '/connect/begin', 'POST', compactObject({
|
|
@@ -2,7 +2,7 @@ import type { AIModelClass } from '@undefineds.co/models';
|
|
|
2
2
|
/**
|
|
3
3
|
* Wire and view types of the AI connections API. No behaviour.
|
|
4
4
|
*/
|
|
5
|
-
import type { AiConnectionsModelSelection, AiConnectionsOAuthCredential } from '@undefineds.co/extension-sdk/web';
|
|
5
|
+
import type { AiClientCredentialsCapability, AiConnectionsModelSelection, AiConnectionsOAuthCredential } from '@undefineds.co/extension-sdk/web';
|
|
6
6
|
export declare const AI_CONNECTIONS_PROVIDERS: readonly ["openai", "anthropic", "kimi", "bailian", "deepseek", "zhipu", "ollama", "custom"];
|
|
7
7
|
export type AiConnectionsProvider = (typeof AI_CONNECTIONS_PROVIDERS)[number];
|
|
8
8
|
export type AiConnectionsMode = 'browserAssistedApiKey' | 'deviceCodeOAuth' | 'authorizationCodeOAuth' | 'connectUnsupported';
|
|
@@ -57,14 +57,32 @@ export interface AiQuotaSnapshot {
|
|
|
57
57
|
source: string;
|
|
58
58
|
stale?: boolean;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* One row of the Xpod key list as the page renders it.
|
|
62
|
+
*
|
|
63
|
+
* The Account owns the credential, so a record carries identity
|
|
64
|
+
* (`id`/`clientCredentialId`/`credentialResource`) and the label the user gave
|
|
65
|
+
* it. `lastUsedAt`/`appliedAt`/`expiresAt`/`appliedTo` are session-local
|
|
66
|
+
* observations or absent: the Account does not own them, and nothing here is a
|
|
67
|
+
* second key index.
|
|
68
|
+
*/
|
|
60
69
|
export interface GatewayKeyRecord {
|
|
61
70
|
id: string;
|
|
62
71
|
kind?: 'client-credentials';
|
|
63
72
|
credentialResource?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Digest of the `sk-` wrapper the known client configuration was written with
|
|
75
|
+
* (the same value the native adapters report as `apiKeyFingerprint`), never a
|
|
76
|
+
* secret. It is a session-local observation: the Account owns no wrapper, so a
|
|
77
|
+
* row restored from the Account list has no digest and cannot be tested. The
|
|
78
|
+
* credential's equality identity stays `clientCredentialId`.
|
|
79
|
+
*/
|
|
64
80
|
fingerprint?: string;
|
|
65
81
|
owner: string;
|
|
66
|
-
scopes
|
|
67
|
-
|
|
82
|
+
/** Account-owned scopes are not projected here; the wrapper's reach is the Pod. */
|
|
83
|
+
scopes?: string[];
|
|
84
|
+
/** Account credits carry no creation timestamp; absent rather than invented. */
|
|
85
|
+
createdAt?: string;
|
|
68
86
|
expiresAt?: string;
|
|
69
87
|
lastUsedAt?: string;
|
|
70
88
|
disabledAt?: string;
|
|
@@ -283,19 +301,22 @@ export interface AiConnectionsClient {
|
|
|
283
301
|
listGatewayModels?(): Promise<AiGatewayModel[]>;
|
|
284
302
|
/** Complete Gateway directory, including platform routing roles and unattributed entries. */
|
|
285
303
|
listGatewayCatalogModels?(): Promise<AiGatewayCatalogModel[]>;
|
|
304
|
+
/**
|
|
305
|
+
* Xpod key management is Account client-credentials management. These three
|
|
306
|
+
* calls are backed by the host's `AiClientCredentialsCapability` and never by
|
|
307
|
+
* a Gateway key route: a client without that capability fails closed.
|
|
308
|
+
*
|
|
309
|
+
* There is deliberately no enable/disable call - the Account has no such
|
|
310
|
+
* capability - and no reveal call: the `sk-` wrapper exists only in the create
|
|
311
|
+
* response and in `plaintexts` for the session that issued it.
|
|
312
|
+
*/
|
|
286
313
|
listGatewayKeys(): Promise<GatewayKeyRecord[]>;
|
|
287
314
|
createGatewayKey(input: {
|
|
288
315
|
name: string;
|
|
289
|
-
apiKey?: string;
|
|
290
316
|
credentialResource?: string;
|
|
291
|
-
/** Client application the
|
|
317
|
+
/** Client application the key is meant for; a session-local annotation only. */
|
|
292
318
|
appliedTo?: string;
|
|
293
|
-
scopes?: string[];
|
|
294
|
-
expiresAt?: string;
|
|
295
319
|
}): Promise<CreatedGatewayKey>;
|
|
296
|
-
updateGatewayKey(keyId: string, input: {
|
|
297
|
-
enabled: boolean;
|
|
298
|
-
}): Promise<GatewayKeyRecord>;
|
|
299
320
|
deleteGatewayKey(keyId: string): Promise<void>;
|
|
300
321
|
beginConnect(provider: AiConnectionsProvider, mode: AiConnectionsMode, options?: AiConnectionBeginOptions): Promise<AiConnectAttempt>;
|
|
301
322
|
connectStatus(provider: AiConnectionsProvider, attempt: Pick<AiConnectAttempt, 'attemptId' | 'state' | 'signature' | 'offeringId'> & Partial<Pick<AiConnectAttempt, 'mode'>>): Promise<AiConnectAttempt>;
|
|
@@ -349,4 +370,10 @@ export interface CreateAiConnectionsClientInput {
|
|
|
349
370
|
webId: string;
|
|
350
371
|
podBaseUrl: string;
|
|
351
372
|
authenticatedFetch: typeof fetch;
|
|
373
|
+
/**
|
|
374
|
+
* The Account client-credentials capability that backs Xpod key management.
|
|
375
|
+
* Optional at the type level so a Pod-only client still type-checks, but the
|
|
376
|
+
* three key calls fail closed without it.
|
|
377
|
+
*/
|
|
378
|
+
clientCredentials?: AiClientCredentialsCapability;
|
|
352
379
|
}
|
|
@@ -121,11 +121,14 @@ export function createAiConnectionsController(host) {
|
|
|
121
121
|
return guard;
|
|
122
122
|
};
|
|
123
123
|
const client = authenticated
|
|
124
|
-
? createInteractiveAiConnectionsClient(
|
|
124
|
+
? createInteractiveAiConnectionsClient(createAiConnectionsClient({
|
|
125
125
|
webId: sessionSnapshot.webId,
|
|
126
126
|
podBaseUrl: readyPod.current.podUrl,
|
|
127
127
|
authenticatedFetch: host.solid.session.fetch,
|
|
128
|
-
|
|
128
|
+
// Account client credentials are the only Xpod key store there is; the
|
|
129
|
+
// key calls are backed by this capability and never by a Pod route.
|
|
130
|
+
clientCredentials: host.capabilities.aiClientCredentials,
|
|
131
|
+
}), host.capabilities.aiConnectionsPodStore, liveCredentials, beginProviderLoad)
|
|
129
132
|
: null;
|
|
130
133
|
let selectedSection = 'keys';
|
|
131
134
|
let selectedProvider = 'openai';
|
|
@@ -445,50 +448,6 @@ export function createAiConnectionsController(host) {
|
|
|
445
448
|
};
|
|
446
449
|
return controller;
|
|
447
450
|
}
|
|
448
|
-
function withAccountClientCredentials(client, credentials) {
|
|
449
|
-
return {
|
|
450
|
-
...client,
|
|
451
|
-
async createGatewayKey(input) {
|
|
452
|
-
if (!credentials)
|
|
453
|
-
throw new Error('当前账号登录状态不支持创建客户端凭据。');
|
|
454
|
-
const issued = await credentials.create({ name: input.name, webId: client.webId });
|
|
455
|
-
try {
|
|
456
|
-
// The wrapper is issued here; the declared purpose and the rest of the
|
|
457
|
-
// caller's input travel with it so the record keeps where it applies.
|
|
458
|
-
return await client.createGatewayKey({
|
|
459
|
-
...input,
|
|
460
|
-
apiKey: issued.apiKey,
|
|
461
|
-
credentialResource: issued.resource,
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
catch (cause) {
|
|
465
|
-
try {
|
|
466
|
-
await credentials.revoke({
|
|
467
|
-
clientId: clientIdFromApiKey(issued.apiKey), resource: issued.resource, webId: client.webId,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
catch {
|
|
471
|
-
throw new Error('API Key 未能保存到 Pod,且账号凭据撤销失败。该凭据尚未应用到客户端。');
|
|
472
|
-
}
|
|
473
|
-
throw cause;
|
|
474
|
-
}
|
|
475
|
-
},
|
|
476
|
-
async deleteGatewayKey(keyId) {
|
|
477
|
-
const record = (await client.listGatewayKeys()).find((key) => key.id === keyId);
|
|
478
|
-
if (record?.kind === 'client-credentials') {
|
|
479
|
-
if (!credentials || !record.clientCredentialId) {
|
|
480
|
-
throw new Error('当前账号登录状态无法撤销此客户端凭据,Key 记录已保留。');
|
|
481
|
-
}
|
|
482
|
-
const issued = await credentials.list();
|
|
483
|
-
const target = issued.find((entry) => entry.clientId === record.clientCredentialId);
|
|
484
|
-
if (!target)
|
|
485
|
-
throw new Error('账号服务中已找不到该客户端凭据,请刷新后重试。');
|
|
486
|
-
await credentials.revoke({ clientId: target.clientId, resource: target.resource, webId: client.webId });
|
|
487
|
-
}
|
|
488
|
-
await client.deleteGatewayKey(keyId);
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
451
|
function createInteractiveAiConnectionsClient(operationsClient, podStore, live, beginProviderLoad) {
|
|
493
452
|
const listProviders = async () => {
|
|
494
453
|
const guard = beginProviderLoad();
|
|
@@ -1032,11 +991,3 @@ function isDefined(value) {
|
|
|
1032
991
|
function errorMessage(error) {
|
|
1033
992
|
return aiConnectionsErrorMessage(error);
|
|
1034
993
|
}
|
|
1035
|
-
/** The wrapper carries the OIDC client id; parsing it needs no stored secret. */
|
|
1036
|
-
function clientIdFromApiKey(apiKey) {
|
|
1037
|
-
const decoded = atob(apiKey.replace(/^sk-/u, ''));
|
|
1038
|
-
const separator = decoded.indexOf(':');
|
|
1039
|
-
if (separator <= 0)
|
|
1040
|
-
throw new Error('客户端凭据格式无效。');
|
|
1041
|
-
return decoded.slice(0, separator);
|
|
1042
|
-
}
|
|
@@ -171,10 +171,15 @@ function messageForSafeErrorCode(code, provider) {
|
|
|
171
171
|
return '模型已获取,但保存到 Pod 失败。请重试同步模型。';
|
|
172
172
|
case 'quota_credential_not_found':
|
|
173
173
|
return '当前身份没有可用的额度凭证。';
|
|
174
|
+
case 'account_client_credentials_unavailable':
|
|
175
|
+
// Xpod keys are Account client credentials; a host without the Account
|
|
176
|
+
// capability has no key surface at all, so the user must re-enter from a
|
|
177
|
+
// signed-in Account session.
|
|
178
|
+
return 'Xpod 密钥 需要当前 Account 登录状态:这个客户端没有 Account 客户端凭据能力。';
|
|
179
|
+
case 'account_client_credential_not_found':
|
|
180
|
+
return '账号服务中已找不到该客户端凭据,请刷新后重试。';
|
|
174
181
|
case 'credential_secret_unavailable':
|
|
175
182
|
return '当前凭证密钥不可用,请重新保存后再查询额度。';
|
|
176
|
-
case 'gateway_api_key_plaintext_unavailable':
|
|
177
|
-
return 'Pod 中未找到此 API Key 的原文,无法复制配置。请创建新的 Key,更新客户端后再删除旧 Key。';
|
|
178
183
|
case 'quota_adapter_not_found':
|
|
179
184
|
return '该接入方式不支持查询官方额度。';
|
|
180
185
|
default:
|