@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.
Files changed (223) hide show
  1. package/config/cloud.json +1 -1
  2. package/config/main.json +36 -2
  3. package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +3 -0
  4. package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +1 -1
  5. package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +2 -1
  6. package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +101 -32
  7. package/dist/api/ai-gateway/connect/DeviceCodeProtocol.d.ts +80 -0
  8. package/dist/api/ai-gateway/connect/DeviceCodeProtocol.js +3 -0
  9. package/dist/api/ai-gateway/connect/FileSessionImportAdapter.d.ts +38 -0
  10. package/dist/api/ai-gateway/connect/FileSessionImportAdapter.js +63 -0
  11. package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.d.ts +24 -0
  12. package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.js +136 -0
  13. package/dist/api/ai-gateway/connect/OAuthConnectAdapter.d.ts +2 -0
  14. package/dist/api/ai-gateway/connect/OAuthConnectAdapter.js +2 -1
  15. package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.d.ts +17 -13
  16. package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.js +52 -19
  17. package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.d.ts +4 -28
  18. package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.js +7 -58
  19. package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.d.ts +4 -0
  20. package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.js +116 -0
  21. package/dist/api/ai-gateway/connect/SessionImportProfiles.d.ts +10 -0
  22. package/dist/api/ai-gateway/connect/SessionImportProfiles.js +112 -0
  23. package/dist/api/ai-gateway/connect/index.d.ts +125 -23
  24. package/dist/api/ai-gateway/connect/index.js +1277 -260
  25. package/dist/api/ai-gateway/models/PodModelSelectionRepository.d.ts +4 -1
  26. package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +154 -19
  27. package/dist/api/ai-gateway/providers/DeepSeekRuntimeAdapter.js +10 -6
  28. package/dist/api/ai-gateway/providers/ModelsDevCatalog.d.ts +7 -0
  29. package/dist/api/ai-gateway/providers/ModelsDevCatalog.js +25 -1
  30. package/dist/api/ai-gateway/providers/OfferingAuthorization.d.ts +24 -0
  31. package/dist/api/ai-gateway/providers/OfferingAuthorization.js +37 -0
  32. package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.d.ts +1 -1
  33. package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.js +5 -3
  34. package/dist/api/ai-gateway/providers/ProviderRegistry.d.ts +6 -3
  35. package/dist/api/ai-gateway/providers/ProviderRegistry.js +55 -16
  36. package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.d.ts +2 -0
  37. package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.js +37 -4
  38. package/dist/api/ai-gateway/providers/ProviderRuntimeRegistry.js +7 -0
  39. package/dist/api/ai-gateway/quota/SubscriptionQuotaAdapters.js +2 -1
  40. package/dist/api/ai-gateway/routing/ModelRouter.js +3 -1
  41. package/dist/api/container/common.js +30 -40
  42. package/dist/api/container/index.js +6 -2
  43. package/dist/api/container/routes.js +5 -22
  44. package/dist/api/handlers/AiClientConfigurationHandler.js +39 -1
  45. package/dist/api/handlers/AiGatewayManagementHandler.d.ts +3 -0
  46. package/dist/api/handlers/AiGatewayManagementHandler.js +173 -58
  47. package/dist/api/service/AiClientConfigurationService.d.ts +4 -15
  48. package/dist/api/service/AiClientConfigurationService.js +17 -64
  49. package/dist/cli/commands/start.js +2 -0
  50. package/dist/components/components.jsonld +1 -0
  51. package/dist/components/context.jsonld +80 -0
  52. package/dist/http/InternalPodDataHttpHandler.d.ts +3 -0
  53. package/dist/http/InternalPodDataHttpHandler.js +13 -2
  54. package/dist/http/InternalPodDataHttpHandler.jsonld +12 -0
  55. package/dist/identity/oidc/RememberedClientPromptFactory.d.ts +1 -1
  56. package/dist/identity/oidc/RememberedClientPromptFactory.js +5 -2
  57. package/dist/identity/oidc/RememberedConsentHandler.js +1 -1
  58. package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +1 -0
  59. package/dist/identity/oidc/ScopedPickWebIdHandler.js +26 -0
  60. package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +4 -0
  61. package/dist/identity/oidc/SessionBoundIdentityProviderFactory.d.ts +10 -0
  62. package/dist/identity/oidc/SessionBoundIdentityProviderFactory.js +21 -0
  63. package/dist/identity/oidc/SessionBoundIdentityProviderFactory.jsonld +172 -0
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.js +4 -2
  66. package/dist/runtime/component-parameter-keys.js +18 -2
  67. package/dist/storage/accessors/MixDataAccessor.js +6 -2
  68. package/dist/storage/rdf/PostgresRdfEngine.js +10 -2
  69. package/dist/storage/rdf/RdfContentTypes.js +4 -0
  70. package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.d.ts +3 -0
  71. package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.js +13 -16
  72. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsList.js +1 -3
  73. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.d.ts +2 -1
  74. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +2 -2
  75. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +4 -1
  76. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +127 -57
  77. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +6 -4
  78. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +436 -83
  79. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.d.ts +3 -2
  80. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +212 -76
  81. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +6 -4
  82. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +58 -41
  83. package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.d.ts +4 -1
  84. package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.js +63 -4
  85. package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.d.ts +46 -6
  86. package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.js +143 -14
  87. package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.d.ts +19 -3
  88. package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.js +45 -7
  89. package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.d.ts +7 -1
  90. package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.js +305 -40
  91. package/node_modules/@undefineds.co/ai-connections/dist/client-config/index.cjs +383 -50
  92. package/node_modules/@undefineds.co/ai-connections/dist/client-config/json-env-adapter.js +3 -2
  93. package/node_modules/@undefineds.co/ai-connections/dist/client-config/pi.js +12 -3
  94. package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.d.ts +2 -0
  95. package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.js +39 -4
  96. package/node_modules/@undefineds.co/ai-connections/dist/client-config/types.d.ts +8 -1
  97. package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.d.ts +2 -0
  98. package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.js +14 -0
  99. package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +2 -2
  100. package/node_modules/@undefineds.co/ai-connections/dist/controller.js +216 -57
  101. package/node_modules/@undefineds.co/ai-connections/dist/index.d.ts +3 -1
  102. package/node_modules/@undefineds.co/ai-connections/dist/index.js +16 -2
  103. package/node_modules/@undefineds.co/ai-connections/dist/provider-visuals.js +6 -1
  104. package/node_modules/@undefineds.co/drizzle-solid/dist/core/execution/ldp-executor.js +27 -6
  105. package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.js +1 -1
  106. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js +2 -2
  107. package/node_modules/@undefineds.co/drizzle-solid/dist/core/sparql/builder/update-builder.js +12 -1
  108. package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/builder.js +1 -1
  109. package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/array.js +8 -0
  110. package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/default.js +9 -1
  111. package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/inline.js +3 -3
  112. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/execution/ldp-executor.js +27 -6
  113. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.js +1 -1
  114. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js +2 -2
  115. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/sparql/builder/update-builder.js +12 -1
  116. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/builder.js +1 -1
  117. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/array.js +8 -0
  118. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/default.js +9 -1
  119. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/inline.js +3 -3
  120. package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +31 -0
  121. package/package.json +2 -2
  122. package/static/app/assets/{index-BikOyGPu.js → index-BCZVzMpj.js} +44 -44
  123. package/static/app/assets/inrupt-smoke.js +2 -2
  124. package/static/app/assets/main.css +1 -1
  125. package/static/app/assets/main.js +10 -10
  126. package/static/auth-callback/assets/{AiConfigContext-DrCH6ckh.js → AiConfigContext-DVkh8Kg2.js} +1 -1
  127. package/static/auth-callback/assets/{AiConfigPage-DVrlSSdA.js → AiConfigPage-HWVWpe-P.js} +1 -1
  128. package/static/auth-callback/assets/{DocumentProcessingPanel-_Md-v2R2.js → DocumentProcessingPanel-DDq-mH17.js} +1 -1
  129. package/static/auth-callback/assets/{IndexLifecyclePanel-BbWwe23H.js → IndexLifecyclePanel-Cx4cjrdP.js} +1 -1
  130. package/static/auth-callback/assets/{IndexSubjectPanel-upH9MjU-.js → IndexSubjectPanel-DzSw8M9j.js} +1 -1
  131. package/static/auth-callback/assets/{ModelAssignmentsPanel-CVGH4t4g.js → ModelAssignmentsPanel-BsRxGc2p.js} +1 -1
  132. package/static/auth-callback/assets/ModelsPage-DEe1lo6Q.js +10 -0
  133. package/static/auth-callback/assets/{NetworkPage-Dcbrb1fd.js → NetworkPage-DBiEh1Oz.js} +1 -1
  134. package/static/auth-callback/assets/{PaneListHeader-DrY-1wBq.js → PaneListHeader-lEEgjyN9.js} +1 -1
  135. package/static/auth-callback/assets/{SearchIndexingPanel-Dw25y53v.js → SearchIndexingPanel-CZCUUGt6.js} +1 -1
  136. package/static/auth-callback/assets/{StatusSubjectPanel-BHlz9rNF.js → StatusSubjectPanel-21qdueMr.js} +1 -1
  137. package/static/auth-callback/assets/{StatusWorkspace-BH7exQP5.js → StatusWorkspace-R8RKsqVz.js} +1 -1
  138. package/static/auth-callback/assets/{SystemSettingsPage-BYLKnM9f.js → SystemSettingsPage-BQ4e-4Wy.js} +1 -1
  139. package/static/auth-callback/assets/{SystemSettingsSubjectPanel-DT34LKku.js → SystemSettingsSubjectPanel-BopXD7SE.js} +1 -1
  140. package/static/auth-callback/assets/{activity-DdULRcSI.js → activity-DSfmTGfY.js} +1 -1
  141. package/static/auth-callback/assets/ai-connections-C03UWyPh.js +1 -0
  142. package/static/auth-callback/assets/{auth-callback-D7l2P25I.js → auth-callback-DleaitAS.js} +91 -91
  143. package/static/auth-callback/assets/auth-callback-vPSC2d2F.css +1 -0
  144. package/static/auth-callback/assets/{badge-j4zjFnmg.js → badge-B0aLu1sl.js} +1 -1
  145. package/static/auth-callback/assets/{download-DGXoks01.js → download-D_bFwdYJ.js} +1 -1
  146. package/static/auth-callback/assets/{external-link-CvbgXXaI.js → external-link-DRfjTaQu.js} +1 -1
  147. package/static/auth-callback/assets/{index-LRWvKJ2i.js → index-CG23cLYQ.js} +1 -1
  148. package/static/auth-callback/assets/{index-BOK3j1dz.js → index-CyV9ejwx.js} +1 -1
  149. package/static/auth-callback/assets/{index-browser-C1Qpe0g4.js → index-browser-D70paxIg.js} +1 -1
  150. package/static/auth-callback/assets/{key-round-CmgS5UdQ.js → key-round-BjV_mVYH.js} +1 -1
  151. package/static/auth-callback/assets/{rotate-ccw-c4CSG6Rq.js → rotate-ccw-CKoOAfse.js} +1 -1
  152. package/static/auth-callback/assets/{skeleton-ZPMTiRSM.js → skeleton-BZNjZaHx.js} +1 -1
  153. package/static/auth-callback/assets/{waypoints-xjyBflU5.js → waypoints-DoaJVzZv.js} +1 -1
  154. package/static/auth-callback/assets/{workspace-layout-B9nj6F5J.js → workspace-layout-CaP46_ZR.js} +1 -1
  155. package/static/auth-callback/auth-callback.html +2 -2
  156. package/static/dashboard/assets/{AiConfigContext-BWOOSYNr.js → AiConfigContext-BSrR27yD.js} +1 -1
  157. package/static/dashboard/assets/{AiConfigPage-CG2kQ9Wy.js → AiConfigPage-D65Z2LET.js} +1 -1
  158. package/static/dashboard/assets/{DocumentProcessingPanel-B4VXajfj.js → DocumentProcessingPanel-DyTe7Nei.js} +1 -1
  159. package/static/dashboard/assets/{IndexLifecyclePanel-CWCy0CC8.js → IndexLifecyclePanel-fK8ZGlrL.js} +1 -1
  160. package/static/dashboard/assets/{IndexSubjectPanel-CSgyQnG_.js → IndexSubjectPanel-CZPGor7v.js} +1 -1
  161. package/static/dashboard/assets/{ModelAssignmentsPanel-CdGIGidT.js → ModelAssignmentsPanel-CfYn5LMQ.js} +1 -1
  162. package/static/dashboard/assets/ModelsPage-djlJsghe.js +25 -0
  163. package/static/dashboard/assets/{NetworkPage-COia9NCH.js → NetworkPage-Coj6GnPl.js} +1 -1
  164. package/static/dashboard/assets/{PaneListHeader-SZWyr602.js → PaneListHeader-CUuFNSsZ.js} +1 -1
  165. package/static/dashboard/assets/{SearchIndexingPanel-D6PZtG6k.js → SearchIndexingPanel-CyJLioXS.js} +1 -1
  166. package/static/dashboard/assets/{StatusSubjectPanel-CxRD4kv0.js → StatusSubjectPanel-Cv26GSVC.js} +1 -1
  167. package/static/dashboard/assets/{StatusWorkspace-D3PfmY9-.js → StatusWorkspace-CDjFrsmB.js} +1 -1
  168. package/static/dashboard/assets/{SystemSettingsPage-COg1MmMj.js → SystemSettingsPage-BuNOyNoa.js} +1 -1
  169. package/static/dashboard/assets/{SystemSettingsSubjectPanel-BSVVB9Ol.js → SystemSettingsSubjectPanel-B2dasdez.js} +1 -1
  170. package/static/dashboard/assets/{activity-BJJx5cN-.js → activity-D1d2j_4Z.js} +1 -1
  171. package/static/dashboard/assets/ai-connections-C03UWyPh.js +1 -0
  172. package/static/dashboard/assets/{badge-elmrFSCj.js → badge-BeAAv6sp.js} +1 -1
  173. package/static/dashboard/assets/{dashboard-IvQrNmru.js → dashboard-BnGrUGv8.js} +68 -68
  174. package/static/dashboard/assets/dashboard-vPSC2d2F.css +1 -0
  175. package/static/dashboard/assets/{download-C3FH2KzG.js → download-DfFGiZRn.js} +1 -1
  176. package/static/dashboard/assets/{external-link-Bx7F0uKb.js → external-link-IsjU_LC9.js} +1 -1
  177. package/static/dashboard/assets/{index-liRoRIP1.js → index-BWQTucPe.js} +1 -1
  178. package/static/dashboard/assets/{index-M4-kWiGz.js → index-Dj75OjnW.js} +1 -1
  179. package/static/dashboard/assets/{key-round-CXg0bKAj.js → key-round-BJ_9wmQ_.js} +1 -1
  180. package/static/dashboard/assets/{rotate-ccw-yxJKOBrX.js → rotate-ccw-D3JGiFbR.js} +1 -1
  181. package/static/dashboard/assets/{skeleton-x9jNGrQT.js → skeleton-CC4nc-z-.js} +1 -1
  182. package/static/dashboard/assets/{waypoints-CTKeRsGp.js → waypoints-h7sqFqiG.js} +1 -1
  183. package/static/dashboard/assets/{workspace-layout--YkgkS38.js → workspace-layout-ZAPTOHhs.js} +1 -1
  184. package/static/dashboard/dashboard.html +2 -2
  185. package/static/settings/assets/{AiConfigContext-CelxXFcQ.js → AiConfigContext-lGkWpvKP.js} +1 -1
  186. package/static/settings/assets/{AiConfigPage-BaP-EovM.js → AiConfigPage-BbZWIBZq.js} +1 -1
  187. package/static/settings/assets/{DocumentProcessingPanel-DzLZluFv.js → DocumentProcessingPanel-DvYdjT-V.js} +1 -1
  188. package/static/settings/assets/{IndexLifecyclePanel-Bzj1-_oI.js → IndexLifecyclePanel-W5OGQZj7.js} +1 -1
  189. package/static/settings/assets/{IndexSubjectPanel-B263QLUh.js → IndexSubjectPanel-g_Aa5soq.js} +1 -1
  190. package/static/settings/assets/{ModelAssignmentsPanel-BAdT4UR0.js → ModelAssignmentsPanel-COlMBKXj.js} +1 -1
  191. package/static/settings/assets/ModelsPage-mApKi-aw.js +10 -0
  192. package/static/settings/assets/{NetworkPage-BK6a7l4g.js → NetworkPage-BDT05JUq.js} +1 -1
  193. package/static/settings/assets/{PaneListHeader-BAS2ICPq.js → PaneListHeader-CByhNpMG.js} +1 -1
  194. package/static/settings/assets/{SearchIndexingPanel-Cb_p-NgW.js → SearchIndexingPanel-BFAyzXls.js} +1 -1
  195. package/static/settings/assets/{StatusSubjectPanel-CcjqUua6.js → StatusSubjectPanel-Bz_XZ3VU.js} +1 -1
  196. package/static/settings/assets/{StatusWorkspace-7Ibty3_k.js → StatusWorkspace-CeVypHcW.js} +1 -1
  197. package/static/settings/assets/{SystemSettingsPage-DPSz4dg4.js → SystemSettingsPage-Dh7cQIfx.js} +1 -1
  198. package/static/settings/assets/{SystemSettingsSubjectPanel-Cb4HkKT5.js → SystemSettingsSubjectPanel-CSnpciO0.js} +1 -1
  199. package/static/settings/assets/{activity-Qhsr5Pxu.js → activity-DXz3EfFN.js} +1 -1
  200. package/static/settings/assets/ai-connections-C03UWyPh.js +1 -0
  201. package/static/settings/assets/{badge-B1vf2qCl.js → badge-1ZA144i4.js} +1 -1
  202. package/static/settings/assets/{download-CYNt0aZ-.js → download-CUHimlYa.js} +1 -1
  203. package/static/settings/assets/{external-link-ClKb39NT.js → external-link-BYHSVj2Z.js} +1 -1
  204. package/static/settings/assets/{index-y8ew3MLM.js → index-DKPDMdW5.js} +1 -1
  205. package/static/settings/assets/{index-browser-DJhvgpNI.js → index-browser-BaEFz0e0.js} +1 -1
  206. package/static/settings/assets/{index-BgiIDSch.js → index-viSgSvbO.js} +1 -1
  207. package/static/settings/assets/{key-round-5d-DrJz-.js → key-round-COByPOg8.js} +1 -1
  208. package/static/settings/assets/{rotate-ccw-CHZY7kS5.js → rotate-ccw-BC2EN6cE.js} +1 -1
  209. package/static/settings/assets/{settings-D5VRWyx6.js → settings-DLH9XUoT.js} +86 -86
  210. package/static/settings/assets/settings-vPSC2d2F.css +1 -0
  211. package/static/settings/assets/{skeleton-FNabNxNE.js → skeleton-B0-FOhKL.js} +1 -1
  212. package/static/settings/assets/{waypoints-BnL_8ljo.js → waypoints-DGMsvqWo.js} +1 -1
  213. package/static/settings/assets/{workspace-layout-D5q3F59S.js → workspace-layout-B0SVrJIu.js} +1 -1
  214. package/static/settings/settings.html +2 -2
  215. package/static/auth-callback/assets/ModelsPage-BpkVFKbf.js +0 -10
  216. package/static/auth-callback/assets/ai-connections-Bm52UO7Y.js +0 -1
  217. package/static/auth-callback/assets/auth-callback-01yz-UDU.css +0 -1
  218. package/static/dashboard/assets/ModelsPage-Chd5UK0D.js +0 -25
  219. package/static/dashboard/assets/ai-connections-Bm52UO7Y.js +0 -1
  220. package/static/dashboard/assets/dashboard-01yz-UDU.css +0 -1
  221. package/static/settings/assets/ModelsPage-C0NFa1zq.js +0 -10
  222. package/static/settings/assets/ai-connections-Bm52UO7Y.js +0 -1
  223. 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
