@undefineds.co/xpod 0.4.3 → 0.4.5
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/config/cloud.json +1 -1
- package/config/main.json +36 -2
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +3 -0
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +1 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +2 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +101 -32
- package/dist/api/ai-gateway/connect/DeviceCodeProtocol.d.ts +80 -0
- package/dist/api/ai-gateway/connect/DeviceCodeProtocol.js +3 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.d.ts +38 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.js +63 -0
- package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.d.ts +24 -0
- package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.js +136 -0
- package/dist/api/ai-gateway/connect/OAuthConnectAdapter.d.ts +2 -0
- package/dist/api/ai-gateway/connect/OAuthConnectAdapter.js +2 -1
- package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.d.ts +17 -13
- package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.js +52 -19
- package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.d.ts +4 -28
- package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.js +7 -58
- package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.d.ts +4 -0
- package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.js +116 -0
- package/dist/api/ai-gateway/connect/SessionImportProfiles.d.ts +10 -0
- package/dist/api/ai-gateway/connect/SessionImportProfiles.js +112 -0
- package/dist/api/ai-gateway/connect/index.d.ts +125 -23
- package/dist/api/ai-gateway/connect/index.js +1277 -260
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.d.ts +4 -1
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +154 -19
- package/dist/api/ai-gateway/providers/DeepSeekRuntimeAdapter.js +10 -6
- package/dist/api/ai-gateway/providers/ModelsDevCatalog.d.ts +7 -0
- package/dist/api/ai-gateway/providers/ModelsDevCatalog.js +25 -1
- package/dist/api/ai-gateway/providers/OfferingAuthorization.d.ts +24 -0
- package/dist/api/ai-gateway/providers/OfferingAuthorization.js +37 -0
- package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.d.ts +1 -1
- package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.js +5 -3
- package/dist/api/ai-gateway/providers/ProviderRegistry.d.ts +6 -3
- package/dist/api/ai-gateway/providers/ProviderRegistry.js +55 -16
- package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.d.ts +2 -0
- package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.js +37 -4
- package/dist/api/ai-gateway/providers/ProviderRuntimeRegistry.js +7 -0
- package/dist/api/ai-gateway/quota/SubscriptionQuotaAdapters.js +2 -1
- package/dist/api/ai-gateway/routing/ModelRouter.js +3 -1
- package/dist/api/container/common.js +30 -40
- package/dist/api/container/index.js +6 -2
- package/dist/api/container/routes.js +5 -22
- package/dist/api/handlers/AiClientConfigurationHandler.js +39 -1
- package/dist/api/handlers/AiGatewayManagementHandler.d.ts +3 -0
- package/dist/api/handlers/AiGatewayManagementHandler.js +173 -58
- package/dist/api/service/AiClientConfigurationService.d.ts +4 -15
- package/dist/api/service/AiClientConfigurationService.js +17 -64
- package/dist/cli/commands/start.js +2 -0
- package/dist/components/components.jsonld +1 -0
- package/dist/components/context.jsonld +80 -0
- package/dist/http/InternalPodDataHttpHandler.d.ts +3 -0
- package/dist/http/InternalPodDataHttpHandler.js +13 -2
- package/dist/http/InternalPodDataHttpHandler.jsonld +12 -0
- package/dist/identity/oidc/RememberedClientPromptFactory.d.ts +1 -1
- package/dist/identity/oidc/RememberedClientPromptFactory.js +5 -2
- package/dist/identity/oidc/RememberedConsentHandler.js +1 -1
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +1 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +26 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +4 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.d.ts +10 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.js +21 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.jsonld +172 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -2
- package/dist/runtime/component-parameter-keys.js +18 -2
- package/dist/storage/accessors/MixDataAccessor.js +6 -2
- package/dist/storage/rdf/PostgresRdfEngine.js +10 -2
- package/dist/storage/rdf/RdfContentTypes.js +4 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.d.ts +3 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.js +13 -16
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsList.js +1 -3
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +127 -57
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +6 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +436 -83
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.d.ts +3 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +212 -76
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +6 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +58 -41
- package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.d.ts +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.js +63 -4
- package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.d.ts +46 -6
- package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.js +143 -14
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.d.ts +19 -3
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.js +45 -7
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.d.ts +7 -1
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.js +305 -40
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/index.cjs +383 -50
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/json-env-adapter.js +3 -2
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/pi.js +12 -3
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.d.ts +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.js +39 -4
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/types.d.ts +8 -1
- package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.d.ts +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.js +14 -0
- package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/controller.js +216 -57
- package/node_modules/@undefineds.co/ai-connections/dist/index.d.ts +3 -1
- package/node_modules/@undefineds.co/ai-connections/dist/index.js +16 -2
- package/node_modules/@undefineds.co/ai-connections/dist/provider-visuals.js +6 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/execution/ldp-executor.js +27 -6
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js +2 -2
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/sparql/builder/update-builder.js +12 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/builder.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/array.js +8 -0
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/default.js +9 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/inline.js +3 -3
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/execution/ldp-executor.js +27 -6
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js +2 -2
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/sparql/builder/update-builder.js +12 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/builder.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/array.js +8 -0
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/default.js +9 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/inline.js +3 -3
- package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +31 -0
- package/package.json +2 -2
- package/static/app/assets/{index-BikOyGPu.js → index-BCZVzMpj.js} +44 -44
- package/static/app/assets/inrupt-smoke.js +2 -2
- package/static/app/assets/main.css +1 -1
- package/static/app/assets/main.js +10 -10
- package/static/auth-callback/assets/{AiConfigContext-DrCH6ckh.js → AiConfigContext-DVkh8Kg2.js} +1 -1
- package/static/auth-callback/assets/{AiConfigPage-DVrlSSdA.js → AiConfigPage-HWVWpe-P.js} +1 -1
- package/static/auth-callback/assets/{DocumentProcessingPanel-_Md-v2R2.js → DocumentProcessingPanel-DDq-mH17.js} +1 -1
- package/static/auth-callback/assets/{IndexLifecyclePanel-BbWwe23H.js → IndexLifecyclePanel-Cx4cjrdP.js} +1 -1
- package/static/auth-callback/assets/{IndexSubjectPanel-upH9MjU-.js → IndexSubjectPanel-DzSw8M9j.js} +1 -1
- package/static/auth-callback/assets/{ModelAssignmentsPanel-CVGH4t4g.js → ModelAssignmentsPanel-BsRxGc2p.js} +1 -1
- package/static/auth-callback/assets/ModelsPage-DEe1lo6Q.js +10 -0
- package/static/auth-callback/assets/{NetworkPage-Dcbrb1fd.js → NetworkPage-DBiEh1Oz.js} +1 -1
- package/static/auth-callback/assets/{PaneListHeader-DrY-1wBq.js → PaneListHeader-lEEgjyN9.js} +1 -1
- package/static/auth-callback/assets/{SearchIndexingPanel-Dw25y53v.js → SearchIndexingPanel-CZCUUGt6.js} +1 -1
- package/static/auth-callback/assets/{StatusSubjectPanel-BHlz9rNF.js → StatusSubjectPanel-21qdueMr.js} +1 -1
- package/static/auth-callback/assets/{StatusWorkspace-BH7exQP5.js → StatusWorkspace-R8RKsqVz.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsPage-BYLKnM9f.js → SystemSettingsPage-BQ4e-4Wy.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsSubjectPanel-DT34LKku.js → SystemSettingsSubjectPanel-BopXD7SE.js} +1 -1
- package/static/auth-callback/assets/{activity-DdULRcSI.js → activity-DSfmTGfY.js} +1 -1
- package/static/auth-callback/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/auth-callback/assets/{auth-callback-D7l2P25I.js → auth-callback-DleaitAS.js} +91 -91
- package/static/auth-callback/assets/auth-callback-vPSC2d2F.css +1 -0
- package/static/auth-callback/assets/{badge-j4zjFnmg.js → badge-B0aLu1sl.js} +1 -1
- package/static/auth-callback/assets/{download-DGXoks01.js → download-D_bFwdYJ.js} +1 -1
- package/static/auth-callback/assets/{external-link-CvbgXXaI.js → external-link-DRfjTaQu.js} +1 -1
- package/static/auth-callback/assets/{index-LRWvKJ2i.js → index-CG23cLYQ.js} +1 -1
- package/static/auth-callback/assets/{index-BOK3j1dz.js → index-CyV9ejwx.js} +1 -1
- package/static/auth-callback/assets/{index-browser-C1Qpe0g4.js → index-browser-D70paxIg.js} +1 -1
- package/static/auth-callback/assets/{key-round-CmgS5UdQ.js → key-round-BjV_mVYH.js} +1 -1
- package/static/auth-callback/assets/{rotate-ccw-c4CSG6Rq.js → rotate-ccw-CKoOAfse.js} +1 -1
- package/static/auth-callback/assets/{skeleton-ZPMTiRSM.js → skeleton-BZNjZaHx.js} +1 -1
- package/static/auth-callback/assets/{waypoints-xjyBflU5.js → waypoints-DoaJVzZv.js} +1 -1
- package/static/auth-callback/assets/{workspace-layout-B9nj6F5J.js → workspace-layout-CaP46_ZR.js} +1 -1
- package/static/auth-callback/auth-callback.html +2 -2
- package/static/dashboard/assets/{AiConfigContext-BWOOSYNr.js → AiConfigContext-BSrR27yD.js} +1 -1
- package/static/dashboard/assets/{AiConfigPage-CG2kQ9Wy.js → AiConfigPage-D65Z2LET.js} +1 -1
- package/static/dashboard/assets/{DocumentProcessingPanel-B4VXajfj.js → DocumentProcessingPanel-DyTe7Nei.js} +1 -1
- package/static/dashboard/assets/{IndexLifecyclePanel-CWCy0CC8.js → IndexLifecyclePanel-fK8ZGlrL.js} +1 -1
- package/static/dashboard/assets/{IndexSubjectPanel-CSgyQnG_.js → IndexSubjectPanel-CZPGor7v.js} +1 -1
- package/static/dashboard/assets/{ModelAssignmentsPanel-CdGIGidT.js → ModelAssignmentsPanel-CfYn5LMQ.js} +1 -1
- package/static/dashboard/assets/ModelsPage-djlJsghe.js +25 -0
- package/static/dashboard/assets/{NetworkPage-COia9NCH.js → NetworkPage-Coj6GnPl.js} +1 -1
- package/static/dashboard/assets/{PaneListHeader-SZWyr602.js → PaneListHeader-CUuFNSsZ.js} +1 -1
- package/static/dashboard/assets/{SearchIndexingPanel-D6PZtG6k.js → SearchIndexingPanel-CyJLioXS.js} +1 -1
- package/static/dashboard/assets/{StatusSubjectPanel-CxRD4kv0.js → StatusSubjectPanel-Cv26GSVC.js} +1 -1
- package/static/dashboard/assets/{StatusWorkspace-D3PfmY9-.js → StatusWorkspace-CDjFrsmB.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsPage-COg1MmMj.js → SystemSettingsPage-BuNOyNoa.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsSubjectPanel-BSVVB9Ol.js → SystemSettingsSubjectPanel-B2dasdez.js} +1 -1
- package/static/dashboard/assets/{activity-BJJx5cN-.js → activity-D1d2j_4Z.js} +1 -1
- package/static/dashboard/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/dashboard/assets/{badge-elmrFSCj.js → badge-BeAAv6sp.js} +1 -1
- package/static/dashboard/assets/{dashboard-IvQrNmru.js → dashboard-BnGrUGv8.js} +68 -68
- package/static/dashboard/assets/dashboard-vPSC2d2F.css +1 -0
- package/static/dashboard/assets/{download-C3FH2KzG.js → download-DfFGiZRn.js} +1 -1
- package/static/dashboard/assets/{external-link-Bx7F0uKb.js → external-link-IsjU_LC9.js} +1 -1
- package/static/dashboard/assets/{index-liRoRIP1.js → index-BWQTucPe.js} +1 -1
- package/static/dashboard/assets/{index-M4-kWiGz.js → index-Dj75OjnW.js} +1 -1
- package/static/dashboard/assets/{key-round-CXg0bKAj.js → key-round-BJ_9wmQ_.js} +1 -1
- package/static/dashboard/assets/{rotate-ccw-yxJKOBrX.js → rotate-ccw-D3JGiFbR.js} +1 -1
- package/static/dashboard/assets/{skeleton-x9jNGrQT.js → skeleton-CC4nc-z-.js} +1 -1
- package/static/dashboard/assets/{waypoints-CTKeRsGp.js → waypoints-h7sqFqiG.js} +1 -1
- package/static/dashboard/assets/{workspace-layout--YkgkS38.js → workspace-layout-ZAPTOHhs.js} +1 -1
- package/static/dashboard/dashboard.html +2 -2
- package/static/settings/assets/{AiConfigContext-CelxXFcQ.js → AiConfigContext-lGkWpvKP.js} +1 -1
- package/static/settings/assets/{AiConfigPage-BaP-EovM.js → AiConfigPage-BbZWIBZq.js} +1 -1
- package/static/settings/assets/{DocumentProcessingPanel-DzLZluFv.js → DocumentProcessingPanel-DvYdjT-V.js} +1 -1
- package/static/settings/assets/{IndexLifecyclePanel-Bzj1-_oI.js → IndexLifecyclePanel-W5OGQZj7.js} +1 -1
- package/static/settings/assets/{IndexSubjectPanel-B263QLUh.js → IndexSubjectPanel-g_Aa5soq.js} +1 -1
- package/static/settings/assets/{ModelAssignmentsPanel-BAdT4UR0.js → ModelAssignmentsPanel-COlMBKXj.js} +1 -1
- package/static/settings/assets/ModelsPage-mApKi-aw.js +10 -0
- package/static/settings/assets/{NetworkPage-BK6a7l4g.js → NetworkPage-BDT05JUq.js} +1 -1
- package/static/settings/assets/{PaneListHeader-BAS2ICPq.js → PaneListHeader-CByhNpMG.js} +1 -1
- package/static/settings/assets/{SearchIndexingPanel-Cb_p-NgW.js → SearchIndexingPanel-BFAyzXls.js} +1 -1
- package/static/settings/assets/{StatusSubjectPanel-CcjqUua6.js → StatusSubjectPanel-Bz_XZ3VU.js} +1 -1
- package/static/settings/assets/{StatusWorkspace-7Ibty3_k.js → StatusWorkspace-CeVypHcW.js} +1 -1
- package/static/settings/assets/{SystemSettingsPage-DPSz4dg4.js → SystemSettingsPage-Dh7cQIfx.js} +1 -1
- package/static/settings/assets/{SystemSettingsSubjectPanel-Cb4HkKT5.js → SystemSettingsSubjectPanel-CSnpciO0.js} +1 -1
- package/static/settings/assets/{activity-Qhsr5Pxu.js → activity-DXz3EfFN.js} +1 -1
- package/static/settings/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/settings/assets/{badge-B1vf2qCl.js → badge-1ZA144i4.js} +1 -1
- package/static/settings/assets/{download-CYNt0aZ-.js → download-CUHimlYa.js} +1 -1
- package/static/settings/assets/{external-link-ClKb39NT.js → external-link-BYHSVj2Z.js} +1 -1
- package/static/settings/assets/{index-y8ew3MLM.js → index-DKPDMdW5.js} +1 -1
- package/static/settings/assets/{index-browser-DJhvgpNI.js → index-browser-BaEFz0e0.js} +1 -1
- package/static/settings/assets/{index-BgiIDSch.js → index-viSgSvbO.js} +1 -1
- package/static/settings/assets/{key-round-5d-DrJz-.js → key-round-COByPOg8.js} +1 -1
- package/static/settings/assets/{rotate-ccw-CHZY7kS5.js → rotate-ccw-BC2EN6cE.js} +1 -1
- package/static/settings/assets/{settings-D5VRWyx6.js → settings-DLH9XUoT.js} +86 -86
- package/static/settings/assets/settings-vPSC2d2F.css +1 -0
- package/static/settings/assets/{skeleton-FNabNxNE.js → skeleton-B0-FOhKL.js} +1 -1
- package/static/settings/assets/{waypoints-BnL_8ljo.js → waypoints-DGMsvqWo.js} +1 -1
- package/static/settings/assets/{workspace-layout-D5q3F59S.js → workspace-layout-B0SVrJIu.js} +1 -1
- package/static/settings/settings.html +2 -2
- package/static/auth-callback/assets/ModelsPage-BpkVFKbf.js +0 -10
- package/static/auth-callback/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/auth-callback/assets/auth-callback-01yz-UDU.css +0 -1
- package/static/dashboard/assets/ModelsPage-Chd5UK0D.js +0 -25
- package/static/dashboard/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/dashboard/assets/dashboard-01yz-UDU.css +0 -1
- package/static/settings/assets/ModelsPage-C0NFa1zq.js +0 -10
- package/static/settings/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/settings/assets/settings-01yz-UDU.css +0 -1
|
@@ -17,15 +17,19 @@ export class PiConfigAdapter extends BaseAiClientConfigAdapter {
|
|
|
17
17
|
const providers = models.providers && typeof models.providers === 'object' && !Array.isArray(models.providers)
|
|
18
18
|
? { ...models.providers }
|
|
19
19
|
: {};
|
|
20
|
+
const previous = isObject(providers.xpod) ? providers.xpod : {};
|
|
20
21
|
const model = profile.model;
|
|
21
22
|
settings.defaultProvider = 'xpod';
|
|
22
|
-
|
|
23
|
+
if (model)
|
|
24
|
+
settings.defaultModel = model;
|
|
23
25
|
providers.xpod = {
|
|
26
|
+
...previous,
|
|
24
27
|
baseUrl: normalizeV1Endpoint(profile.endpoint),
|
|
25
28
|
apiKey: profileApiKey(profile),
|
|
26
29
|
authHeader: true,
|
|
27
30
|
api: 'openai-responses',
|
|
28
|
-
models: activeModelReferences(profile)
|
|
31
|
+
models: profile.activeModels?.length ? activeModelReferences(profile)
|
|
32
|
+
: model ? mergeModel(previous.models, model) : previous.models ?? [],
|
|
29
33
|
};
|
|
30
34
|
models.providers = providers;
|
|
31
35
|
return new Map([
|
|
@@ -44,7 +48,7 @@ export class PiConfigAdapter extends BaseAiClientConfigAdapter {
|
|
|
44
48
|
? configuredModels.map((entry) => isObject(entry) && typeof entry.id === 'string' ? entry.id : undefined)
|
|
45
49
|
.filter((id) => id !== undefined)
|
|
46
50
|
: [];
|
|
47
|
-
return ok && modelIds.includes(profile.model
|
|
51
|
+
return ok && (!profile.model || modelIds.includes(profile.model))
|
|
48
52
|
? { ok: true }
|
|
49
53
|
: { ok: false, reason: 'Pi projection differs from the requested connection' };
|
|
50
54
|
}
|
|
@@ -88,3 +92,8 @@ function restoreOwnedProperty(target, original, key) {
|
|
|
88
92
|
function isObject(value) {
|
|
89
93
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
90
94
|
}
|
|
95
|
+
function mergeModel(models, id) {
|
|
96
|
+
const existing = Array.isArray(models) ? models : [];
|
|
97
|
+
return existing.some((entry) => isObject(entry) && entry.id === id)
|
|
98
|
+
? existing : [...existing, { id }];
|
|
99
|
+
}
|
|
@@ -17,7 +17,12 @@ export class AiClientConfigTransaction {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
const snapshots = await Promise.all(writes.map((write) => this.snapshot(write.path)));
|
|
20
|
+
// Check all guarded writes before creating backups or changing any file.
|
|
21
|
+
for (const write of writes) {
|
|
22
|
+
this.assertExpectedContent(write, snapshots.find((snapshot) => snapshot.path === write.path).content);
|
|
23
|
+
}
|
|
20
24
|
const staged = new Map();
|
|
25
|
+
const applied = [];
|
|
21
26
|
try {
|
|
22
27
|
for (const write of writes) {
|
|
23
28
|
const snapshot = snapshots.find((candidate) => candidate.path === write.path);
|
|
@@ -32,18 +37,23 @@ export class AiClientConfigTransaction {
|
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
for (const write of writes) {
|
|
40
|
+
if (write.expectedContentHash !== undefined) {
|
|
41
|
+
this.assertExpectedContent(write, (await this.snapshot(write.path)).content);
|
|
42
|
+
}
|
|
35
43
|
if (write.content === null) {
|
|
36
44
|
await fs.promises.rm(write.path, { force: true });
|
|
45
|
+
applied.push(write);
|
|
37
46
|
}
|
|
38
47
|
else {
|
|
39
48
|
await this.rename(staged.get(write.path), write.path);
|
|
49
|
+
applied.push(write);
|
|
40
50
|
await fs.promises.chmod(write.path, 0o600);
|
|
41
51
|
await this.syncDirectory(path.dirname(write.path));
|
|
42
52
|
}
|
|
43
53
|
}
|
|
44
54
|
}
|
|
45
55
|
catch (error) {
|
|
46
|
-
await this.rollback(snapshots);
|
|
56
|
+
await this.rollback(snapshots, applied);
|
|
47
57
|
throw error;
|
|
48
58
|
}
|
|
49
59
|
finally {
|
|
@@ -109,14 +119,30 @@ export class AiClientConfigTransaction {
|
|
|
109
119
|
await fs.promises.chmod(filePath, 0o600);
|
|
110
120
|
await this.syncDirectory(path.dirname(filePath));
|
|
111
121
|
}
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
assertExpectedContent(write, content) {
|
|
123
|
+
if (write.expectedContentHash === undefined)
|
|
124
|
+
return;
|
|
125
|
+
const hash = content === undefined ? undefined : crypto.createHash('sha256').update(content).digest('hex');
|
|
126
|
+
if (hash !== write.expectedContentHash) {
|
|
127
|
+
throw new Error(`AI client configuration changed since planning: ${write.path}; retry with a fresh plan`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async rollback(snapshots, applied) {
|
|
131
|
+
for (const write of [...applied].reverse()) {
|
|
132
|
+
const snapshot = snapshots.find((candidate) => candidate.path === write.path);
|
|
133
|
+
let tempPath;
|
|
114
134
|
try {
|
|
135
|
+
// An external client may refresh login state even after our write. Never
|
|
136
|
+
// roll back a file unless it still contains exactly what we wrote.
|
|
137
|
+
if (!await this.stillContainsWrite(write))
|
|
138
|
+
continue;
|
|
115
139
|
if (!snapshot.existed) {
|
|
116
140
|
await fs.promises.rm(snapshot.path, { force: true });
|
|
117
141
|
continue;
|
|
118
142
|
}
|
|
119
|
-
|
|
143
|
+
tempPath = await this.stage(snapshot.path, snapshot.content.toString('utf8'));
|
|
144
|
+
if (!await this.stillContainsWrite(write))
|
|
145
|
+
continue;
|
|
120
146
|
await this.rename(tempPath, snapshot.path);
|
|
121
147
|
await fs.promises.chmod(snapshot.path, snapshot.mode ?? 0o600);
|
|
122
148
|
await this.syncDirectory(path.dirname(snapshot.path));
|
|
@@ -124,8 +150,17 @@ export class AiClientConfigTransaction {
|
|
|
124
150
|
catch {
|
|
125
151
|
// Preserve the initiating failure. Backups remain available for recovery.
|
|
126
152
|
}
|
|
153
|
+
finally {
|
|
154
|
+
if (tempPath)
|
|
155
|
+
await fs.promises.rm(tempPath, { force: true }).catch(() => undefined);
|
|
156
|
+
}
|
|
127
157
|
}
|
|
128
158
|
}
|
|
159
|
+
async stillContainsWrite(write) {
|
|
160
|
+
const current = await this.snapshot(write.path);
|
|
161
|
+
return write.content === null ? !current.existed :
|
|
162
|
+
current.content?.equals(Buffer.from(write.content, 'utf8')) === true;
|
|
163
|
+
}
|
|
129
164
|
async syncDirectory(directory) {
|
|
130
165
|
let handle;
|
|
131
166
|
try {
|
|
@@ -7,7 +7,7 @@ export interface AiConnectionsClientProfile {
|
|
|
7
7
|
* The active model projection returned by the authenticated Xpod Gateway.
|
|
8
8
|
* Client adapters must never invent or discover provider models themselves.
|
|
9
9
|
*/
|
|
10
|
-
activeModels
|
|
10
|
+
activeModels?: readonly AiClientModelReference[];
|
|
11
11
|
/** Stable hash of the active Gateway projection used when this profile was planned. */
|
|
12
12
|
catalogVersion?: string;
|
|
13
13
|
}
|
|
@@ -17,6 +17,9 @@ export interface AiClientModelReference {
|
|
|
17
17
|
provider?: string;
|
|
18
18
|
displayName?: string;
|
|
19
19
|
availability?: AiClientModelAvailability;
|
|
20
|
+
contextWindow?: number;
|
|
21
|
+
inputModalities?: readonly string[];
|
|
22
|
+
capabilities?: readonly string[];
|
|
20
23
|
}
|
|
21
24
|
export type ClientOwnership = 'unowned' | 'owned' | 'foreign';
|
|
22
25
|
export interface ClientDetection {
|
|
@@ -27,6 +30,8 @@ export interface ClientDetection {
|
|
|
27
30
|
export interface ClientInspection {
|
|
28
31
|
ownership: ClientOwnership;
|
|
29
32
|
webIdHash?: string;
|
|
33
|
+
apiKeyFingerprint?: string;
|
|
34
|
+
projectionMatches?: boolean;
|
|
30
35
|
configPaths: string[];
|
|
31
36
|
}
|
|
32
37
|
export interface ClientVerification {
|
|
@@ -38,6 +43,8 @@ export interface ConfigWrite {
|
|
|
38
43
|
content: string | null;
|
|
39
44
|
backupPath?: string;
|
|
40
45
|
createBackup?: boolean;
|
|
46
|
+
/** Refuse a stale write if the file changed after planning. */
|
|
47
|
+
expectedContentHash?: string;
|
|
41
48
|
}
|
|
42
49
|
export interface AiClientConfigPlan {
|
|
43
50
|
client: string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getProviderAvatar } from './provider-visuals.js';
|
|
2
|
+
// Brand marks for supported coding clients. Codex/Claude Code reuse the
|
|
3
|
+
// OpenAI/Anthropic provider marks; Pi and CodeBuddy use their official logos.
|
|
4
|
+
const piAvatar = 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%20800%22%3E%3Crect%20width%3D%22800%22%20height%3D%22800%22%20rx%3D%22120%22%20fill%3D%22%2309090b%22%2F%3E%3Cpath%20fill%3D%22%23fff%22%20fill-rule%3D%22evenodd%22%20d%3D%22%20M165.29%20165.29%20H517.36%20V400%20H400%20V517.36%20H282.65%20V634.72%20H165.29%20Z%20M282.65%20282.65%20V400%20H400%20V282.65%20Z%20%22%2F%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M517.36%20400%20H634.72%20V634.72%20H517.36%20Z%22%2F%3E%3C%2Fsvg%3E';
|
|
5
|
+
const codebuddyAvatar = 'data:image/svg+xml,%3Csvg%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%220%200%2040%2040%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-path%3D%22url(%23clip0_5110_14771)%22%3E%3Crect%20width%3D%2240%22%20height%3D%2240%22%20rx%3D%2220%22%20fill%3D%22url(%23paint0_linear_5110_14771)%22%2F%3E%3Cg%20filter%3D%22url(%23filter0_f_5110_14771)%22%3E%3Ccircle%20cx%3D%2212.6071%22%20cy%3D%2234.9794%22%20r%3D%2214.081%22%20fill%3D%22%2332E6B9%22%20fill-opacity%3D%220.4%22%2F%3E%3C%2Fg%3E%3Cg%20filter%3D%22url(%23filter1_f_5110_14771)%22%3E%3Ccircle%20cx%3D%2233.3404%22%20cy%3D%2243.6185%22%20r%3D%2212.8704%22%20fill%3D%22%2332E6B9%22%2F%3E%3C%2Fg%3E%3Cpath%20d%3D%22M27.6897%203.3426C28.057%203.01322%2028.0795%202.99983%2028.3485%202.98366C28.7851%202.95177%2029.1858%203.16107%2029.8664%203.78072C31.4566%205.22574%2033.6703%208.19724%2035.047%2010.7343L35.5796%2011.7191L36.3309%2012.0927C37.0562%2012.4593%2038.2459%2013.2111%2038.7428%2013.614C38.9673%2013.7997%2038.9992%2013.8032%2039.2326%2013.7124C40.2872%2013.3017%2041.7982%2013.846%2043.1307%2015.1244C44.3302%2016.274%2045.479%2018.2384%2045.9191%2019.8778C45.9833%2020.1417%2046.0683%2020.709%2046.0994%2021.1314C46.1997%2022.6144%2045.7243%2023.7986%2044.8085%2024.3349C44.6213%2024.443%2044.6085%2024.4725%2044.6137%2024.939C44.6559%2027.1604%2044.0571%2029.3777%2042.8543%2031.54C41.4965%2033.9678%2039.0794%2036.4798%2035.8073%2038.846C34.0502%2040.1247%2029.8918%2042.5469%2028.0122%2043.3973C23.5096%2045.4244%2019.8999%2046.2022%2016.7645%2045.8181C14.8943%2045.5915%2012.7771%2044.8611%2011.5246%2044.0137C11.1951%2043.786%2011.1428%2043.7719%2010.8909%2043.8439C9.55017%2044.2289%207.79442%2043.4374%206.30272%2041.7817C5.70771%2041.1197%204.74709%2039.4939%204.43575%2038.6241C3.71569%2036.5887%203.85859%2034.752%204.81783%2033.6551C5.0657%2033.3725%205.07387%2033.3607%205.01972%2032.8856C4.93033%2032.1079%204.88982%2030.9566%204.93066%2030.2138L4.96343%2029.5205L3.92091%2027.6777C2.3078%2024.8074%201.28326%2022.3968%200.887982%2020.5554C0.679402%2019.546%200.692781%2019.0981%200.949061%2018.7667C1.10529%2018.5666%201.61622%2018.3594%202.23252%2018.2455C3.78518%2017.9729%207.17203%2018.2198%2010.9394%2018.885L11.3303%2018.9525L12.1903%2018.1921C13.6179%2016.9275%2014.5665%2016.2176%2016.315%2015.1274C18.1373%2013.9872%2020.1943%2013.0498%2022.5103%2012.3071L23.2533%2012.0687L23.6615%2010.9964C25.1239%207.13587%2026.6218%204.28969%2027.6897%203.3426ZM15.4393%2023.1261C13.7864%2024.0804%2012.9602%2024.5582%2012.3529%2025.093C9.89364%2027.2585%208.9738%2030.6888%2010.0208%2033.7938C10.2794%2034.5606%2010.7568%2035.387%2011.7111%2037.0398C12.6653%2038.6926%2013.1424%2039.5194%2013.6771%2040.1267C15.8426%2042.5859%2019.2732%2043.5045%2022.3783%2042.4574C23.145%2042.1989%2023.972%2041.7223%2025.6248%2040.768L35.1333%2035.2783C36.7862%2034.324%2037.6124%2033.8462%2038.2196%2033.3114C40.6789%2031.1459%2041.5988%2027.7156%2040.5518%2024.6105C40.2932%2023.8438%2039.8157%2023.0174%2038.8615%2021.3646C37.9072%2019.7117%2037.4302%2018.885%2036.8955%2018.2777C34.7299%2015.8185%2031.2993%2014.8999%2028.1943%2015.947C27.4275%2016.2055%2026.6006%2016.6821%2024.9478%2017.6364L15.4393%2023.1261Z%22%20fill%3D%22url(%23paint1_linear_5110_14771)%22%2F%3E%3Crect%20x%3D%2215.8816%22%20y%3D%2230.0273%22%20width%3D%224.00904%22%20height%3D%228.32646%22%20rx%3D%222.00452%22%20transform%3D%22rotate(-30%2015.8816%2030.0273)%22%20fill%3D%22white%22%2F%3E%3Crect%20x%3D%2226.6978%22%20y%3D%2223.7812%22%20width%3D%224.00904%22%20height%3D%228.32646%22%20rx%3D%222.00452%22%20transform%3D%22rotate(-30%2026.6978%2023.7812)%22%20fill%3D%22white%22%2F%3E%3C%2Fg%3E%3Cdefs%3E%3Cfilter%20id%3D%22filter0_f_5110_14771%22%20x%3D%22-14.9163%22%20y%3D%227.45599%22%20width%3D%2255.0468%22%20height%3D%2255.047%22%20filterUnits%3D%22userSpaceOnUse%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeFlood%20flood-opacity%3D%220%22%20result%3D%22BackgroundImageFix%22%2F%3E%3CfeBlend%20mode%3D%22normal%22%20in%3D%22SourceGraphic%22%20in2%3D%22BackgroundImageFix%22%20result%3D%22shape%22%2F%3E%3CfeGaussianBlur%20stdDeviation%3D%226.72123%22%20result%3D%22effect1_foregroundBlur_5110_14771%22%2F%3E%3C%2Ffilter%3E%3Cfilter%20id%3D%22filter1_f_5110_14771%22%20x%3D%229.46959%22%20y%3D%2219.7477%22%20width%3D%2247.7417%22%20height%3D%2247.741%22%20filterUnits%3D%22userSpaceOnUse%22%20color-interpolation-filters%3D%22sRGB%22%3E%3CfeFlood%20flood-opacity%3D%220%22%20result%3D%22BackgroundImageFix%22%2F%3E%3CfeBlend%20mode%3D%22normal%22%20in%3D%22SourceGraphic%22%20in2%3D%22BackgroundImageFix%22%20result%3D%22shape%22%2F%3E%3CfeGaussianBlur%20stdDeviation%3D%225.50019%22%20result%3D%22effect1_foregroundBlur_5110_14771%22%2F%3E%3C%2Ffilter%3E%3ClinearGradient%20id%3D%22paint0_linear_5110_14771%22%20x1%3D%2220%22%20y1%3D%220%22%20x2%3D%2220%22%20y2%3D%2240%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%236C4DFF%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23583ED3%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22paint1_linear_5110_14771%22%20x1%3D%2214.639%22%20y1%3D%2210.7917%22%20x2%3D%2232.1712%22%20y2%3D%2241.1585%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22white%22%20stop-opacity%3D%220.8%22%2F%3E%3Cstop%20offset%3D%220.437689%22%20stop-color%3D%22white%22%2F%3E%3C%2FlinearGradient%3E%3CclipPath%20id%3D%22clip0_5110_14771%22%3E%3Crect%20width%3D%2240%22%20height%3D%2240%22%20rx%3D%2220%22%20fill%3D%22white%22%2F%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E';
|
|
6
|
+
const clientAvatars = {
|
|
7
|
+
codex: getProviderAvatar('openai'),
|
|
8
|
+
'claude-code': getProviderAvatar('anthropic'),
|
|
9
|
+
pi: piAvatar,
|
|
10
|
+
codebuddy: codebuddyAvatar,
|
|
11
|
+
};
|
|
12
|
+
export function getClientAvatar(client) {
|
|
13
|
+
return clientAvatars[client];
|
|
14
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { WebExtensionHost } from '@undefineds.co/extension-sdk/web';
|
|
2
|
-
import { type AiConnectionsClient, type AiConnectionsProvider, type AiProviderConnectionSummary, type AiProviderSummary } from './ai-connections-client';
|
|
2
|
+
import { type AiConnectionsClient, type AiConnectionsMode, type AiConnectionsProvider, type AiProviderConnectionSummary, type AiProviderSummary } from './ai-connections-client';
|
|
3
3
|
import type { AiClientConfigurationBridge } from './AiClientConfigurationSection';
|
|
4
4
|
export interface AiProviderDefinition {
|
|
5
5
|
id: AiConnectionsProvider;
|
|
6
6
|
name: string;
|
|
7
|
-
browserMode:
|
|
7
|
+
browserMode: AiConnectionsMode;
|
|
8
8
|
browserLabel: string;
|
|
9
9
|
description: string;
|
|
10
10
|
homeUrl: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useSyncExternalStore } from 'react';
|
|
2
|
-
import { AI_CONNECTIONS_PROVIDERS, createAiConnectionsClient, normalizeAiConnectionsThrownError, } from './ai-connections-client.js';
|
|
2
|
+
import { AI_CONNECTIONS_PROVIDERS, AiConnectionsRequestError, createAiConnectionsClient, normalizeAiConnectionsThrownError, } from './ai-connections-client.js';
|
|
3
3
|
export const PROVIDERS = [
|
|
4
4
|
{ id: 'openai', name: 'OpenAI', browserMode: 'browserAssistedApiKey', browserLabel: '登录', description: 'OpenAI 模型与编码能力', homeUrl: 'https://openai.com', apiKeyUrl: 'https://platform.openai.com/api-keys', apiKeyPlaceholder: 'sk-...', defaultBaseUrl: 'https://api.openai.com/v1' },
|
|
5
5
|
{ id: 'anthropic', name: 'Anthropic', browserMode: 'browserAssistedApiKey', browserLabel: '登录', description: 'Claude 模型与编码能力', homeUrl: 'https://www.anthropic.com', apiKeyUrl: 'https://console.anthropic.com/settings/keys', apiKeyPlaceholder: 'sk-ant-...', defaultBaseUrl: 'https://api.anthropic.com' },
|
|
@@ -23,11 +23,11 @@ export function createAiConnectionsController(host) {
|
|
|
23
23
|
const authenticated = sessionSnapshot.status === 'authenticated'
|
|
24
24
|
&& readyPod !== undefined;
|
|
25
25
|
const client = authenticated
|
|
26
|
-
? createInteractiveAiConnectionsClient(createAiConnectionsClient({
|
|
26
|
+
? createInteractiveAiConnectionsClient(withAccountClientCredentials(createAiConnectionsClient({
|
|
27
27
|
webId: sessionSnapshot.webId,
|
|
28
28
|
podBaseUrl: readyPod.current.podUrl,
|
|
29
29
|
authenticatedFetch: host.solid.session.fetch,
|
|
30
|
-
}), host.capabilities.aiConnectionsPodStore)
|
|
30
|
+
}), host.capabilities.aiClientCredentials), host.capabilities.aiConnectionsPodStore)
|
|
31
31
|
: null;
|
|
32
32
|
let selectedSection = 'keys';
|
|
33
33
|
let selectedProvider = 'openai';
|
|
@@ -102,7 +102,7 @@ export function createAiConnectionsController(host) {
|
|
|
102
102
|
return;
|
|
103
103
|
providerLoadGeneration += 1;
|
|
104
104
|
providerStates = { ...providerStates, [provider]: state };
|
|
105
|
-
const product = durableProviderFromProductState(provider, state);
|
|
105
|
+
const product = providerSummaries[provider] ? undefined : durableProviderFromProductState(provider, state);
|
|
106
106
|
if (product) {
|
|
107
107
|
providerSummaries = {
|
|
108
108
|
...providerSummaries,
|
|
@@ -151,12 +151,96 @@ export function createAiConnectionsController(host) {
|
|
|
151
151
|
};
|
|
152
152
|
return controller;
|
|
153
153
|
}
|
|
154
|
+
function withAccountClientCredentials(client, credentials) {
|
|
155
|
+
return {
|
|
156
|
+
...client,
|
|
157
|
+
async createGatewayKey({ name }) {
|
|
158
|
+
if (!credentials)
|
|
159
|
+
throw new Error('当前账号登录状态不支持创建客户端凭据。');
|
|
160
|
+
const issued = await credentials.create({ name, webId: client.webId });
|
|
161
|
+
try {
|
|
162
|
+
return await client.createGatewayKey({ name, apiKey: issued.apiKey, credentialResource: issued.resource });
|
|
163
|
+
}
|
|
164
|
+
catch (cause) {
|
|
165
|
+
try {
|
|
166
|
+
await credentials.revoke({ ...issued, webId: client.webId });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
throw new Error('API Key 未能保存到 Pod,且账号凭据撤销失败。该凭据尚未应用到客户端。');
|
|
170
|
+
}
|
|
171
|
+
throw cause;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
async deleteGatewayKey(keyId) {
|
|
175
|
+
const record = (await client.listGatewayKeys()).find((key) => key.id === keyId);
|
|
176
|
+
if (record?.kind === 'client-credentials') {
|
|
177
|
+
if (!credentials || !record.credentialResource) {
|
|
178
|
+
throw new Error('当前账号登录状态无法撤销此客户端凭据,Key 记录已保留。');
|
|
179
|
+
}
|
|
180
|
+
const apiKey = await client.revealGatewayKey(keyId);
|
|
181
|
+
await credentials.revoke({ apiKey, resource: record.credentialResource, webId: client.webId });
|
|
182
|
+
}
|
|
183
|
+
await client.deleteGatewayKey(keyId);
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
154
187
|
function createInteractiveAiConnectionsClient(operationsClient, podStore) {
|
|
155
188
|
if (!podStore)
|
|
156
189
|
return operationsClient;
|
|
190
|
+
const refreshTasks = new Map();
|
|
191
|
+
const refreshOAuthCredential = podStore.readCredentialSecret && podStore.updateOAuthCredential
|
|
192
|
+
? (provider, credentialId) => {
|
|
193
|
+
const key = JSON.stringify([provider, credentialId]);
|
|
194
|
+
const pending = refreshTasks.get(key);
|
|
195
|
+
if (pending)
|
|
196
|
+
return pending;
|
|
197
|
+
const task = (async () => {
|
|
198
|
+
const providers = await podStore.listProviders();
|
|
199
|
+
const credential = providers
|
|
200
|
+
.find((item) => item.id === provider)
|
|
201
|
+
?.credentials.find((item) => item.id === credentialId);
|
|
202
|
+
if (!credential)
|
|
203
|
+
throw new Error('oauth_credential_not_found');
|
|
204
|
+
const secret = await podStore.readCredentialSecret(provider, credentialId);
|
|
205
|
+
const refreshToken = typeof secret.refreshToken === 'string' ? secret.refreshToken : undefined;
|
|
206
|
+
if (!refreshToken)
|
|
207
|
+
throw new Error('oauth_refresh_token_required');
|
|
208
|
+
const result = await operationsClient.refreshOAuthCredential(provider, credentialId, refreshToken, credential.version, credential.offeringId, secret.authorizationMethodId === 'browser-oauth' ? 'authorizationCodeOAuth' : undefined);
|
|
209
|
+
if (result.status !== 'completed' || !result.oauthCredential)
|
|
210
|
+
throw new Error('oauth_refresh_failed');
|
|
211
|
+
await podStore.updateOAuthCredential(provider, credentialId, credential.version, result.oauthCredential);
|
|
212
|
+
const { oauthCredential: _discarded, ...publicResult } = result;
|
|
213
|
+
return publicResult;
|
|
214
|
+
})().finally(() => { refreshTasks.delete(key); });
|
|
215
|
+
refreshTasks.set(key, task);
|
|
216
|
+
return task;
|
|
217
|
+
}
|
|
218
|
+
: operationsClient.refreshOAuthCredential;
|
|
219
|
+
const readUsableCredential = async (provider, credential, forceRefresh = false) => {
|
|
220
|
+
let didRefresh = false;
|
|
221
|
+
let secret = await podStore.readCredentialSecret(provider, credential.id);
|
|
222
|
+
const expiresAt = typeof secret.expiresAt === 'string' ? secret.expiresAt : credential.expiresAt;
|
|
223
|
+
if ((credential.authMode === 'oauth' || credential.authMode === 'deviceCode')
|
|
224
|
+
&& (forceRefresh || (expiresAt && Date.parse(expiresAt) <= Date.now() + 60000))) {
|
|
225
|
+
if (!podStore.updateOAuthCredential)
|
|
226
|
+
throw new Error('oauth_refresh_unavailable');
|
|
227
|
+
await refreshOAuthCredential(provider, credential.id, '', credential.version);
|
|
228
|
+
const providers = await podStore.listProviders();
|
|
229
|
+
const current = providers.find((item) => item.id === provider)
|
|
230
|
+
?.credentials.find((item) => item.id === credential.id);
|
|
231
|
+
if (!current)
|
|
232
|
+
throw new Error('oauth_credential_not_found');
|
|
233
|
+
didRefresh = true;
|
|
234
|
+
credential = current;
|
|
235
|
+
secret = await podStore.readCredentialSecret(provider, credential.id);
|
|
236
|
+
}
|
|
237
|
+
return { credential, secret, didRefresh };
|
|
238
|
+
};
|
|
157
239
|
return {
|
|
158
240
|
...operationsClient,
|
|
159
|
-
listProviders: async () => podStore.listProviders(),
|
|
241
|
+
listProviders: async () => mergeAuthorizationMethodsIntoProviders(await podStore.listProviders(), operationsClient.listAuthorizationMethods
|
|
242
|
+
? await operationsClient.listAuthorizationMethods()
|
|
243
|
+
: []),
|
|
160
244
|
listModels: podStore.listModels
|
|
161
245
|
? async () => podStore.listModels()
|
|
162
246
|
: operationsClient.listModels,
|
|
@@ -164,7 +248,8 @@ function createInteractiveAiConnectionsClient(operationsClient, podStore) {
|
|
|
164
248
|
? async (provider, input) => podStore.createApiKeyCredential(provider, input)
|
|
165
249
|
: operationsClient.createApiKeyCredential,
|
|
166
250
|
createLocalCredential: podStore.createLocalCredential
|
|
167
|
-
? async (provider, input) => input.
|
|
251
|
+
? async (provider, input) => input.authorizationMethodId === 'local-session-import'
|
|
252
|
+
|| (!input.authorizationMethodId && input.offeringId === 'official-subscription')
|
|
168
253
|
? operationsClient.createLocalCredential(provider, input)
|
|
169
254
|
: podStore.createLocalCredential(provider, input)
|
|
170
255
|
: operationsClient.createLocalCredential,
|
|
@@ -187,26 +272,7 @@ function createInteractiveAiConnectionsClient(operationsClient, podStore) {
|
|
|
187
272
|
};
|
|
188
273
|
}
|
|
189
274
|
: operationsClient.pollDevice,
|
|
190
|
-
refreshOAuthCredential
|
|
191
|
-
? async (provider, credentialId, _refreshToken, _expectedVersion) => {
|
|
192
|
-
const providers = await podStore.listProviders();
|
|
193
|
-
const credential = providers
|
|
194
|
-
.find((item) => item.id === provider)
|
|
195
|
-
?.credentials.find((item) => item.id === credentialId);
|
|
196
|
-
if (!credential)
|
|
197
|
-
throw new Error('oauth_credential_not_found');
|
|
198
|
-
const secret = await podStore.readCredentialSecret(provider, credentialId);
|
|
199
|
-
const refreshToken = typeof secret.refreshToken === 'string' ? secret.refreshToken : undefined;
|
|
200
|
-
if (!refreshToken)
|
|
201
|
-
throw new Error('oauth_refresh_token_required');
|
|
202
|
-
const result = await operationsClient.refreshOAuthCredential(provider, credentialId, refreshToken, credential.version);
|
|
203
|
-
if (result.status !== 'completed' || !result.oauthCredential)
|
|
204
|
-
return result;
|
|
205
|
-
await podStore.updateOAuthCredential(provider, credentialId, credential.version, result.oauthCredential);
|
|
206
|
-
const { oauthCredential: _discarded, ...publicResult } = result;
|
|
207
|
-
return publicResult;
|
|
208
|
-
}
|
|
209
|
-
: operationsClient.refreshOAuthCredential,
|
|
275
|
+
refreshOAuthCredential,
|
|
210
276
|
quota: podStore.readCredentialSecret
|
|
211
277
|
? async (provider, _refresh = false, input) => {
|
|
212
278
|
const providers = await podStore.listProviders();
|
|
@@ -219,7 +285,7 @@ function createInteractiveAiConnectionsClient(operationsClient, podStore) {
|
|
|
219
285
|
: credentials.find((item) => item.enabled && (!input?.offeringId || item.offeringId === input.offeringId));
|
|
220
286
|
if (!credential)
|
|
221
287
|
throw new Error('quota_credential_not_found');
|
|
222
|
-
const secret = await
|
|
288
|
+
const { secret } = await readUsableCredential(provider, credential);
|
|
223
289
|
const quotaSecret = discoverySecretFromProviderSecret(secret, credential.authMode);
|
|
224
290
|
if (!quotaSecret)
|
|
225
291
|
throw new Error('credential_secret_unavailable');
|
|
@@ -247,12 +313,14 @@ function createInteractiveAiConnectionsClient(operationsClient, podStore) {
|
|
|
247
313
|
testProviderCredential: podStore.readCredentialSecret
|
|
248
314
|
? async (provider, input) => {
|
|
249
315
|
const summaries = await podStore.listProviders();
|
|
250
|
-
|
|
316
|
+
let credential = summaries
|
|
251
317
|
.find((item) => item.id === provider)
|
|
252
318
|
?.credentials.find((item) => item.id === input.credentialId);
|
|
253
319
|
if (!credential)
|
|
254
320
|
throw new Error('test_credential_not_found');
|
|
255
|
-
const
|
|
321
|
+
const usable = await readUsableCredential(provider, credential);
|
|
322
|
+
credential = usable.credential;
|
|
323
|
+
const { secret } = usable;
|
|
256
324
|
const discoverySecret = discoverySecretFromProviderSecret(secret, credential.authMode);
|
|
257
325
|
if (!discoverySecret)
|
|
258
326
|
throw new Error('test_secret_missing');
|
|
@@ -293,43 +361,106 @@ function createInteractiveAiConnectionsClient(operationsClient, podStore) {
|
|
|
293
361
|
&& (!input?.credentialId || item.id === input.credentialId))) ?? [];
|
|
294
362
|
if (credentials.length === 0)
|
|
295
363
|
throw new Error('models_credential_not_found');
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
364
|
+
const scopeFor = (credential) => JSON.stringify([
|
|
365
|
+
provider,
|
|
366
|
+
credential.offeringId,
|
|
367
|
+
(credential.baseUrl ?? '').trim().replace(/\/+$/u, ''),
|
|
368
|
+
input?.compatibility ?? credential.compatibility ?? 'auto',
|
|
369
|
+
// Custom model catalogs are persisted per connection instance.
|
|
370
|
+
provider === 'custom' ? credential.id : undefined,
|
|
371
|
+
]);
|
|
372
|
+
const scopes = new Map();
|
|
373
|
+
for (const credential of [...credentials].sort((left, right) => left.priority - right.priority)) {
|
|
374
|
+
const scope = scopeFor(credential);
|
|
375
|
+
const candidates = scopes.get(scope) ?? [];
|
|
376
|
+
candidates.push(credential);
|
|
377
|
+
scopes.set(scope, candidates);
|
|
378
|
+
}
|
|
379
|
+
const settled = await Promise.allSettled([...scopes.values()].map(async (candidates) => {
|
|
380
|
+
let lastError;
|
|
381
|
+
for (const credential of candidates) {
|
|
382
|
+
try {
|
|
383
|
+
let usable = await readUsableCredential(provider, credential);
|
|
384
|
+
const discover = () => {
|
|
385
|
+
const discoverySecret = discoverySecretFromProviderSecret(usable.secret, credential.authMode);
|
|
386
|
+
if (!discoverySecret)
|
|
387
|
+
throw new Error('models_secret_missing');
|
|
388
|
+
return operationsClient.discoverModels(provider, {
|
|
389
|
+
credentialId: credential.id,
|
|
390
|
+
offeringId: credential.offeringId,
|
|
391
|
+
authMode: credential.authMode === 'deviceCode' || credential.authMode === 'oauth'
|
|
392
|
+
? 'deviceCodeOAuth'
|
|
393
|
+
: credential.authMode === 'local' ? 'local' : 'apiKey',
|
|
394
|
+
secret: discoverySecret,
|
|
395
|
+
baseUrl: credential.baseUrl,
|
|
396
|
+
proxyUrl: credential.proxyUrl,
|
|
397
|
+
compatibility: input?.compatibility ?? credential.compatibility,
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
let result;
|
|
401
|
+
try {
|
|
402
|
+
result = await discover();
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
if (usable.didRefresh || !(error instanceof AiConnectionsRequestError)
|
|
406
|
+
|| error.code !== 'provider_models_fetch_failed' || error.providerStatus !== 401
|
|
407
|
+
|| (credential.authMode !== 'oauth' && credential.authMode !== 'deviceCode'))
|
|
408
|
+
throw error;
|
|
409
|
+
usable = await readUsableCredential(provider, usable.credential, true);
|
|
410
|
+
result = await discover();
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
...result,
|
|
414
|
+
credential: credential.id,
|
|
415
|
+
credentialVersion: usable.credential.version,
|
|
416
|
+
models: result.models.map((model) => ({
|
|
417
|
+
...model,
|
|
418
|
+
offeringId: credential.offeringId,
|
|
419
|
+
...(provider === 'custom' ? { credentialId: credential.id } : {}),
|
|
420
|
+
})),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
lastError = error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
throw lastError;
|
|
320
428
|
}));
|
|
321
429
|
const successful = settled.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
|
|
322
430
|
if (successful.length === 0) {
|
|
323
431
|
throw settled[0].reason;
|
|
324
432
|
}
|
|
433
|
+
for (const result of successful) {
|
|
434
|
+
await podStore.markCredentialHealth?.(provider, result.credential, 'healthy', result.credentialVersion);
|
|
435
|
+
}
|
|
436
|
+
const modelKey = (model) => `${model.offeringId}\0${model.credentialId ?? ''}\0${('resourceId' in model ? model.resourceId : undefined) ?? model.id}`;
|
|
325
437
|
if (podStore.saveDiscoveredModels) {
|
|
326
|
-
|
|
438
|
+
try {
|
|
439
|
+
const failedOfferings = new Set([...scopes.values()].flatMap((candidates, index) => settled[index]?.status === 'rejected' ? [candidates[0].offeringId] : []));
|
|
440
|
+
const catalogs = new Map();
|
|
441
|
+
for (const result of successful) {
|
|
442
|
+
const credential = credentials.find((item) => item.id === result.credential);
|
|
443
|
+
// A partial catalog must not mark models from a failed endpoint missing.
|
|
444
|
+
if (provider !== 'custom' && failedOfferings.has(credential.offeringId))
|
|
445
|
+
continue;
|
|
446
|
+
const key = provider === 'custom' ? credential.id : credential.offeringId;
|
|
447
|
+
const catalog = catalogs.get(key) ?? { credential: credential.id, models: [] };
|
|
448
|
+
catalog.models.push(...result.models);
|
|
449
|
+
catalogs.set(key, catalog);
|
|
450
|
+
}
|
|
451
|
+
// Each offering shares a Pod model document; persist its union once, serially.
|
|
452
|
+
for (const catalog of catalogs.values()) {
|
|
453
|
+
await podStore.saveDiscoveredModels(provider, catalog.credential, [...new Map(catalog.models.map((model) => [modelKey(model), model])).values()]);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
throw new Error('models_persistence_failed');
|
|
458
|
+
}
|
|
327
459
|
}
|
|
328
460
|
const models = [...new Map(successful.flatMap((result) => result.models).map((model) => [
|
|
329
|
-
|
|
330
|
-
model,
|
|
461
|
+
modelKey(model), model,
|
|
331
462
|
])).values()];
|
|
332
|
-
return { ...successful[0], models };
|
|
463
|
+
return { ...successful[0], models, complete: successful.length === scopes.size };
|
|
333
464
|
}
|
|
334
465
|
: operationsClient.discoverModels,
|
|
335
466
|
saveModelSelection: podStore.saveModelSelection
|
|
@@ -427,6 +558,34 @@ function connectModesFromProviderProduct(product) {
|
|
|
427
558
|
function primaryCredential(product) {
|
|
428
559
|
return product.credentials.find((credential) => credential.enabled) ?? product.credentials[0];
|
|
429
560
|
}
|
|
561
|
+
function mergeAuthorizationMethodsIntoProviders(providers, methods) {
|
|
562
|
+
if (methods.length === 0)
|
|
563
|
+
return providers;
|
|
564
|
+
const methodsByOffering = new Map(methods.map((item) => [
|
|
565
|
+
`${item.provider}:${item.offeringId}`,
|
|
566
|
+
item,
|
|
567
|
+
]));
|
|
568
|
+
return providers.map((provider) => ({
|
|
569
|
+
...provider,
|
|
570
|
+
offerings: provider.offerings.map((offering) => {
|
|
571
|
+
const capability = methodsByOffering.get(`${provider.id}:${offering.id}`);
|
|
572
|
+
if (!capability)
|
|
573
|
+
return offering;
|
|
574
|
+
const authorizationMethods = capability.authorizationMethods;
|
|
575
|
+
return {
|
|
576
|
+
...offering,
|
|
577
|
+
...(capability.endpoints ? { endpoints: capability.endpoints } : {}),
|
|
578
|
+
...(authorizationMethods.length ? {
|
|
579
|
+
authorizationMethods,
|
|
580
|
+
authModes: mergeAuthModes(offering.authModes, authorizationMethods.map((method) => method.authMode)),
|
|
581
|
+
} : {}),
|
|
582
|
+
};
|
|
583
|
+
}),
|
|
584
|
+
}));
|
|
585
|
+
}
|
|
586
|
+
function mergeAuthModes(current, next) {
|
|
587
|
+
return Array.from(new Set([...(current ?? []), ...next]));
|
|
588
|
+
}
|
|
430
589
|
function durableProviderFromProductState(provider, state) {
|
|
431
590
|
if (state === 'configured') {
|
|
432
591
|
return {
|
|
@@ -19,4 +19,6 @@ export * from './manifest';
|
|
|
19
19
|
export * from './service-access';
|
|
20
20
|
export declare const aiConnectionApplet: import("@undefineds.co/extension-sdk/web").TwoPaneAppletModule<AiConnectionsController, unknown>;
|
|
21
21
|
export declare const aiConnectionExtension: WebExtensionModule;
|
|
22
|
-
export declare function createAiConnectionsExtension(
|
|
22
|
+
export declare function createAiConnectionsExtension(options?: {
|
|
23
|
+
renderToaster?: boolean;
|
|
24
|
+
}): WebExtensionModule;
|
|
@@ -5,6 +5,7 @@ import { AiConnectionsHeader } from './AiConnectionsHeader.js';
|
|
|
5
5
|
import { AiConnectionsMainHeader } from './AiConnectionsMainHeader.js';
|
|
6
6
|
import { createAiConnectionsController, } from './controller.js';
|
|
7
7
|
import { aiConnectionManifest } from './manifest.js';
|
|
8
|
+
import { createElement } from 'react';
|
|
8
9
|
export * from './ai-connections-client.js';
|
|
9
10
|
export * from './AiClientConfigurationSection.js';
|
|
10
11
|
export * from './AiConnectionsPanel.js';
|
|
@@ -42,6 +43,19 @@ export const aiConnectionExtension = {
|
|
|
42
43
|
[appletManifest.appId]: aiConnectionApplet,
|
|
43
44
|
},
|
|
44
45
|
};
|
|
45
|
-
export function createAiConnectionsExtension() {
|
|
46
|
-
|
|
46
|
+
export function createAiConnectionsExtension(options = {}) {
|
|
47
|
+
if (options.renderToaster !== false)
|
|
48
|
+
return aiConnectionExtension;
|
|
49
|
+
return {
|
|
50
|
+
...aiConnectionExtension,
|
|
51
|
+
applets: {
|
|
52
|
+
[appletManifest.appId]: defineApplet({
|
|
53
|
+
...aiConnectionApplet,
|
|
54
|
+
slots: {
|
|
55
|
+
...aiConnectionApplet.slots,
|
|
56
|
+
main: ({ controller }) => createElement(AiConnectionsMain, { controller, renderToaster: false }),
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
47
61
|
}
|