- settings.defaultModel = model;
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
+ }
@@ -11,7 +11,9 @@ export declare class AiClientConfigTransaction {
11
11
  private snapshot;
12
12
  private stage;
13
13
  private writeNewFile;
14
+ private assertExpectedContent;
14
15
  private rollback;
16
+ private stillContainsWrite;
15
17
  private syncDirectory;
16
18
  private exists;
17
19
  }
@@ -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
- async rollback(snapshots) {
113
- for (const snapshot of [...snapshots].reverse()) {
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
- const tempPath = await this.stage(snapshot.path, snapshot.content.toString('utf8'));
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: readonly AiClientModelReference[];
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,2 @@
1
+ import type { AiConnectionsClientId } from './AiClientConfigurationSection';
2
+ export declare function getClientAvatar(client: AiConnectionsClientId): string | undefined;
@@ -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: 'browserAssistedApiKey' | 'deviceCodeOAuth' | 'connectUnsupported';
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.offeringId === 'official-subscription'
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: podStore.readCredentialSecret && podStore.updateOAuthCredential
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 podStore.readCredentialSecret(provider, credential.id);
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
- const credential = summaries
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 secret = await podStore.readCredentialSecret(provider, credential.id);
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 settled = await Promise.allSettled(credentials.map(async (credential) => {
297
- const secret = await podStore.readCredentialSecret(provider, credential.id);
298
- const discoverySecret = discoverySecretFromProviderSecret(secret, credential.authMode);
299
- if (!discoverySecret)
300
- throw new Error('models_secret_missing');
301
- const result = await operationsClient.discoverModels(provider, {
302
- credentialId: credential.id,
303
- offeringId: credential.offeringId,
304
- authMode: credential.authMode === 'deviceCode' || credential.authMode === 'oauth'
305
- ? 'deviceCodeOAuth'
306
- : credential.authMode === 'local' ? 'local' : 'apiKey',
307
- secret: discoverySecret,
308
- baseUrl: credential.baseUrl,
309
- proxyUrl: credential.proxyUrl,
310
- compatibility: input?.compatibility ?? credential.compatibility,
311
- });
312
- return {
313
- ...result,
314
- models: result.models.map((model) => ({
315
- ...model,
316
- offeringId: credential.offeringId,
317
- ...(provider === 'custom' ? { credentialId: credential.id } : {}),
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
- await Promise.all(successful.map((result) => podStore.saveDiscoveredModels(provider, result.credential, result.models)));
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
- `${('offeringId' in model ? model.offeringId : undefined) ?? ''}\0${('resourceId' in model ? model.resourceId : undefined) ?? model.id}`,
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(): WebExtensionModule;
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
- return aiConnectionExtension;
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
  }