@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
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProviderConnectService = exports.DeepSeekConnectAdapter = exports.KimiDeviceCodeConnectAdapter = exports.BrowserAssistedApiKeyConnectAdapter = exports.InMemoryConnectAttemptStore = exports.PodConnectedCredentialRepository = exports.OpenAiSubscriptionSessionImportAdapter = exports.requireKimiOAuthClientId = exports.OAuthIntegrationRegistry = exports.OAuthConnectCredentialStore = void 0;
3
+ exports.ProviderConnectService = exports.DeepSeekConnectAdapter = exports.AuthorizationCodeConnectAdapter = exports.DeviceCodeConnectAdapter = exports.BrowserAssistedApiKeyConnectAdapter = exports.InMemoryConnectAttemptStore = exports.PodConnectedCredentialRepository = exports.LoopbackAuthorizationCallbackReceiver = exports.OpenAiSubscriptionSessionImportAdapter = exports.requireTrustedOAuthIntegration = exports.OAuthIntegrationRegistry = exports.OAuthConnectCredentialStore = void 0;
4
4
  exports.customModelsFromMetadata = customModelsFromMetadata;
5
5
  const node_crypto_1 = require("node:crypto");
6
6
  const drizzle_solid_1 = require("@undefineds.co/drizzle-solid");
@@ -10,17 +10,19 @@ const ProviderRegistry_1 = require("../providers/ProviderRegistry");
10
10
  const CallerPodAccess_1 = require("../auth/CallerPodAccess");
11
11
  const PodBaseUrlResolver_1 = require("../pod/PodBaseUrlResolver");
12
12
  const OAuthConnectAdapter_1 = require("./OAuthConnectAdapter");
13
- const OAuthIntegrationRegistry_1 = require("./OAuthIntegrationRegistry");
14
13
  const provider_http_transport_1 = require("../../service/provider-http-transport");
15
14
  const n3_1 = require("n3");
16
15
  var OAuthConnectAdapter_2 = require("./OAuthConnectAdapter");
17
16
  Object.defineProperty(exports, "OAuthConnectCredentialStore", { enumerable: true, get: function () { return OAuthConnectAdapter_2.OAuthConnectCredentialStore; } });
18
- var OAuthIntegrationRegistry_2 = require("./OAuthIntegrationRegistry");
19
- Object.defineProperty(exports, "OAuthIntegrationRegistry", { enumerable: true, get: function () { return OAuthIntegrationRegistry_2.OAuthIntegrationRegistry; } });
20
- Object.defineProperty(exports, "requireKimiOAuthClientId", { enumerable: true, get: function () { return OAuthIntegrationRegistry_2.requireKimiOAuthClientId; } });
17
+ var OAuthIntegrationRegistry_1 = require("./OAuthIntegrationRegistry");
18
+ Object.defineProperty(exports, "OAuthIntegrationRegistry", { enumerable: true, get: function () { return OAuthIntegrationRegistry_1.OAuthIntegrationRegistry; } });
19
+ Object.defineProperty(exports, "requireTrustedOAuthIntegration", { enumerable: true, get: function () { return OAuthIntegrationRegistry_1.requireTrustedOAuthIntegration; } });
21
20
  var OpenAiSubscriptionSessionImportAdapter_1 = require("./OpenAiSubscriptionSessionImportAdapter");
22
21
  Object.defineProperty(exports, "OpenAiSubscriptionSessionImportAdapter", { enumerable: true, get: function () { return OpenAiSubscriptionSessionImportAdapter_1.OpenAiSubscriptionSessionImportAdapter; } });
22
+ var LoopbackAuthorizationCallbackReceiver_1 = require("./LoopbackAuthorizationCallbackReceiver");
23
+ Object.defineProperty(exports, "LoopbackAuthorizationCallbackReceiver", { enumerable: true, get: function () { return LoopbackAuthorizationCallbackReceiver_1.LoopbackAuthorizationCallbackReceiver; } });
23
24
  const CREDENTIAL_COLLECTION_QUERY_UNSUPPORTED = 'credential_collection_query_unsupported';
25
+ const credentialUpdateLocks = new Map();
24
26
  class PodConnectedCredentialRepository {
25
27
  constructor(options = {}) {
26
28
  this.internalPodAccess = options.internalPodAccess;
@@ -164,6 +166,7 @@ class PodConnectedCredentialRepository {
164
166
  });
165
167
  const expectedVersionString = String(expectedVersion ?? currentVersion);
166
168
  const updated = await updateByCredentialIdAndVersion({
169
+ owner: input.webId,
167
170
  db,
168
171
  credential,
169
172
  credentialId: input.credentialId,
@@ -250,17 +253,20 @@ class PodConnectedCredentialRepository {
250
253
  if (input.expectedVersion === undefined || currentVersion !== input.expectedVersion) {
251
254
  return false;
252
255
  }
253
- const updated = await db.update(credential)
254
- .set({
255
- encryptedSecret: JSON.stringify(input.encryptedSecret),
256
- wrappedDataKey: input.encryptedSecret.wrappedDek,
257
- encryptionAlgorithm: input.encryptedSecret.algorithm,
258
- keyVersion: String(currentVersion + 1),
259
- })
260
- .where((0, drizzle_solid_1.and)((0, drizzle_solid_1.eq)(credential.id, input.credentialId), (0, drizzle_solid_1.eq)(credential.keyVersion, String(input.expectedVersion))))
261
- .returning()
262
- .execute();
263
- return updated.length === 1;
256
+ const updated = await updateByCredentialIdAndVersion({
257
+ owner: input.webId,
258
+ db,
259
+ credential,
260
+ credentialId: input.credentialId,
261
+ expectedVersion: String(input.expectedVersion),
262
+ patch: {
263
+ encryptedSecret: JSON.stringify(input.encryptedSecret),
264
+ wrappedDataKey: input.encryptedSecret.wrappedDek,
265
+ encryptionAlgorithm: input.encryptedSecret.algorithm,
266
+ keyVersion: String(currentVersion + 1),
267
+ },
268
+ });
269
+ return updated !== null;
264
270
  }
265
271
  async markReauthRequired(input) {
266
272
  const current = await this.getActiveCredential(input);
@@ -449,7 +455,7 @@ class InMemoryConnectAttemptStore {
449
455
  }
450
456
  return attempt ? cloneAttempt(attempt) : undefined;
451
457
  }
452
- async consume(id, now) {
458
+ async consume(id, now, terminalStatus) {
453
459
  this.pruneExpired(now, id);
454
460
  const attempt = this.attempts.get(id);
455
461
  if (!attempt) {
@@ -463,6 +469,7 @@ class InMemoryConnectAttemptStore {
463
469
  throw new Error('Connect attempt expired');
464
470
  }
465
471
  attempt.consumedAt = new Date(now);
472
+ attempt.terminalStatus = terminalStatus;
466
473
  return cloneAttempt(attempt);
467
474
  }
468
475
  async claimPoll(id, now) {
@@ -523,10 +530,9 @@ class InMemoryConnectAttemptStore {
523
530
  }
524
531
  }
525
532
  exports.InMemoryConnectAttemptStore = InMemoryConnectAttemptStore;
526
- class BrowserAssistedApiKeyConnectAdapter {
533
+ class SignedConnectAttemptAdapterBase {
527
534
  constructor(options) {
528
535
  this.provider = normalizeProvider(options.provider);
529
- this.consoleUrl = options.consoleUrl;
530
536
  this.attempts = options.attempts;
531
537
  this.credentialRepository = options.credentialRepository;
532
538
  this.vault = options.vault;
@@ -535,6 +541,91 @@ class BrowserAssistedApiKeyConnectAdapter {
535
541
  this.randomBytes = options.randomBytes ?? node_crypto_1.randomBytes;
536
542
  this.signingSecret = options.signingSecret;
537
543
  }
544
+ async createAttempt(input, expiresAt, extra = {}) {
545
+ const attemptWithoutSignature = {
546
+ id: token(this.randomBytes),
547
+ provider: this.provider,
548
+ deployment: input.deployment,
549
+ webId: input.webId,
550
+ mode: input.requestedMode,
551
+ offeringId: extra.offeringId ?? input.offeringId,
552
+ authorizationMethodId: extra.authorizationMethodId ?? input.authorizationMethodId,
553
+ state: token(this.randomBytes),
554
+ expiresAt,
555
+ expectedCredentialVersion: input.expectedCredentialVersion,
556
+ ...extra,
557
+ };
558
+ const signature = signAttempt(attemptWithoutSignature, this.signingSecret);
559
+ return this.attempts.create({ ...attemptWithoutSignature, signature });
560
+ }
561
+ async loadAttemptForStatus(input, mode) {
562
+ const attempt = await this.attempts.get(input.attemptId, this.now());
563
+ if (!attempt) {
564
+ throw new Error('Connect attempt not found');
565
+ }
566
+ if (attempt.webId !== input.webId) {
567
+ throw new Error('Connect attempt is bound to a different WebID');
568
+ }
569
+ if (attempt.deployment !== input.deployment) {
570
+ throw new Error('Connect attempt is bound to a different deployment');
571
+ }
572
+ if (attempt.provider !== normalizeProvider(input.provider)) {
573
+ throw new Error('Connect attempt is bound to a different provider');
574
+ }
575
+ if (attempt.mode !== mode) {
576
+ throw new Error('Connect attempt mode mismatch');
577
+ }
578
+ if (input.offeringId && attempt.offeringId !== input.offeringId) {
579
+ throw new Error('Connect attempt is bound to a different offering');
580
+ }
581
+ if (attempt.state !== input.state) {
582
+ throw new Error('Invalid Connect attempt state');
583
+ }
584
+ if (!signatureMatches(input.signature, signAttempt(attempt, this.signingSecret))) {
585
+ throw new Error('Invalid Connect attempt signature');
586
+ }
587
+ return attempt;
588
+ }
589
+ async loadConsumableAttempt(input, mode) {
590
+ const attempt = await this.loadAttemptForStatus(input, mode);
591
+ if (attempt.expiresAt.getTime() <= this.now().getTime()) {
592
+ throw new Error('Connect attempt expired');
593
+ }
594
+ if (attempt.consumedAt) {
595
+ throw new Error('Connect attempt already consumed');
596
+ }
597
+ return attempt;
598
+ }
599
+ statusForAttempt(attempt) {
600
+ if (attempt.expiresAt.getTime() <= this.now().getTime()) {
601
+ return 'expired';
602
+ }
603
+ if (attempt.terminalStatus) {
604
+ return attempt.terminalStatus;
605
+ }
606
+ return attempt.consumedAt ? 'completed' : 'pending';
607
+ }
608
+ assertInput(input, mode) {
609
+ if (normalizeProvider(input.provider) !== this.provider) {
610
+ throw new Error('Connect provider mismatch');
611
+ }
612
+ if (input.deployment !== this.deployment) {
613
+ throw new Error('Connect deployment mismatch');
614
+ }
615
+ if (input.requestedMode !== mode) {
616
+ throw new Error('Unsupported Connect mode');
617
+ }
618
+ if (!input.webId) {
619
+ throw new Error('Connect WebID is required');
620
+ }
621
+ }
622
+ }
623
+ class BrowserAssistedApiKeyConnectAdapter extends SignedConnectAttemptAdapterBase {
624
+ constructor(options) {
625
+ super(options);
626
+ this.mode = 'browserAssistedApiKey';
627
+ this.consoleUrl = options.consoleUrl;
628
+ }
538
629
  async begin(input) {
539
630
  this.assertInput(input, 'browserAssistedApiKey');
540
631
  const now = this.now();
@@ -547,6 +638,7 @@ class BrowserAssistedApiKeyConnectAdapter {
547
638
  mode: 'browserAssistedApiKey',
548
639
  status: 'pending',
549
640
  provider: this.provider,
641
+ offeringId: attempt.offeringId,
550
642
  deployment: this.deployment,
551
643
  attemptId: attempt.id,
552
644
  state: attempt.state,
@@ -567,6 +659,7 @@ class BrowserAssistedApiKeyConnectAdapter {
567
659
  });
568
660
  const encryptedSecret = await this.vault.seal(principal(input.webId), credentialIri, this.provider, { type: 'apiKey', apiKey: input.apiKey });
569
661
  const metadata = metadataWithoutUndefined({
662
+ offeringId: attempt.offeringId,
570
663
  baseUrl: input.baseUrl,
571
664
  health: 'unknown',
572
665
  });
@@ -581,6 +674,7 @@ class BrowserAssistedApiKeyConnectAdapter {
581
674
  status: 'active',
582
675
  accountLabel: input.accountLabel,
583
676
  expectedVersion: consumed.expectedCredentialVersion,
677
+ offeringId: attempt.offeringId,
584
678
  health: 'unknown',
585
679
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
586
680
  }, { auth: input.auth });
@@ -588,6 +682,7 @@ class BrowserAssistedApiKeyConnectAdapter {
588
682
  mode: 'browserAssistedApiKey',
589
683
  status: 'completed',
590
684
  provider: this.provider,
685
+ offeringId: attempt.offeringId,
591
686
  deployment: input.deployment,
592
687
  attemptId: attempt.id,
593
688
  credentialId: record.id,
@@ -599,6 +694,7 @@ class BrowserAssistedApiKeyConnectAdapter {
599
694
  mode: attempt.mode,
600
695
  status: this.statusForAttempt(attempt),
601
696
  provider: this.provider,
697
+ offeringId: attempt.offeringId,
602
698
  deployment: input.deployment,
603
699
  attemptId: attempt.id,
604
700
  expiresAt: attempt.expiresAt.toISOString(),
@@ -613,239 +709,556 @@ class BrowserAssistedApiKeyConnectAdapter {
613
709
  auth: input.auth,
614
710
  });
615
711
  }
616
- async createAttempt(input, expiresAt, extra = {}) {
617
- const attemptWithoutSignature = {
618
- id: token(this.randomBytes),
619
- provider: this.provider,
620
- deployment: input.deployment,
621
- webId: input.webId,
622
- mode: input.requestedMode,
623
- state: token(this.randomBytes),
624
- expiresAt,
625
- expectedCredentialVersion: input.expectedCredentialVersion,
626
- ...extra,
627
- };
628
- const signature = signAttempt(attemptWithoutSignature, this.signingSecret);
629
- return this.attempts.create({ ...attemptWithoutSignature, signature });
630
- }
631
- async loadAttemptForStatus(input, mode) {
632
- const attempt = await this.attempts.get(input.attemptId, this.now());
633
- if (!attempt) {
634
- throw new Error('Connect attempt not found');
635
- }
636
- if (attempt.webId !== input.webId) {
637
- throw new Error('Connect attempt is bound to a different WebID');
638
- }
639
- if (attempt.deployment !== input.deployment) {
640
- throw new Error('Connect attempt is bound to a different deployment');
641
- }
642
- if (attempt.provider !== normalizeProvider(input.provider)) {
643
- throw new Error('Connect attempt is bound to a different provider');
644
- }
645
- if (attempt.mode !== mode) {
646
- throw new Error('Connect attempt mode mismatch');
647
- }
648
- if (attempt.state !== input.state) {
649
- throw new Error('Invalid Connect attempt state');
650
- }
651
- if (!signatureMatches(input.signature, signAttempt(attempt, this.signingSecret))) {
652
- throw new Error('Invalid Connect attempt signature');
653
- }
654
- return attempt;
655
- }
656
- async loadConsumableAttempt(input, mode) {
657
- const attempt = await this.loadAttemptForStatus(input, mode);
658
- if (attempt.expiresAt.getTime() <= this.now().getTime()) {
659
- throw new Error('Connect attempt expired');
660
- }
661
- if (attempt.consumedAt) {
662
- throw new Error('Connect attempt already consumed');
663
- }
664
- return attempt;
665
- }
666
- statusForAttempt(attempt) {
667
- if (attempt.expiresAt.getTime() <= this.now().getTime()) {
668
- return 'expired';
669
- }
670
- return attempt.consumedAt ? 'completed' : 'pending';
671
- }
672
- assertInput(input, mode) {
673
- if (normalizeProvider(input.provider) !== this.provider) {
674
- throw new Error('Connect provider mismatch');
675
- }
676
- if (input.deployment !== this.deployment) {
677
- throw new Error('Connect deployment mismatch');
678
- }
679
- if (input.requestedMode !== mode) {
680
- throw new Error('Unsupported Connect mode');
681
- }
682
- if (!input.webId) {
683
- throw new Error('Connect WebID is required');
684
- }
685
- }
686
712
  }
687
713
  exports.BrowserAssistedApiKeyConnectAdapter = BrowserAssistedApiKeyConnectAdapter;
688
- class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
714
+ class DeviceCodeConnectAdapter extends SignedConnectAttemptAdapterBase {
689
715
  constructor(options) {
690
716
  super({
691
717
  ...options,
692
- provider: 'kimi',
693
- consoleUrl: 'https://kimi.moonshot.cn/device',
718
+ provider: options.integration.provider,
694
719
  });
720
+ this.mode = 'deviceCodeOAuth';
721
+ this.authorizationMethodId = 'device-code';
722
+ this.integration = options.integration;
723
+ this.offeringId = options.integration.offeringId;
695
724
  this.fetchImpl = options.fetch ?? fetch;
696
- this.clientId = (0, OAuthIntegrationRegistry_1.requireKimiOAuthClientId)(options.oauthIntegration);
697
- this.deviceAuthorizationEndpoint = options.deviceAuthorizationEndpoint ?? 'https://auth.kimi.com/api/oauth/device_authorization';
698
- this.tokenEndpoint = options.tokenEndpoint ?? 'https://auth.kimi.com/api/oauth/token';
725
+ this.clientId = options.integration.clientId;
726
+ this.protocol = options.integration.protocol;
727
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 10_000;
699
728
  this.oauthCredentials = new OAuthConnectAdapter_1.OAuthConnectCredentialStore({
700
- provider: 'kimi',
729
+ provider: this.provider,
730
+ offeringId: this.offeringId,
701
731
  deployment: options.deployment,
702
732
  credentialRepository: options.credentialRepository,
703
733
  vault: options.vault,
704
734
  });
705
- assertKimiEndpoint(this.deviceAuthorizationEndpoint, '/api/oauth/device_authorization');
706
- assertKimiEndpoint(this.tokenEndpoint, '/api/oauth/token');
735
+ assertTrustedEndpoint(this.protocol.begin);
736
+ assertTrustedEndpoint(this.protocol.poll);
737
+ if (this.protocol.tokenExchange) {
738
+ assertTrustedEndpoint(this.protocol.tokenExchange);
739
+ }
740
+ if (this.protocol.refresh) {
741
+ assertTrustedEndpoint(this.protocol.refresh);
742
+ }
707
743
  }
708
744
  async begin(input) {
709
745
  this.assertInput(input, 'deviceCodeOAuth');
746
+ this.assertOfferingInput(input.offeringId);
747
+ this.assertAuthorizationMethodInput(input.authorizationMethodId);
710
748
  const now = this.now();
711
- const verifier = token(this.randomBytes);
712
- const challenge = codeChallenge(verifier);
713
- const response = await this.fetchImpl(this.deviceAuthorizationEndpoint, {
749
+ const usesPkce = this.protocol.begin.codec === 'oauthDeviceCodePkce' || this.protocol.poll.codec === 'oauthDeviceCodePkce';
750
+ const verifier = usesPkce ? token(this.randomBytes) : undefined;
751
+ const challenge = verifier ? codeChallenge(verifier) : undefined;
752
+ const response = await this.fetchImpl(this.protocol.begin.endpoint, this.providerRequest({
714
753
  method: 'POST',
715
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
716
- body: new URLSearchParams({
717
- client_id: this.clientId,
718
- code_challenge: challenge,
719
- code_challenge_method: 'S256',
720
- }),
721
- });
754
+ ...deviceCodeBeginRequest(this.protocol, this.clientId, challenge),
755
+ }));
722
756
  const body = await safeJson(response);
723
757
  if (!response.ok) {
724
- throw new Error(`Kimi device authorization failed: ${safeProviderError(body)}`);
725
- }
726
- requireStringField(body, 'device_code');
727
- requireStringField(body, 'user_code');
728
- requireStringField(body, 'verification_uri_complete');
729
- const expiresIn = numberFrom(body.expires_in, 300);
730
- const attempt = await this.createAttempt(input, new Date(now.getTime() + expiresIn * 1000), {
758
+ throw new Error(`Device authorization failed: ${safeProviderError(body)}`);
759
+ }
760
+ const deviceCode = requireProtocolStringField(body, this.protocol.begin.deviceCodeField ?? ['device_code', 'device_auth_id']);
761
+ const userCode = requireProtocolStringField(body, this.protocol.begin.userCodeField ?? ['user_code', 'usercode']);
762
+ if (!stringFrom(body[this.protocol.begin.verificationUriCompleteField ?? 'verification_uri_complete'])
763
+ && !stringFrom(body[this.protocol.begin.verificationUriField ?? 'verification_uri'])
764
+ && !this.protocol.defaultVerificationUri) {
765
+ throw new Error('Provider response missing required field: verification_uri_complete');
766
+ }
767
+ const verificationUri = verifiedVerificationUri(body, this.protocol);
768
+ const expiresAt = deviceCodeExpiresAt(body, this.protocol, now);
769
+ const intervalSeconds = numberFrom(body[this.protocol.begin.intervalField ?? 'interval'], this.protocol.begin.defaultIntervalSeconds ?? 5);
770
+ const attempt = await this.createAttempt(input, expiresAt, {
731
771
  mode: 'deviceCodeOAuth',
772
+ offeringId: this.offeringId,
773
+ authorizationMethodId: this.authorizationMethodId,
732
774
  codeVerifier: verifier,
733
- deviceCode: stringFrom(body.device_code),
734
- intervalSeconds: numberFrom(body.interval, 5),
735
- currentPollIntervalSeconds: numberFrom(body.interval, 5),
736
- nextPollAt: new Date(now.getTime() + numberFrom(body.interval, 5) * 1000),
775
+ deviceCode,
776
+ userCode,
777
+ intervalSeconds,
778
+ currentPollIntervalSeconds: intervalSeconds,
779
+ nextPollAt: new Date(now.getTime() + intervalSeconds * 1000),
737
780
  });
738
781
  return {
739
782
  mode: 'deviceCodeOAuth',
740
783
  status: 'pending',
741
- provider: 'kimi',
784
+ provider: this.provider,
785
+ offeringId: this.offeringId,
742
786
  deployment: input.deployment,
743
787
  attemptId: attempt.id,
744
788
  state: attempt.state,
745
789
  signature: attempt.signature,
746
790
  expiresAt: attempt.expiresAt.toISOString(),
747
791
  pkceChallenge: challenge,
748
- deviceCode: attempt.deviceCode,
749
- userCode: stringFrom(body.user_code),
750
- verificationUri: stringFrom(body.verification_uri),
751
- verificationUriComplete: stringFrom(body.verification_uri_complete),
792
+ userCode,
793
+ verificationUri: verificationUri.verificationUri,
794
+ verificationUriComplete: verificationUri.verificationUriComplete,
752
795
  intervalSeconds: attempt.intervalSeconds,
753
796
  };
754
797
  }
755
798
  async pollDevice(input) {
756
799
  const attempt = await this.loadConsumableAttempt(input, 'deviceCodeOAuth');
800
+ this.assertAttemptOffering(attempt);
757
801
  const now = this.nowForConsume();
758
802
  const claim = await this.attempts.claimPoll(input.attemptId, now);
759
803
  if (!claim.claimed) {
760
804
  return pendingResult(input, claim.attempt.lastPollStatus === 'slow_down' ? 'slow_down' : 'authorization_pending', claim.attempt.currentPollIntervalSeconds ?? claim.attempt.intervalSeconds);
761
805
  }
762
806
  try {
763
- const response = await this.fetchImpl(this.tokenEndpoint, {
807
+ const response = await this.fetchImpl(this.protocol.poll.endpoint, this.providerRequest({
764
808
  method: 'POST',
765
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
766
- body: new URLSearchParams({
767
- grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
768
- device_code: attempt.deviceCode ?? '',
769
- client_id: this.clientId,
770
- code_verifier: attempt.codeVerifier ?? '',
771
- }),
772
- });
809
+ ...deviceCodePollRequest(this.protocol, this.clientId, attempt),
810
+ }));
773
811
  const body = await safeJson(response);
774
812
  if (!response.ok) {
775
- if (body.error === 'authorization_pending' || body.error === 'slow_down') {
776
- const status = body.error;
777
- const intervalSeconds = status === 'slow_down'
813
+ const pendingStatus = this.pendingPollStatus(response, body);
814
+ if (pendingStatus) {
815
+ const intervalSeconds = pendingStatus === 'slow_down'
778
816
  ? (attempt.currentPollIntervalSeconds ?? attempt.intervalSeconds ?? 5) + 5
779
817
  : (attempt.currentPollIntervalSeconds ?? attempt.intervalSeconds ?? 5);
780
818
  await this.attempts.updatePollSchedule(input.attemptId, {
781
819
  intervalSeconds,
782
820
  nextPollAt: new Date(now.getTime() + intervalSeconds * 1000),
783
- lastPollStatus: status,
821
+ lastPollStatus: pendingStatus,
784
822
  });
785
- return pendingResult(input, status, intervalSeconds);
823
+ return pendingResult(input, pendingStatus, intervalSeconds, this.provider, this.offeringId);
786
824
  }
787
825
  if (body.error === 'expired_token') {
788
- await this.attempts.consume(input.attemptId, this.nowForConsume());
789
- return pendingResult(input, 'expired');
826
+ await this.attempts.consume(input.attemptId, this.nowForConsume(), 'expired');
827
+ return pendingResult(input, 'expired', undefined, this.provider, this.offeringId);
828
+ }
829
+ if (body.error === 'access_denied') {
830
+ await this.attempts.consume(input.attemptId, this.nowForConsume(), 'denied');
831
+ return pendingResult(input, 'denied', undefined, this.provider, this.offeringId);
790
832
  }
791
- throw new Error(`Kimi device token failed: ${safeProviderError(body)}`);
833
+ throw new Error(`Device token failed: ${safeProviderError(body)}`);
792
834
  }
835
+ const tokenBody = await this.resolveTokenBody(body, attempt);
793
836
  await this.attempts.consume(input.attemptId, this.nowForConsume());
794
- requireStringField(body, 'access_token');
795
- requireStringField(body, 'refresh_token');
837
+ requireStringField(tokenBody, 'access_token');
838
+ requireStringField(tokenBody, 'refresh_token');
796
839
  return {
797
840
  mode: 'deviceCodeOAuth',
798
841
  status: 'completed',
799
- provider: 'kimi',
842
+ provider: this.provider,
843
+ offeringId: this.offeringId,
800
844
  deployment: input.deployment,
801
845
  attemptId: input.attemptId,
802
- oauthCredential: oneTimeOAuthCredential(body, this.now(), attempt.expectedCredentialVersion),
846
+ oauthCredential: oneTimeOAuthCredential(tokenBody, this.now(), attempt.expectedCredentialVersion, this.integration, undefined, this.authorizationMethodId),
803
847
  };
804
848
  }
805
849
  catch (error) {
806
850
  await this.attempts.releasePollClaim(input.attemptId);
807
851
  throw error;
808
852
  }
809
- }
810
- async status(input) {
811
- const attempt = await this.loadAttemptForStatus(input, 'deviceCodeOAuth');
853
+ }
854
+ pendingPollStatus(response, body) {
855
+ if (body.error === 'authorization_pending' || body.error === 'slow_down') {
856
+ return body.error;
857
+ }
858
+ return this.protocol.poll.pendingHttpStatuses?.includes(response.status) ? 'authorization_pending' : undefined;
859
+ }
860
+ async status(input) {
861
+ const attempt = await this.loadAttemptForStatus(input, 'deviceCodeOAuth');
862
+ this.assertAttemptOffering(attempt);
863
+ return {
864
+ mode: 'deviceCodeOAuth',
865
+ status: this.statusForAttempt(attempt),
866
+ provider: this.provider,
867
+ offeringId: this.offeringId,
868
+ deployment: input.deployment,
869
+ attemptId: attempt.id,
870
+ expiresAt: attempt.expiresAt.toISOString(),
871
+ intervalSeconds: attempt.currentPollIntervalSeconds ?? attempt.intervalSeconds,
872
+ };
873
+ }
874
+ async cancel(input) {
875
+ const attempt = await this.loadConsumableAttempt(input, 'deviceCodeOAuth');
876
+ this.assertAttemptOffering(attempt);
877
+ await this.attempts.consume(input.attemptId, this.nowForConsume(), 'cancelled');
878
+ return pendingResult(input, 'cancelled', undefined, this.provider, this.offeringId);
879
+ }
880
+ async refresh(input, current, secret) {
881
+ this.assertStoredOffering(current);
882
+ const refreshToken = stringFrom(secret.refreshToken);
883
+ if (!refreshToken) {
884
+ return this.credentialRepository.markReauthRequired({
885
+ webId: input.webId,
886
+ provider: this.provider,
887
+ deployment: input.deployment,
888
+ reason: 'missing_refresh_token',
889
+ expectedVersion: current.version,
890
+ auth: input.auth,
891
+ });
892
+ }
893
+ const refresh = this.protocol.refresh ?? this.protocol.poll;
894
+ const refreshResult = await executeOAuthRefreshRequest(this.fetchImpl, (init) => this.providerRequest(init), refresh, this.clientId, refreshToken);
895
+ const body = refreshResult.body;
896
+ if (!refreshResult.ok) {
897
+ return this.credentialRepository.markReauthRequired({
898
+ webId: input.webId,
899
+ provider: this.provider,
900
+ deployment: input.deployment,
901
+ reason: safeProviderError(body),
902
+ expectedVersion: current.version,
903
+ auth: input.auth,
904
+ });
905
+ }
906
+ requireStringField(body, 'access_token');
907
+ return this.updateOAuthCredential(input, body, current, secret);
908
+ }
909
+ async refreshCallerOwned(input) {
910
+ this.assertOfferingInput(input.offeringId);
911
+ if (!input.refreshToken.trim())
912
+ throw new Error('oauth_refresh_token_required');
913
+ const refresh = this.protocol.refresh ?? this.protocol.poll;
914
+ const refreshResult = await executeOAuthRefreshRequest(this.fetchImpl, (init) => this.providerRequest(init), refresh, this.clientId, input.refreshToken);
915
+ const body = refreshResult.body;
916
+ if (!refreshResult.ok) {
917
+ throw new Error(`OAuth refresh failed: ${safeProviderError(body)}`);
918
+ }
919
+ return {
920
+ mode: 'deviceCodeOAuth',
921
+ status: 'completed',
922
+ provider: this.provider,
923
+ offeringId: this.offeringId,
924
+ deployment: input.deployment,
925
+ credentialId: input.credentialId,
926
+ oauthCredential: oneTimeOAuthCredential(body, this.now(), input.expectedVersion, this.integration, input.refreshToken, this.authorizationMethodId),
927
+ };
928
+ }
929
+ async disconnect(input) {
930
+ this.assertOfferingInput(input.offeringId);
931
+ const current = await this.findOAuthCredential(input);
932
+ if (!current) {
933
+ throw new Error('oauth_credential_not_found');
934
+ }
935
+ return this.credentialRepository.revokeCredential({
936
+ webId: input.webId,
937
+ provider: this.provider,
938
+ deployment: input.deployment,
939
+ credentialId: current.id,
940
+ expectedVersion: current.version,
941
+ auth: input.auth,
942
+ });
943
+ }
944
+ nowForConsume() {
945
+ return this.now();
946
+ }
947
+ async updateOAuthCredential(input, body, current, currentSecret) {
948
+ const expiresAt = expiresAtFrom(body.expires_in, this.now());
949
+ const nextIdToken = stringFrom(body.id_token) || stringFrom(currentSecret.idToken);
950
+ const secret = {
951
+ type: 'deviceCodeOAuth',
952
+ accessToken: stringFrom(body.access_token),
953
+ refreshToken: stringFrom(body.refresh_token) || stringFrom(currentSecret.refreshToken),
954
+ expiresAt: expiresAt?.toISOString(),
955
+ scope: stringFrom(body.scope),
956
+ idToken: nextIdToken,
957
+ accountId: stringFrom(body.account_id)
958
+ || jwtClaim(nextIdToken, this.protocol.accountIdClaim)
959
+ || stringFrom(currentSecret.accountId),
960
+ };
961
+ return this.oauthCredentials.updateOAuthCredential({
962
+ current,
963
+ webId: input.webId,
964
+ deployment: input.deployment,
965
+ secret,
966
+ expiresAt,
967
+ auth: input.auth,
968
+ metadata: {
969
+ authoritativeSubject: decodeJwtSubject(nextIdToken),
970
+ },
971
+ });
972
+ }
973
+ async findOAuthCredential(input) {
974
+ const credentials = input.credentialId
975
+ ? [
976
+ await this.credentialRepository.getCredentialById({
977
+ webId: input.webId,
978
+ provider: this.provider,
979
+ deployment: input.deployment,
980
+ credentialId: input.credentialId,
981
+ auth: input.auth,
982
+ }),
983
+ ]
984
+ : await this.credentialRepository.listProviderCredentials({
985
+ webId: input.webId,
986
+ provider: this.provider,
987
+ deployment: input.deployment,
988
+ auth: input.auth,
989
+ });
990
+ return credentials.find((credential) => credential && this.isProviderOAuthCredential(credential));
991
+ }
992
+ async resolveTokenBody(body, attempt) {
993
+ if (this.protocol.tokenExchange?.codec !== 'authorizationCodeForm') {
994
+ return body;
995
+ }
996
+ const code = requireProtocolStringField(body, this.protocol.tokenExchange.codeField ?? ['code', 'authorization_code']);
997
+ const codeVerifier = stringFrom(body[this.protocol.tokenExchange.codeVerifierField ?? 'code_verifier']) || attempt.codeVerifier;
998
+ if (!codeVerifier) {
999
+ throw new Error('Provider response missing required field: code_verifier');
1000
+ }
1001
+ const response = await this.fetchImpl(this.protocol.tokenExchange.endpoint, this.providerRequest({
1002
+ method: 'POST',
1003
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1004
+ body: new URLSearchParams({
1005
+ grant_type: 'authorization_code',
1006
+ code,
1007
+ code_verifier: codeVerifier,
1008
+ client_id: this.clientId,
1009
+ ...(this.protocol.tokenExchange.redirectUri ? { redirect_uri: this.protocol.tokenExchange.redirectUri } : {}),
1010
+ }),
1011
+ }));
1012
+ const tokenBody = await safeJson(response);
1013
+ if (!response.ok) {
1014
+ throw new Error(`Device token exchange failed: ${safeProviderError(tokenBody)}`);
1015
+ }
1016
+ return tokenBody;
1017
+ }
1018
+ providerRequest(init) {
1019
+ return {
1020
+ ...init,
1021
+ redirect: 'error',
1022
+ signal: requestTimeoutSignal(this.requestTimeoutMs),
1023
+ };
1024
+ }
1025
+ assertOfferingInput(offeringId) {
1026
+ if (offeringId && normalizeProvider(offeringId) !== normalizeProvider(this.offeringId)) {
1027
+ throw new Error('Connect offering mismatch');
1028
+ }
1029
+ }
1030
+ assertAuthorizationMethodInput(authorizationMethodId) {
1031
+ if (authorizationMethodId && normalizeProvider(authorizationMethodId) !== this.authorizationMethodId) {
1032
+ throw new Error('Connect authorization method mismatch');
1033
+ }
1034
+ }
1035
+ assertAttemptOffering(attempt) {
1036
+ if (attempt.offeringId !== this.offeringId) {
1037
+ throw new Error('Connect attempt is bound to a different offering');
1038
+ }
1039
+ if (attempt.authorizationMethodId !== this.authorizationMethodId) {
1040
+ throw new Error('Connect attempt authorization method mismatch');
1041
+ }
1042
+ }
1043
+ assertStoredOffering(current) {
1044
+ const metadata = metadataFromRowValue(current.metadata) ?? {};
1045
+ if ((current.offeringId ?? stringMetadata(metadata, 'offeringId')) !== this.offeringId) {
1046
+ throw new Error('oauth_credential_not_found');
1047
+ }
1048
+ }
1049
+ isProviderOAuthCredential(credential) {
1050
+ if (!credential)
1051
+ return false;
1052
+ const metadata = metadataFromRowValue(credential.metadata) ?? {};
1053
+ return credential.provider === this.provider
1054
+ && isOAuthProviderCredential(credential)
1055
+ && (credential.offeringId ?? stringMetadata(metadata, 'offeringId')) === this.offeringId;
1056
+ }
1057
+ }
1058
+ exports.DeviceCodeConnectAdapter = DeviceCodeConnectAdapter;
1059
+ class AuthorizationCodeConnectAdapter extends SignedConnectAttemptAdapterBase {
1060
+ constructor(options) {
1061
+ super({
1062
+ ...options,
1063
+ provider: options.integration.provider,
1064
+ });
1065
+ this.mode = 'authorizationCodeOAuth';
1066
+ this.authorizationMethodId = 'browser-oauth';
1067
+ this.callbacks = new Map();
1068
+ this.integration = options.integration;
1069
+ this.offeringId = options.integration.offeringId;
1070
+ this.fetchImpl = options.fetch ?? fetch;
1071
+ this.clientId = options.integration.clientId;
1072
+ this.callbackReceiver = options.callbackReceiver;
1073
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 10_000;
1074
+ this.oauthCredentials = new OAuthConnectAdapter_1.OAuthConnectCredentialStore({
1075
+ provider: this.provider,
1076
+ offeringId: this.offeringId,
1077
+ deployment: options.deployment,
1078
+ credentialRepository: options.credentialRepository,
1079
+ vault: options.vault,
1080
+ });
1081
+ assertTrustedEndpoint(this.integration.protocol.authorization);
1082
+ assertTrustedEndpoint(this.integration.protocol.token);
1083
+ if (this.integration.protocol.refresh) {
1084
+ assertTrustedEndpoint(this.integration.protocol.refresh);
1085
+ }
1086
+ }
1087
+ async begin(input) {
1088
+ this.assertInput(input, 'authorizationCodeOAuth');
1089
+ this.assertOfferingInput(input.offeringId);
1090
+ this.assertAuthorizationMethodInput(input.authorizationMethodId);
1091
+ const now = this.now();
1092
+ const expiresAt = new Date(now.getTime() + 15 * 60 * 1000);
1093
+ const verifier = token(this.randomBytes);
1094
+ const challenge = codeChallenge(verifier);
1095
+ const attempt = await this.createAttempt(input, expiresAt, {
1096
+ mode: 'authorizationCodeOAuth',
1097
+ offeringId: this.offeringId,
1098
+ authorizationMethodId: this.authorizationMethodId,
1099
+ codeVerifier: verifier,
1100
+ });
1101
+ let registration;
1102
+ try {
1103
+ registration = await this.callbackReceiver.register({
1104
+ redirectUris: this.integration.protocol.authorization.redirectUris,
1105
+ state: attempt.state,
1106
+ expiresAt,
1107
+ onCallback: async (result) => {
1108
+ const context = this.callbacks.get(attempt.id);
1109
+ if (!context)
1110
+ return;
1111
+ context.authorizationCode = result.code;
1112
+ context.error = result.error;
1113
+ },
1114
+ });
1115
+ if (!this.integration.protocol.authorization.redirectUris.includes(registration.redirectUri)) {
1116
+ registration.close();
1117
+ await this.attempts.consume(attempt.id, this.now(), 'cancelled');
1118
+ throw new Error('Authorization callback redirect URI is not allowlisted');
1119
+ }
1120
+ const expiresTimer = setTimeout(() => this.closeCallback(attempt.id), Math.max(0, expiresAt.getTime() - this.now().getTime()));
1121
+ expiresTimer.unref();
1122
+ this.callbacks.set(attempt.id, {
1123
+ redirectUri: registration.redirectUri,
1124
+ close: registration.close,
1125
+ expiresTimer,
1126
+ });
1127
+ }
1128
+ catch (error) {
1129
+ registration?.close();
1130
+ this.callbacks.delete(attempt.id);
1131
+ try {
1132
+ await this.attempts.consume(attempt.id, this.now(), 'cancelled');
1133
+ }
1134
+ catch {
1135
+ // Attempt cleanup is best-effort when registration fails after expiry.
1136
+ }
1137
+ throw error;
1138
+ }
1139
+ return {
1140
+ mode: 'authorizationCodeOAuth',
1141
+ status: 'pending',
1142
+ provider: this.provider,
1143
+ offeringId: this.offeringId,
1144
+ deployment: input.deployment,
1145
+ attemptId: attempt.id,
1146
+ state: attempt.state,
1147
+ signature: attempt.signature,
1148
+ expiresAt: attempt.expiresAt.toISOString(),
1149
+ pkceChallenge: challenge,
1150
+ authorizationUrl: this.authorizationUrl(attempt, challenge, registration.redirectUri),
1151
+ };
1152
+ }
1153
+ async pollDevice(input) {
1154
+ const attempt = await this.loadAuthorizationAttempt(input, true);
1155
+ this.assertAttemptOffering(attempt);
1156
+ const context = this.callbacks.get(input.attemptId);
1157
+ if (!context) {
1158
+ throw new Error('Authorization callback listener not found');
1159
+ }
1160
+ if (context.error) {
1161
+ this.closeCallback(input.attemptId);
1162
+ await this.attempts.consume(input.attemptId, this.now(), 'denied');
1163
+ return oauthPendingResult(input, 'denied', this.provider, this.offeringId, 'authorizationCodeOAuth');
1164
+ }
1165
+ if (!context.authorizationCode) {
1166
+ return oauthPendingResult(input, 'authorization_pending', this.provider, this.offeringId, 'authorizationCodeOAuth');
1167
+ }
1168
+ const now = this.now();
1169
+ const claim = await this.attempts.claimPoll(input.attemptId, now);
1170
+ if (!claim.claimed) {
1171
+ return oauthPendingResult(input, 'authorization_pending', this.provider, this.offeringId, 'authorizationCodeOAuth');
1172
+ }
1173
+ try {
1174
+ const body = await this.exchangeAuthorizationCode(context.authorizationCode, attempt.codeVerifier, context.redirectUri);
1175
+ const oauthCredential = oneTimeOAuthCredential(body, this.now(), attempt.expectedCredentialVersion, this.integration, undefined, this.authorizationMethodId);
1176
+ await this.attempts.consume(input.attemptId, this.now());
1177
+ this.closeCallback(input.attemptId);
1178
+ return {
1179
+ mode: 'authorizationCodeOAuth',
1180
+ status: 'completed',
1181
+ provider: this.provider,
1182
+ offeringId: this.offeringId,
1183
+ deployment: input.deployment,
1184
+ attemptId: input.attemptId,
1185
+ oauthCredential,
1186
+ };
1187
+ }
1188
+ catch (error) {
1189
+ this.closeCallback(input.attemptId);
1190
+ try {
1191
+ await this.attempts.consume(input.attemptId, this.now(), 'denied');
1192
+ }
1193
+ catch {
1194
+ await this.attempts.releasePollClaim(input.attemptId);
1195
+ }
1196
+ throw error;
1197
+ }
1198
+ }
1199
+ async status(input) {
1200
+ const attempt = await this.loadAuthorizationAttempt(input, false);
1201
+ this.assertAttemptOffering(attempt);
1202
+ return {
1203
+ mode: 'authorizationCodeOAuth',
1204
+ status: this.statusForAttempt(attempt),
1205
+ provider: this.provider,
1206
+ offeringId: this.offeringId,
1207
+ deployment: input.deployment,
1208
+ attemptId: attempt.id,
1209
+ expiresAt: attempt.expiresAt.toISOString(),
1210
+ };
1211
+ }
1212
+ async cancel(input) {
1213
+ const attempt = await this.loadAuthorizationAttempt(input, true);
1214
+ this.assertAttemptOffering(attempt);
1215
+ this.closeCallback(input.attemptId);
1216
+ await this.attempts.consume(input.attemptId, this.now(), 'cancelled');
1217
+ return oauthPendingResult(input, 'cancelled', this.provider, this.offeringId, 'authorizationCodeOAuth');
1218
+ }
1219
+ async refreshCallerOwned(input) {
1220
+ this.assertOfferingInput(input.offeringId);
1221
+ if (input.authorizationMethodId) {
1222
+ this.assertAuthorizationMethodInput(input.authorizationMethodId);
1223
+ }
1224
+ if (!input.refreshToken.trim())
1225
+ throw new Error('oauth_refresh_token_required');
1226
+ const refresh = this.integration.protocol.refresh ?? this.integration.protocol.token;
1227
+ const refreshResult = await executeOAuthRefreshRequest(this.fetchImpl, (init) => this.providerRequest(init), refresh, this.clientId, input.refreshToken);
1228
+ const body = refreshResult.body;
1229
+ if (!refreshResult.ok) {
1230
+ throw new Error(`OAuth refresh failed: ${safeProviderError(body)}`);
1231
+ }
812
1232
  return {
813
- mode: 'deviceCodeOAuth',
814
- status: this.statusForAttempt(attempt),
815
- provider: 'kimi',
1233
+ mode: 'authorizationCodeOAuth',
1234
+ status: 'completed',
1235
+ provider: this.provider,
1236
+ offeringId: this.offeringId,
816
1237
  deployment: input.deployment,
817
- attemptId: attempt.id,
818
- expiresAt: attempt.expiresAt.toISOString(),
819
- deviceCode: attempt.deviceCode,
820
- intervalSeconds: attempt.currentPollIntervalSeconds ?? attempt.intervalSeconds,
1238
+ credentialId: input.credentialId,
1239
+ oauthCredential: oneTimeOAuthCredential(body, this.now(), input.expectedVersion, this.integration, input.refreshToken, this.authorizationMethodId),
821
1240
  };
822
1241
  }
823
1242
  async refresh(input, current, secret) {
1243
+ this.assertStoredOffering(current);
824
1244
  const refreshToken = stringFrom(secret.refreshToken);
825
1245
  if (!refreshToken) {
826
1246
  return this.credentialRepository.markReauthRequired({
827
1247
  webId: input.webId,
828
- provider: 'kimi',
1248
+ provider: this.provider,
829
1249
  deployment: input.deployment,
830
1250
  reason: 'missing_refresh_token',
831
1251
  expectedVersion: current.version,
832
1252
  auth: input.auth,
833
1253
  });
834
1254
  }
835
- const response = await this.fetchImpl(this.tokenEndpoint, {
836
- method: 'POST',
837
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
838
- body: new URLSearchParams({
839
- grant_type: 'refresh_token',
840
- refresh_token: refreshToken,
841
- client_id: this.clientId,
842
- }),
843
- });
844
- const body = await safeJson(response);
845
- if (!response.ok) {
1255
+ const refresh = this.integration.protocol.refresh ?? this.integration.protocol.token;
1256
+ const refreshResult = await executeOAuthRefreshRequest(this.fetchImpl, (init) => this.providerRequest(init), refresh, this.clientId, refreshToken);
1257
+ const body = refreshResult.body;
1258
+ if (!refreshResult.ok) {
846
1259
  return this.credentialRepository.markReauthRequired({
847
1260
  webId: input.webId,
848
- provider: 'kimi',
1261
+ provider: this.provider,
849
1262
  deployment: input.deployment,
850
1263
  reason: safeProviderError(body),
851
1264
  expectedVersion: current.version,
@@ -853,60 +1266,74 @@ class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
853
1266
  });
854
1267
  }
855
1268
  requireStringField(body, 'access_token');
856
- requireStringField(body, 'refresh_token');
857
- return this.updateOAuthCredential(input, body, current);
858
- }
859
- async refreshCallerOwned(input) {
860
- if (!input.refreshToken.trim())
861
- throw new Error('oauth_refresh_token_required');
862
- const response = await this.fetchImpl(this.tokenEndpoint, {
863
- method: 'POST',
864
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
865
- body: new URLSearchParams({
866
- grant_type: 'refresh_token',
867
- refresh_token: input.refreshToken,
868
- client_id: this.clientId,
869
- }),
870
- });
871
- const body = await safeJson(response);
872
- if (!response.ok) {
873
- throw new Error(`Kimi OAuth refresh failed: ${safeProviderError(body)}`);
874
- }
875
- return {
876
- mode: 'deviceCodeOAuth',
877
- status: 'completed',
878
- provider: 'kimi',
879
- deployment: input.deployment,
880
- credentialId: input.credentialId,
881
- oauthCredential: oneTimeOAuthCredential(body, this.now(), input.expectedVersion),
882
- };
1269
+ return this.updateOAuthCredential(input, body, current, secret);
883
1270
  }
884
1271
  async disconnect(input) {
1272
+ this.assertOfferingInput(input.offeringId);
885
1273
  const current = await this.findOAuthCredential(input);
886
1274
  if (!current) {
887
1275
  throw new Error('oauth_credential_not_found');
888
1276
  }
889
1277
  return this.credentialRepository.revokeCredential({
890
1278
  webId: input.webId,
891
- provider: 'kimi',
1279
+ provider: this.provider,
892
1280
  deployment: input.deployment,
893
1281
  credentialId: current.id,
894
1282
  expectedVersion: current.version,
895
1283
  auth: input.auth,
896
1284
  });
897
1285
  }
898
- nowForConsume() {
899
- return this.now();
1286
+ authorizationUrl(attempt, challenge, redirectUri) {
1287
+ const url = new URL(this.integration.protocol.authorization.endpoint);
1288
+ url.searchParams.set('response_type', this.integration.protocol.authorization.responseType ?? 'code');
1289
+ url.searchParams.set('client_id', this.clientId);
1290
+ url.searchParams.set('redirect_uri', redirectUri);
1291
+ url.searchParams.set('state', attempt.state);
1292
+ url.searchParams.set('code_challenge', challenge);
1293
+ url.searchParams.set('code_challenge_method', this.integration.protocol.authorization.codeChallengeMethod ?? 'S256');
1294
+ const scopes = this.integration.protocol.authorization.scopes ?? [];
1295
+ if (scopes.length > 0) {
1296
+ url.searchParams.set('scope', scopes.join(' '));
1297
+ }
1298
+ for (const [key, value] of Object.entries(this.integration.protocol.authorization.extraParams ?? {})) {
1299
+ url.searchParams.set(key, value);
1300
+ }
1301
+ return url.toString();
1302
+ }
1303
+ async exchangeAuthorizationCode(code, codeVerifier, redirectUri) {
1304
+ if (!codeVerifier) {
1305
+ throw new Error('Authorization code verifier is missing');
1306
+ }
1307
+ const response = await this.fetchImpl(this.integration.protocol.token.endpoint, this.providerRequest({
1308
+ method: 'POST',
1309
+ headers: withProtocolHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }, this.integration.protocol.token.headers),
1310
+ body: new URLSearchParams({
1311
+ grant_type: 'authorization_code',
1312
+ code,
1313
+ code_verifier: codeVerifier,
1314
+ client_id: this.clientId,
1315
+ redirect_uri: redirectUri,
1316
+ }),
1317
+ }));
1318
+ const body = await safeJson(response);
1319
+ if (!response.ok) {
1320
+ throw new Error(`Authorization code token exchange failed: ${safeProviderError(body)}`);
1321
+ }
1322
+ return body;
900
1323
  }
901
- async updateOAuthCredential(input, body, current) {
1324
+ async updateOAuthCredential(input, body, current, currentSecret) {
902
1325
  const expiresAt = expiresAtFrom(body.expires_in, this.now());
1326
+ const nextIdToken = stringFrom(body.id_token) || stringFrom(currentSecret.idToken);
903
1327
  const secret = {
904
1328
  type: 'deviceCodeOAuth',
905
1329
  accessToken: stringFrom(body.access_token),
906
- refreshToken: stringFrom(body.refresh_token),
1330
+ refreshToken: stringFrom(body.refresh_token) || stringFrom(currentSecret.refreshToken),
907
1331
  expiresAt: expiresAt?.toISOString(),
908
1332
  scope: stringFrom(body.scope),
909
- idToken: stringFrom(body.id_token),
1333
+ idToken: nextIdToken,
1334
+ accountId: stringFrom(body.account_id)
1335
+ || jwtClaim(nextIdToken, this.integration.protocol.accountIdClaim)
1336
+ || stringFrom(currentSecret.accountId),
910
1337
  };
911
1338
  return this.oauthCredentials.updateOAuthCredential({
912
1339
  current,
@@ -916,7 +1343,8 @@ class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
916
1343
  expiresAt,
917
1344
  auth: input.auth,
918
1345
  metadata: {
919
- authoritativeSubject: decodeJwtSubject(stringFrom(body.id_token)),
1346
+ authoritativeSubject: decodeJwtSubject(nextIdToken),
1347
+ authorizationMethodId: this.authorizationMethodId,
920
1348
  },
921
1349
  });
922
1350
  }
@@ -925,7 +1353,7 @@ class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
925
1353
  ? [
926
1354
  await this.credentialRepository.getCredentialById({
927
1355
  webId: input.webId,
928
- provider: 'kimi',
1356
+ provider: this.provider,
929
1357
  deployment: input.deployment,
930
1358
  credentialId: input.credentialId,
931
1359
  auth: input.auth,
@@ -933,14 +1361,74 @@ class KimiDeviceCodeConnectAdapter extends BrowserAssistedApiKeyConnectAdapter {
933
1361
  ]
934
1362
  : await this.credentialRepository.listProviderCredentials({
935
1363
  webId: input.webId,
936
- provider: 'kimi',
1364
+ provider: this.provider,
937
1365
  deployment: input.deployment,
938
1366
  auth: input.auth,
939
1367
  });
940
- return credentials.find((credential) => credential && isKimiOAuthCredential(credential));
1368
+ return credentials.find((credential) => credential && this.isProviderOAuthCredential(credential));
1369
+ }
1370
+ providerRequest(init) {
1371
+ return {
1372
+ ...init,
1373
+ redirect: 'error',
1374
+ signal: requestTimeoutSignal(this.requestTimeoutMs),
1375
+ };
1376
+ }
1377
+ assertOfferingInput(offeringId) {
1378
+ if (offeringId && normalizeProvider(offeringId) !== normalizeProvider(this.offeringId)) {
1379
+ throw new Error('Connect offering mismatch');
1380
+ }
1381
+ }
1382
+ assertAuthorizationMethodInput(authorizationMethodId) {
1383
+ if (authorizationMethodId && normalizeProvider(authorizationMethodId) !== this.authorizationMethodId) {
1384
+ throw new Error('Connect authorization method mismatch');
1385
+ }
1386
+ }
1387
+ assertAttemptOffering(attempt) {
1388
+ if (attempt.offeringId !== this.offeringId) {
1389
+ throw new Error('Connect attempt is bound to a different offering');
1390
+ }
1391
+ if (attempt.authorizationMethodId !== this.authorizationMethodId) {
1392
+ throw new Error('Connect attempt authorization method mismatch');
1393
+ }
1394
+ }
1395
+ assertStoredOffering(current) {
1396
+ const metadata = metadataFromRowValue(current.metadata) ?? {};
1397
+ if ((current.offeringId ?? stringMetadata(metadata, 'offeringId')) !== this.offeringId) {
1398
+ throw new Error('oauth_credential_not_found');
1399
+ }
1400
+ }
1401
+ isProviderOAuthCredential(credential) {
1402
+ if (!credential)
1403
+ return false;
1404
+ const metadata = metadataFromRowValue(credential.metadata) ?? {};
1405
+ return credential.provider === this.provider
1406
+ && isOAuthProviderCredential(credential)
1407
+ && (credential.offeringId ?? stringMetadata(metadata, 'offeringId')) === this.offeringId;
1408
+ }
1409
+ async loadAuthorizationAttempt(input, consumable) {
1410
+ try {
1411
+ return consumable
1412
+ ? await this.loadConsumableAttempt(input, 'authorizationCodeOAuth')
1413
+ : await this.loadAttemptForStatus(input, 'authorizationCodeOAuth');
1414
+ }
1415
+ catch (error) {
1416
+ if (isTerminalAttemptError(error)) {
1417
+ this.closeCallback(input.attemptId);
1418
+ }
1419
+ throw error;
1420
+ }
1421
+ }
1422
+ closeCallback(attemptId) {
1423
+ const context = this.callbacks.get(attemptId);
1424
+ if (!context)
1425
+ return;
1426
+ clearTimeout(context.expiresTimer);
1427
+ context.close();
1428
+ this.callbacks.delete(attemptId);
941
1429
  }
942
1430
  }
943
- exports.KimiDeviceCodeConnectAdapter = KimiDeviceCodeConnectAdapter;
1431
+ exports.AuthorizationCodeConnectAdapter = AuthorizationCodeConnectAdapter;
944
1432
  class DeepSeekConnectAdapter {
945
1433
  constructor() {
946
1434
  this.provider = 'deepseek';
@@ -968,7 +1456,7 @@ class ProviderConnectService {
968
1456
  this.credentialRepository = options.credentialRepository;
969
1457
  this.vault = options.vault;
970
1458
  for (const adapter of options.adapters) {
971
- this.adapters.set(normalizeProvider(adapter.provider), adapter);
1459
+ this.adapters.set(connectAdapterKey(adapter.provider, adapter.offeringId, adapter.mode), adapter);
972
1460
  }
973
1461
  for (const importer of options.localSessionImporters ?? []) {
974
1462
  this.localSessionImporters.set(localSessionImporterKey(importer.provider, importer.offeringId), importer);
@@ -976,7 +1464,8 @@ class ProviderConnectService {
976
1464
  }
977
1465
  begin(input) {
978
1466
  const descriptor = this.registry.requireProvider(input.provider);
979
- if (descriptor.connect?.mode !== input.requestedMode) {
1467
+ const offeringId = this.requireConnectOffering(input.provider, input.offeringId, input.requestedMode);
1468
+ if (descriptor.connect?.mode !== input.requestedMode && !this.offeringSupportsConnectMode(input.provider, offeringId, input.requestedMode)) {
980
1469
  throw new Error('Requested Connect mode does not match provider capability');
981
1470
  }
982
1471
  if (descriptor.connect?.configured === false) {
@@ -987,12 +1476,36 @@ class ProviderConnectService {
987
1476
  mode: descriptor.connect.mode,
988
1477
  status: 'unsupported',
989
1478
  provider: normalizeProvider(input.provider),
1479
+ offeringId,
990
1480
  deployment: input.deployment,
991
1481
  apiKeyManagementSupported: descriptor.connect.apiKeyManagementSupported,
992
1482
  message,
993
1483
  });
994
1484
  }
995
- return this.requireAdapter(input.provider).begin(input);
1485
+ return this.requireAdapter(input.provider, offeringId, input.requestedMode).begin({
1486
+ ...input,
1487
+ offeringId,
1488
+ });
1489
+ }
1490
+ getAuthorizationMethods() {
1491
+ return this.registry.listProducts().flatMap((product) => product.offerings.map((offering) => {
1492
+ const authorizationMethods = (offering.authorizationMethods ?? []).map((method) => {
1493
+ const unavailableReason = this.authorizationMethodUnavailableReason(product.id, offering.id, method);
1494
+ return metadataWithoutUndefined({
1495
+ ...method,
1496
+ lifecycle: unavailableReason ? 'unavailable' : method.lifecycle,
1497
+ reason: unavailableReason ?? method.reason,
1498
+ });
1499
+ });
1500
+ return {
1501
+ provider: product.id,
1502
+ offeringId: offering.id,
1503
+ authorizationMethods,
1504
+ ...(product.id === 'custom' ? {} : {
1505
+ endpoints: offering.endpoints.map((endpoint) => ({ ...endpoint })),
1506
+ }),
1507
+ };
1508
+ }));
996
1509
  }
997
1510
  async listProviders(input) {
998
1511
  return Promise.all(this.registry.listProviders().map(async (descriptor) => {
@@ -1100,11 +1613,30 @@ class ProviderConnectService {
1100
1613
  return publicPoolCredentialSummary(created);
1101
1614
  }
1102
1615
  async createLocalCredential(input) {
1616
+ if (!this.credentialRepository || !this.vault)
1617
+ throw new Error('credential_pool_not_configured');
1618
+ const key = JSON.stringify([input.webId, input.deployment, normalizeProvider(input.provider),
1619
+ this.requireLocalOffering(normalizeProvider(input.provider), input.offeringId)]);
1620
+ const previous = ProviderConnectService.localImportLocks.get(key) ?? Promise.resolve();
1621
+ let release;
1622
+ const pending = new Promise((resolve) => { release = resolve; });
1623
+ ProviderConnectService.localImportLocks.set(key, pending);
1624
+ await previous;
1625
+ try {
1626
+ return await this.importLocalCredential(input);
1627
+ }
1628
+ finally {
1629
+ release();
1630
+ if (ProviderConnectService.localImportLocks.get(key) === pending) {
1631
+ ProviderConnectService.localImportLocks.delete(key);
1632
+ }
1633
+ }
1634
+ }
1635
+ async importLocalCredential(input) {
1103
1636
  if (!this.credentialRepository || !this.vault)
1104
1637
  throw new Error('credential_pool_not_configured');
1105
1638
  const provider = normalizeProvider(input.provider);
1106
1639
  const offeringId = this.requireLocalOffering(provider, input.offeringId);
1107
- const { credentialId, credentialIri } = createPoolCredentialLocator(input.webId, input.deployment, provider);
1108
1640
  const importer = this.localSessionImporters.get(localSessionImporterKey(provider, offeringId));
1109
1641
  const offering = this.registry.getOffering(provider, offeringId);
1110
1642
  if (offering?.kind === 'oauth-subscription' && !importer) {
@@ -1115,7 +1647,79 @@ class ProviderConnectService {
1115
1647
  });
1116
1648
  }
1117
1649
  const imported = await importer?.importSession({ deployment: input.deployment });
1118
- const encryptedSecret = await this.vault.seal({ webId: input.webId }, credentialIri, provider, imported?.secret ?? { type: 'local' });
1650
+ if (imported) {
1651
+ const candidates = (await this.credentialRepository.listProviderCredentials({ ...input, provider }))
1652
+ .sort((left, right) => (right.expiresAt?.getTime() ?? 0) - (left.expiresAt?.getTime() ?? 0)
1653
+ || (right.version ?? 0) - (left.version ?? 0));
1654
+ let refreshFailure;
1655
+ for (const existing of candidates) {
1656
+ if (existing.status !== 'active' || existing.offeringId !== offeringId)
1657
+ continue;
1658
+ let secret;
1659
+ try {
1660
+ secret = await this.vault.open({ webId: input.webId }, existing.credentialIri, provider, existing.encryptedSecret);
1661
+ }
1662
+ catch {
1663
+ // An unreadable credential cannot establish session identity.
1664
+ continue;
1665
+ }
1666
+ if (!sameImportedSession(provider, secret, imported.secret))
1667
+ continue;
1668
+ const importedExpired = (dateFrom(imported.secret.expiresAt)?.getTime() ?? Infinity) <= Date.now();
1669
+ const existingExpiresAt = dateFrom(secret.expiresAt)?.getTime() ?? existing.expiresAt?.getTime() ?? 0;
1670
+ const existingIsNewer = existingExpiresAt > (dateFrom(imported.secret.expiresAt)?.getTime() ?? 0);
1671
+ const isPreviousImport = typeof secret.importedSessionFingerprint === 'string'
1672
+ && secret.importedSessionFingerprint === importedSessionFingerprint(imported.secret);
1673
+ let nextSecret;
1674
+ try {
1675
+ nextSecret = await this.refreshImportedSecret(importedExpired && (existingIsNewer || isPreviousImport) ? secret : imported.secret, { ...input, provider, offeringId, credentialId: existing.id, expectedVersion: existing.version ?? 0 });
1676
+ }
1677
+ catch (error) {
1678
+ if (!(error instanceof Error) || error.message !== 'local_session_reauth_required')
1679
+ throw error;
1680
+ refreshFailure = error;
1681
+ continue;
1682
+ }
1683
+ nextSecret.importedSessionFingerprint = importedSessionFingerprint(imported.secret);
1684
+ const health = existing.enabled === false ? 'disabled' : 'healthy';
1685
+ const updated = await this.credentialRepository.updateCredential({
1686
+ ...input,
1687
+ provider,
1688
+ credentialId: existing.id,
1689
+ expectedVersion: existing.version,
1690
+ patch: {
1691
+ encryptedSecret: await this.vault.seal({ webId: input.webId }, existing.credentialIri, provider, nextSecret),
1692
+ authMode: imported.credentialAuthMode ?? 'local',
1693
+ expiresAt: dateFrom(nextSecret.expiresAt),
1694
+ scopes: scopeListFromSecret(nextSecret),
1695
+ reauthRequired: false,
1696
+ health,
1697
+ metadata: metadataWithoutUndefined({
1698
+ ...imported.metadata,
1699
+ ...existing.metadata,
1700
+ offeringId,
1701
+ accountId: nextSecret.accountId,
1702
+ authoritativeSubject: nextSecret.accountSubject,
1703
+ health,
1704
+ }),
1705
+ },
1706
+ });
1707
+ if (!updated)
1708
+ throw new Error('credential_version_conflict');
1709
+ return publicPoolCredentialSummary(updated);
1710
+ }
1711
+ if (refreshFailure)
1712
+ throw refreshFailure;
1713
+ }
1714
+ const { credentialId, credentialIri } = createPoolCredentialLocator(input.webId, input.deployment, provider);
1715
+ const nextSecret = imported
1716
+ ? await this.refreshImportedSecret(imported.secret, {
1717
+ ...input, provider, offeringId, credentialId, expectedVersion: 0,
1718
+ })
1719
+ : { type: 'local' };
1720
+ if (imported)
1721
+ nextSecret.importedSessionFingerprint = importedSessionFingerprint(imported.secret);
1722
+ const encryptedSecret = await this.vault.seal({ webId: input.webId }, credentialIri, provider, nextSecret);
1119
1723
  const created = await this.credentialRepository.createCredential({
1120
1724
  id: credentialId,
1121
1725
  credentialIri,
@@ -1130,8 +1734,12 @@ class ProviderConnectService {
1130
1734
  priority: input.priority ?? 100,
1131
1735
  enabled: true,
1132
1736
  health: 'healthy',
1737
+ expiresAt: dateFrom(nextSecret.expiresAt),
1738
+ scopes: scopeListFromSecret(nextSecret),
1133
1739
  metadata: metadataWithoutUndefined({
1134
1740
  ...imported?.metadata,
1741
+ accountId: nextSecret.accountId,
1742
+ authoritativeSubject: nextSecret.accountSubject,
1135
1743
  offeringId,
1136
1744
  priority: input.priority ?? 100,
1137
1745
  enabled: true,
@@ -1141,6 +1749,31 @@ class ProviderConnectService {
1141
1749
  }, { auth: input.auth });
1142
1750
  return publicPoolCredentialSummary(created);
1143
1751
  }
1752
+ async refreshImportedSecret(secret, input) {
1753
+ if ((dateFrom(secret.expiresAt)?.getTime() ?? Infinity) > Date.now()) {
1754
+ return { ...secret, ...metadataWithoutUndefined({ ...kimiAccountIdentityHint(input.provider, secret.accessToken) }) };
1755
+ }
1756
+ if (typeof secret.refreshToken !== 'string' || !secret.refreshToken) {
1757
+ throw new Error('local_session_missing_refresh_token');
1758
+ }
1759
+ let refreshed;
1760
+ try {
1761
+ refreshed = await this.refreshCallerOwned({ ...input, refreshToken: secret.refreshToken });
1762
+ }
1763
+ catch (error) {
1764
+ const message = error instanceof Error ? error.message : '';
1765
+ throw new Error(/^OAuth refresh failed: (?:invalid_grant|invalid_token)$/u.test(message)
1766
+ ? 'local_session_reauth_required' : 'local_session_refresh_failed');
1767
+ }
1768
+ if (refreshed.status !== 'completed' || !refreshed.oauthCredential?.accessToken) {
1769
+ throw new Error('local_session_refresh_failed');
1770
+ }
1771
+ const result = { ...secret, ...metadataWithoutUndefined({ ...refreshed.oauthCredential }) };
1772
+ if ((dateFrom(result.expiresAt)?.getTime() ?? Infinity) <= Date.now()) {
1773
+ throw new Error('local_session_refresh_failed');
1774
+ }
1775
+ return { ...result, ...metadataWithoutUndefined({ ...kimiAccountIdentityHint(input.provider, result.accessToken) }) };
1776
+ }
1144
1777
  async updateCredential(input) {
1145
1778
  if (!this.credentialRepository) {
1146
1779
  throw new Error('credential_pool_not_configured');
@@ -1252,21 +1885,24 @@ class ProviderConnectService {
1252
1885
  return offering.id;
1253
1886
  }
1254
1887
  completeApiKey(input) {
1255
- const adapter = this.requireAdapter(input.provider);
1888
+ const adapter = this.requireAdapter(input.provider, input.offeringId, 'browserAssistedApiKey');
1256
1889
  if (!adapter.completeApiKey) {
1257
1890
  throw new Error('Provider does not support API key Connect completion');
1258
1891
  }
1259
1892
  return adapter.completeApiKey(input);
1260
1893
  }
1261
- pollDevice(input) {
1262
- const adapter = this.requireAdapter(input.provider);
1263
- if (!adapter.pollDevice) {
1264
- throw new Error('Provider does not support device-code polling');
1894
+ async pollDevice(input) {
1895
+ if (input.mode) {
1896
+ const adapter = this.requireAdapter(input.provider, input.offeringId, input.mode);
1897
+ if (!adapter.pollDevice) {
1898
+ throw new Error('Provider does not support OAuth polling');
1899
+ }
1900
+ return adapter.pollDevice(input);
1265
1901
  }
1266
- return adapter.pollDevice(input);
1902
+ return this.runAttemptOperation(input, 'pollDevice', 'Provider does not support OAuth polling');
1267
1903
  }
1268
- status(input) {
1269
- const adapter = this.requireAdapter(input.provider);
1904
+ async status(input) {
1905
+ const adapter = await this.requireAdapterForAttemptStatus(input);
1270
1906
  if (!adapter.status) {
1271
1907
  throw new Error('Provider does not support Connect status');
1272
1908
  }
@@ -1276,17 +1912,13 @@ class ProviderConnectService {
1276
1912
  return this.refreshWithRetry(input, 2);
1277
1913
  }
1278
1914
  refreshCallerOwned(input) {
1279
- const adapter = this.requireAdapter(input.provider);
1915
+ const adapter = this.requireOAuthRefreshAdapter(input, 'refreshCallerOwned');
1280
1916
  if (!adapter.refreshCallerOwned) {
1281
1917
  throw new Error('Provider does not support caller-owned OAuth refresh');
1282
1918
  }
1283
1919
  return adapter.refreshCallerOwned(input);
1284
1920
  }
1285
1921
  async refreshWithRetry(input, remainingAttempts) {
1286
- const adapter = this.requireAdapter(input.provider);
1287
- if (!adapter.refresh) {
1288
- throw new Error('Provider does not support refresh');
1289
- }
1290
1922
  if (!this.credentialRepository || !this.vault) {
1291
1923
  throw new Error('CredentialVault and PodCredentialRepository are required for provider refresh');
1292
1924
  }
@@ -1294,6 +1926,10 @@ class ProviderConnectService {
1294
1926
  if (!current) {
1295
1927
  throw new Error('oauth_credential_not_found');
1296
1928
  }
1929
+ const adapter = this.requireOAuthRefreshAdapter(input, 'refresh', current);
1930
+ if (!adapter.refresh) {
1931
+ throw new Error('Provider does not support refresh');
1932
+ }
1297
1933
  const secret = await this.vault.open({ webId: input.webId }, current.credentialIri, normalizeProvider(input.provider), current.encryptedSecret);
1298
1934
  try {
1299
1935
  return await adapter.refresh(input, current, secret);
@@ -1309,12 +1945,64 @@ class ProviderConnectService {
1309
1945
  throw error;
1310
1946
  }
1311
1947
  }
1312
- disconnect(input) {
1313
- const adapter = this.requireAdapter(input.provider);
1948
+ async disconnect(input) {
1949
+ const adapter = await this.requireAdapterForDisconnect(input);
1314
1950
  if (!adapter.disconnect) {
1315
1951
  throw new Error('Provider does not support disconnect');
1316
1952
  }
1317
- return adapter.disconnect(input);
1953
+ return adapter.disconnect({
1954
+ ...input,
1955
+ offeringId: input.offeringId ?? adapter.offeringId,
1956
+ });
1957
+ }
1958
+ async cancel(input) {
1959
+ if (input.mode) {
1960
+ const adapter = this.requireAdapter(input.provider, input.offeringId, input.mode);
1961
+ if (!adapter.cancel) {
1962
+ throw new Error('Provider does not support Connect cancellation');
1963
+ }
1964
+ return adapter.cancel(input);
1965
+ }
1966
+ return this.runAttemptOperation(input, 'cancel', 'Provider does not support Connect cancellation');
1967
+ }
1968
+ async runAttemptOperation(input, operation, unsupportedMessage) {
1969
+ const candidates = this.findAdapterCandidates(input.provider, input.offeringId).filter((adapter) => adapter[operation]);
1970
+ let deferredError;
1971
+ for (const adapter of candidates) {
1972
+ try {
1973
+ return await adapter[operation](input);
1974
+ }
1975
+ catch (error) {
1976
+ if (!isAttemptBindingError(error)) {
1977
+ throw error;
1978
+ }
1979
+ deferredError ??= error;
1980
+ }
1981
+ }
1982
+ if (deferredError)
1983
+ throw deferredError;
1984
+ throw new Error(unsupportedMessage);
1985
+ }
1986
+ requireOAuthRefreshAdapter(input, operation, current) {
1987
+ if (input.mode) {
1988
+ return this.requireAdapter(input.provider, input.offeringId, input.mode);
1989
+ }
1990
+ const authorizationMethodId = input.authorizationMethodId ?? credentialAuthorizationMethodId(current);
1991
+ if (authorizationMethodId) {
1992
+ const adapter = this.findAdapterCandidates(input.provider, input.offeringId)
1993
+ .find((candidate) => candidate[operation] && candidate.authorizationMethodId === authorizationMethodId);
1994
+ if (adapter)
1995
+ return adapter;
1996
+ }
1997
+ if (operation === 'refreshCallerOwned' || current?.authMode === 'deviceCodeOAuth') {
1998
+ return this.requireAdapter(input.provider, input.offeringId ?? credentialOfferingId(current), 'deviceCodeOAuth');
1999
+ }
2000
+ const candidates = this.findAdapterCandidates(input.provider, input.offeringId)
2001
+ .filter((adapter) => adapter[operation] && (adapter.mode === 'deviceCodeOAuth' || adapter.mode === 'authorizationCodeOAuth'));
2002
+ if (candidates.length === 1) {
2003
+ return candidates[0];
2004
+ }
2005
+ return this.requireAdapter(input.provider, input.offeringId, 'deviceCodeOAuth');
1318
2006
  }
1319
2007
  async getRefreshCredential(input) {
1320
2008
  if (!this.credentialRepository) {
@@ -1326,18 +2014,109 @@ class ProviderConnectService {
1326
2014
  credentialId: input.credentialId,
1327
2015
  })
1328
2016
  : (await this.credentialRepository.listProviderCredentials(input))
1329
- .find(isOAuthProviderCredential);
1330
- return credential && isOAuthProviderCredential(credential) ? credential : undefined;
2017
+ .find((record) => isOAuthProviderCredential(record, input.offeringId));
2018
+ return credential && isOAuthProviderCredential(credential, input.offeringId) ? credential : undefined;
1331
2019
  }
1332
- requireAdapter(provider) {
1333
- const adapter = this.adapters.get(normalizeProvider(provider));
2020
+ requireAdapter(provider, offeringId, mode) {
2021
+ const adapter = this.findAdapter(provider, offeringId, mode);
1334
2022
  if (!adapter) {
1335
2023
  throw new Error(`No Connect adapter registered for ${provider}`);
1336
2024
  }
1337
2025
  return adapter;
1338
2026
  }
2027
+ async requireAdapterForAttemptStatus(input) {
2028
+ const candidates = this.findAdapterCandidates(input.provider, input.offeringId).filter((adapter) => adapter.status);
2029
+ let deferredError;
2030
+ for (const adapter of candidates) {
2031
+ try {
2032
+ await adapter.status(input);
2033
+ return adapter;
2034
+ }
2035
+ catch (error) {
2036
+ if (!isAttemptBindingError(error)) {
2037
+ throw error;
2038
+ }
2039
+ deferredError ??= error;
2040
+ }
2041
+ }
2042
+ if (deferredError)
2043
+ throw deferredError;
2044
+ throw new Error(`No Connect adapter registered for ${input.provider}`);
2045
+ }
2046
+ async requireAdapterForDisconnect(input) {
2047
+ if (input.credentialId && this.credentialRepository) {
2048
+ const credential = await this.credentialRepository.getCredentialById({
2049
+ webId: input.webId,
2050
+ provider: normalizeProvider(input.provider),
2051
+ deployment: input.deployment,
2052
+ credentialId: input.credentialId,
2053
+ auth: input.auth,
2054
+ });
2055
+ if (credential) {
2056
+ const mode = credential.authMode === 'apiKey'
2057
+ ? 'browserAssistedApiKey'
2058
+ : credential.authMode === 'deviceCodeOAuth'
2059
+ ? 'deviceCodeOAuth'
2060
+ : undefined;
2061
+ return this.requireAdapter(input.provider, input.offeringId ?? credentialOfferingId(credential), mode);
2062
+ }
2063
+ }
2064
+ return this.requireAdapter(input.provider, input.offeringId);
2065
+ }
2066
+ findAdapter(provider, offeringId, mode) {
2067
+ const exact = this.adapters.get(connectAdapterKey(provider, offeringId, mode));
2068
+ if (exact)
2069
+ return exact;
2070
+ const candidates = this.findAdapterCandidates(provider, offeringId, mode);
2071
+ return candidates.length === 1 ? candidates[0] : undefined;
2072
+ }
2073
+ findAdapterCandidates(provider, offeringId, mode) {
2074
+ const normalizedProvider = normalizeProvider(provider);
2075
+ const normalizedOffering = offeringId ? normalizeProvider(offeringId) : undefined;
2076
+ return [...this.adapters.values()].filter((adapter) => {
2077
+ if (normalizeProvider(adapter.provider) !== normalizedProvider)
2078
+ return false;
2079
+ if (mode && adapter.mode && adapter.mode !== mode)
2080
+ return false;
2081
+ if ((mode === 'deviceCodeOAuth' || mode === 'authorizationCodeOAuth') && !adapter.mode)
2082
+ return false;
2083
+ if (!normalizedOffering)
2084
+ return true;
2085
+ if (normalizeProvider(adapter.offeringId ?? '') === normalizedOffering)
2086
+ return true;
2087
+ return !adapter.offeringId
2088
+ && (!adapter.mode || adapter.mode === 'browserAssistedApiKey')
2089
+ && (mode === undefined || mode === 'browserAssistedApiKey');
2090
+ });
2091
+ }
2092
+ requireConnectOffering(provider, offeringId, mode) {
2093
+ if (offeringId)
2094
+ return offeringId;
2095
+ const product = this.registry.getProduct(provider);
2096
+ const candidates = product?.offerings.filter((offering) => this.offeringSupportsConnectMode(provider, offering.id, mode)) ?? [];
2097
+ return candidates.length === 1 ? candidates[0].id : undefined;
2098
+ }
2099
+ offeringSupportsConnectMode(provider, offeringId, mode) {
2100
+ if (!offeringId)
2101
+ return false;
2102
+ const offering = this.registry.getOffering(provider, offeringId);
2103
+ return Boolean(offering?.authorizationMethods?.some((method) => method.connectMode === mode && method.lifecycle === 'active'));
2104
+ }
2105
+ authorizationMethodUnavailableReason(provider, offeringId, method) {
2106
+ if (method.lifecycle === 'unavailable') {
2107
+ return method.reason ?? 'authorization_method_unavailable';
2108
+ }
2109
+ if (method.connectMode && !this.findAdapter(provider, offeringId, method.connectMode)) {
2110
+ return 'authorization_adapter_unavailable';
2111
+ }
2112
+ if (method.id === 'local-session-import' && !this.localSessionImporters.has(localSessionImporterKey(provider, offeringId))) {
2113
+ return 'local_session_importer_unavailable';
2114
+ }
2115
+ return undefined;
2116
+ }
1339
2117
  }
1340
2118
  exports.ProviderConnectService = ProviderConnectService;
2119
+ ProviderConnectService.localImportLocks = new Map();
1341
2120
  function token(randomBytes) {
1342
2121
  return randomBytes(32).toString('base64url');
1343
2122
  }
@@ -1348,6 +2127,7 @@ function publicOfferingSummary(offering) {
1348
2127
  kind: offering.kind,
1349
2128
  lifecycle: offering.lifecycle,
1350
2129
  authModes: offering.authModes,
2130
+ authorizationMethods: offering.authorizationMethods,
1351
2131
  runtimeProviderIds: offering.runtimeProviderIds,
1352
2132
  productLabel: offering.productLabel,
1353
2133
  credentialPrefixHints: offering.credentialPrefixHints,
@@ -1382,18 +2162,23 @@ function publicPoolCredentialSummary(record) {
1382
2162
  function publicAuthMode(authMode) {
1383
2163
  return authMode === 'deviceCodeOAuth' ? 'deviceCode' : authMode;
1384
2164
  }
1385
- function isOAuthProviderCredential(record) {
2165
+ function isOAuthProviderCredential(record, offeringId) {
1386
2166
  return record.status === 'active'
1387
2167
  && record.authMode === 'deviceCodeOAuth'
1388
- && credentialOfferingId(record) === 'official-subscription';
1389
- }
1390
- function isKimiOAuthCredential(record) {
1391
- return normalizeProvider(record.provider) === 'kimi' && isOAuthProviderCredential(record);
2168
+ && (!offeringId || credentialOfferingId(record) === offeringId);
1392
2169
  }
1393
2170
  function credentialOfferingId(record) {
2171
+ if (!record)
2172
+ return undefined;
1394
2173
  const metadata = metadataFromRowValue(record.metadata) ?? {};
1395
2174
  return record.offeringId ?? stringMetadata(metadata, 'offeringId');
1396
2175
  }
2176
+ function credentialAuthorizationMethodId(record) {
2177
+ if (!record)
2178
+ return undefined;
2179
+ const metadata = metadataFromRowValue(record.metadata) ?? {};
2180
+ return stringMetadata(metadata, 'authorizationMethodId');
2181
+ }
1397
2182
  function publicCredentialHealth(record) {
1398
2183
  const metadata = metadataFromRowValue(record.metadata) ?? {};
1399
2184
  const health = record.health ?? stringMetadata(metadata, 'health');
@@ -1470,6 +2255,54 @@ function modelIdsFromMetadata(metadata) {
1470
2255
  }
1471
2256
  return [...ids];
1472
2257
  }
2258
+ function importedSessionFingerprint(secret) {
2259
+ if (typeof secret.accessToken !== 'string' || !secret.accessToken)
2260
+ return undefined;
2261
+ return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify([
2262
+ secret.accountId, secret.accountSubject, secret.accessToken, secret.refreshToken,
2263
+ ])).digest('hex');
2264
+ }
2265
+ function kimiAccountIdentityHint(provider, accessToken) {
2266
+ if (provider !== 'kimi' || typeof accessToken !== 'string' || jwtClaim(accessToken, 'iss') !== 'kimi-auth') {
2267
+ return undefined;
2268
+ }
2269
+ // These unverified claims are comparison hints within an already authorized owner's
2270
+ // credential pool. They must never authenticate a caller or grant Pod access.
2271
+ const accountId = jwtClaim(accessToken, 'user_id');
2272
+ const accountSubject = jwtClaim(accessToken, 'sub');
2273
+ return {
2274
+ accountId: accountId ? `kimi-auth:${accountId}` : undefined,
2275
+ accountSubject: accountSubject ? `kimi-auth:${accountSubject}` : undefined,
2276
+ };
2277
+ }
2278
+ function sameImportedSession(provider, existing, imported) {
2279
+ const identityFields = ['accountId', 'accountSubject'];
2280
+ const hasValue = (value) => typeof value === 'string' && value.trim().length > 0;
2281
+ if (provider === 'kimi') {
2282
+ const existingIssuer = jwtClaim(stringFrom(existing.accessToken), 'iss');
2283
+ const importedIssuer = jwtClaim(stringFrom(imported.accessToken), 'iss');
2284
+ if (existingIssuer && importedIssuer && existingIssuer !== importedIssuer)
2285
+ return false;
2286
+ }
2287
+ // A shared token must not override an explicit account identity conflict.
2288
+ if (identityFields.some((field) => hasValue(existing[field]) && hasValue(imported[field])
2289
+ && existing[field] !== imported[field]))
2290
+ return false;
2291
+ const existingIdentity = kimiAccountIdentityHint(provider, existing.accessToken);
2292
+ const importedIdentity = kimiAccountIdentityHint(provider, imported.accessToken);
2293
+ if (existingIdentity && importedIdentity) {
2294
+ if (identityFields.some((field) => hasValue(existingIdentity[field]) && hasValue(importedIdentity[field])
2295
+ && existingIdentity[field] !== importedIdentity[field]))
2296
+ return false;
2297
+ if (identityFields.some((field) => hasValue(existingIdentity[field])
2298
+ && existingIdentity[field] === importedIdentity[field]))
2299
+ return true;
2300
+ }
2301
+ if (existing.importedSessionFingerprint === importedSessionFingerprint(imported)
2302
+ && typeof existing.importedSessionFingerprint === 'string')
2303
+ return true;
2304
+ return [...identityFields, 'accessToken', 'refreshToken'].some((field) => hasValue(existing[field]) && existing[field] === imported[field]);
2305
+ }
1473
2306
  function createPoolCredentialLocator(webId, deployment, provider) {
1474
2307
  const key = `${deployment}-${provider}-${(0, node_crypto_1.randomUUID)()}`;
1475
2308
  return {
@@ -2075,6 +2908,8 @@ function signAttempt(attempt, secret) {
2075
2908
  deployment: attempt.deployment,
2076
2909
  webId: attempt.webId,
2077
2910
  mode: attempt.mode,
2911
+ offeringId: attempt.offeringId,
2912
+ authorizationMethodId: attempt.authorizationMethodId,
2078
2913
  state: attempt.state,
2079
2914
  expiresAt: attempt.expiresAt.toISOString(),
2080
2915
  }))
@@ -2219,7 +3054,14 @@ function stringFrom(value) {
2219
3054
  return typeof value === 'string' ? value : '';
2220
3055
  }
2221
3056
  function numberFrom(value, fallback) {
2222
- return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
3057
+ if (typeof value === 'number' && Number.isFinite(value)) {
3058
+ return value;
3059
+ }
3060
+ if (typeof value === 'string' && value.trim()) {
3061
+ const parsed = Number(value);
3062
+ return Number.isFinite(parsed) ? parsed : fallback;
3063
+ }
3064
+ return fallback;
2223
3065
  }
2224
3066
  function expiresAtFrom(expiresIn, now) {
2225
3067
  if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn)) {
@@ -2227,16 +3069,26 @@ function expiresAtFrom(expiresIn, now) {
2227
3069
  }
2228
3070
  return new Date(now.getTime() + expiresIn * 1000);
2229
3071
  }
2230
- function oneTimeOAuthCredential(body, now, expectedVersion) {
3072
+ function oneTimeOAuthCredential(body, now, expectedVersion, integration, fallbackRefreshToken, authorizationMethodId) {
2231
3073
  requireStringField(body, 'access_token');
2232
- requireStringField(body, 'refresh_token');
3074
+ const refreshToken = stringFrom(body.refresh_token) || fallbackRefreshToken;
3075
+ if (!refreshToken) {
3076
+ throw new Error('Provider response missing required field: refresh_token');
3077
+ }
3078
+ const identityHint = kimiAccountIdentityHint(integration?.provider, body.access_token);
2233
3079
  return {
2234
3080
  accessToken: stringFrom(body.access_token),
2235
- refreshToken: stringFrom(body.refresh_token),
3081
+ refreshToken,
2236
3082
  expiresAt: expiresAtFrom(body.expires_in, now)?.toISOString(),
2237
3083
  scope: stringFrom(body.scope),
2238
3084
  idToken: stringFrom(body.id_token),
2239
- accountSubject: decodeJwtSubject(stringFrom(body.id_token)),
3085
+ accountSubject: identityHint?.accountSubject ?? decodeJwtSubject(stringFrom(body.id_token)),
3086
+ accountLabel: integration?.accountLabel,
3087
+ accountId: integration?.accountId
3088
+ ?? identityHint?.accountId
3089
+ ?? (stringFrom(body.account_id) || jwtClaim(stringFrom(body.id_token), integration?.protocol.accountIdClaim)),
3090
+ offeringId: integration?.offeringId,
3091
+ authorizationMethodId,
2240
3092
  expectedVersion,
2241
3093
  };
2242
3094
  }
@@ -2261,26 +3113,153 @@ const SAFE_PROVIDER_ERROR_CODES = new Set([
2261
3113
  'invalid_grant',
2262
3114
  'invalid_client',
2263
3115
  ]);
2264
- function pendingResult(input, status, intervalSeconds) {
3116
+ function pendingResult(input, status, intervalSeconds, provider = normalizeProvider(input.provider), offeringId = input.offeringId) {
3117
+ return oauthPendingResult(input, status, provider, offeringId, 'deviceCodeOAuth', intervalSeconds);
3118
+ }
3119
+ function oauthPendingResult(input, status, provider, offeringId, mode, intervalSeconds) {
2265
3120
  return {
2266
- mode: 'deviceCodeOAuth',
3121
+ mode,
2267
3122
  status,
2268
- provider: 'kimi',
3123
+ provider,
3124
+ offeringId,
2269
3125
  deployment: input.deployment,
2270
3126
  attemptId: input.attemptId,
2271
3127
  intervalSeconds,
2272
3128
  };
2273
3129
  }
2274
- function assertKimiEndpoint(endpoint, pathname) {
2275
- const url = new URL(endpoint);
2276
- if (url.origin !== 'https://auth.kimi.com'
2277
- || url.pathname !== pathname
3130
+ function connectAdapterKey(provider, offeringId, mode) {
3131
+ return `${normalizeProvider(provider)}:${normalizeProvider(offeringId ?? '')}:${mode ?? ''}`;
3132
+ }
3133
+ function isAttemptBindingError(error) {
3134
+ if (!(error instanceof Error))
3135
+ return false;
3136
+ return error.message === 'Connect attempt not found'
3137
+ || error.message.startsWith('Connect attempt is bound to a different ')
3138
+ || error.message === 'Connect attempt mode mismatch';
3139
+ }
3140
+ function isTerminalAttemptError(error) {
3141
+ if (!(error instanceof Error))
3142
+ return false;
3143
+ return error.message === 'Connect attempt not found'
3144
+ || error.message === 'Connect attempt expired'
3145
+ || error.message === 'Connect attempt already consumed';
3146
+ }
3147
+ function assertTrustedEndpoint(descriptor) {
3148
+ const url = new URL(descriptor.endpoint);
3149
+ if (url.protocol !== 'https:'
2278
3150
  || url.search
2279
3151
  || url.hash
2280
3152
  || url.username
2281
3153
  || url.password) {
2282
- throw new Error('Kimi Connect endpoint is not allowlisted');
3154
+ throw new Error('Device Connect endpoint is not allowlisted');
3155
+ }
3156
+ }
3157
+ function deviceCodeBeginRequest(protocol, clientId, challenge) {
3158
+ if (protocol.begin.codec === 'oauthDeviceCodePkce' && !challenge) {
3159
+ throw new Error('Device authorization PKCE challenge is required');
3160
+ }
3161
+ if (protocol.begin.codec === 'oauthDeviceCode' || protocol.begin.codec === 'oauthDeviceCodePkce') {
3162
+ return {
3163
+ headers: withProtocolHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }, protocol.begin.headers),
3164
+ body: new URLSearchParams(metadataWithoutUndefined({
3165
+ client_id: clientId,
3166
+ code_challenge: protocol.begin.codec === 'oauthDeviceCodePkce' ? challenge : undefined,
3167
+ code_challenge_method: protocol.begin.codec === 'oauthDeviceCodePkce' ? 'S256' : undefined,
3168
+ })),
3169
+ };
3170
+ }
3171
+ return {
3172
+ headers: withProtocolHeaders({ 'Content-Type': 'application/json' }, protocol.begin.headers),
3173
+ body: JSON.stringify({ client_id: clientId }),
3174
+ };
3175
+ }
3176
+ function deviceCodePollRequest(protocol, clientId, attempt) {
3177
+ if (protocol.poll.codec === 'oauthDeviceCode' || protocol.poll.codec === 'oauthDeviceCodePkce') {
3178
+ return {
3179
+ headers: withProtocolHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }, protocol.poll.headers),
3180
+ body: new URLSearchParams(metadataWithoutUndefined({
3181
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
3182
+ device_code: attempt.deviceCode ?? '',
3183
+ client_id: clientId,
3184
+ code_verifier: protocol.poll.codec === 'oauthDeviceCodePkce' ? attempt.codeVerifier ?? '' : undefined,
3185
+ })),
3186
+ };
3187
+ }
3188
+ return {
3189
+ headers: withProtocolHeaders({ 'Content-Type': 'application/json' }, protocol.poll.headers),
3190
+ body: JSON.stringify(metadataWithoutUndefined({
3191
+ [deviceCodePollField(protocol)]: attempt.deviceCode ?? '',
3192
+ user_code: attempt.userCode,
3193
+ })),
3194
+ };
3195
+ }
3196
+ function deviceCodePollField(protocol) {
3197
+ return protocol.poll.deviceCodeField
3198
+ ?? protocol.begin.deviceCodeField
3199
+ ?? (protocol.poll.codec === 'deviceCodeJson' ? 'device_auth_id' : 'device_code');
3200
+ }
3201
+ async function executeOAuthRefreshRequest(fetchImpl, request, refresh, clientId, refreshToken) {
3202
+ const response = await fetchImpl(refresh.endpoint, request({
3203
+ method: 'POST',
3204
+ headers: withProtocolHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }, refresh.headers),
3205
+ body: new URLSearchParams(metadataWithoutUndefined({
3206
+ grant_type: 'refresh_token',
3207
+ refresh_token: refreshToken,
3208
+ client_id: clientId,
3209
+ })),
3210
+ }));
3211
+ return { ok: response.ok, body: await safeJson(response) };
3212
+ }
3213
+ function withProtocolHeaders(base, extra) {
3214
+ return { ...base, ...(extra ?? {}) };
3215
+ }
3216
+ function verifiedVerificationUri(body, protocol) {
3217
+ const verificationUri = stringFrom(body[protocol.begin.verificationUriField ?? 'verification_uri'])
3218
+ || protocol.defaultVerificationUri;
3219
+ const verificationUriComplete = stringFrom(body[protocol.begin.verificationUriCompleteField ?? 'verification_uri_complete'])
3220
+ || verificationUri;
3221
+ const origins = protocol.verificationUriOrigins ?? [];
3222
+ for (const candidate of [verificationUri, verificationUriComplete]) {
3223
+ if (!candidate || origins.length === 0)
3224
+ continue;
3225
+ const url = new URL(candidate);
3226
+ if (!origins.includes(url.origin)) {
3227
+ throw new Error('Provider verification URI is not allowlisted');
3228
+ }
3229
+ }
3230
+ return { verificationUri, verificationUriComplete };
3231
+ }
3232
+ function deviceCodeExpiresAt(body, protocol, now) {
3233
+ const expiresAtField = protocol.begin.expiresAtField;
3234
+ const expiresAt = expiresAtField ? absoluteExpiresAtFrom(body[expiresAtField]) : undefined;
3235
+ if (expiresAt)
3236
+ return expiresAt;
3237
+ const expiresIn = numberFrom(body[protocol.begin.expiresInField ?? 'expires_in'], protocol.begin.defaultExpiresInSeconds ?? 300);
3238
+ return new Date(now.getTime() + expiresIn * 1000);
3239
+ }
3240
+ function absoluteExpiresAtFrom(value) {
3241
+ if (typeof value === 'number' && Number.isFinite(value)) {
3242
+ return new Date(value * 1000);
3243
+ }
3244
+ if (typeof value === 'string' && value.trim()) {
3245
+ const asNumber = Number(value);
3246
+ const parsed = Number.isFinite(asNumber) ? new Date(asNumber * 1000) : new Date(value);
3247
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
3248
+ }
3249
+ return undefined;
3250
+ }
3251
+ function requireProtocolStringField(body, field) {
3252
+ const fields = Array.isArray(field) ? field : [field];
3253
+ for (const candidate of fields) {
3254
+ const value = stringFrom(body[candidate]);
3255
+ if (value)
3256
+ return value;
2283
3257
  }
3258
+ throw new Error(`Provider response missing required field: ${fields[0]}`);
3259
+ }
3260
+ function requestTimeoutSignal(timeoutMs) {
3261
+ const timeoutFactory = AbortSignal.timeout;
3262
+ return typeof timeoutFactory === 'function' ? timeoutFactory(timeoutMs) : undefined;
2284
3263
  }
2285
3264
  function requireStringField(body, field) {
2286
3265
  if (typeof body[field] !== 'string' || !body[field].trim()) {
@@ -2303,6 +3282,34 @@ function decodeJwtSubject(idToken) {
2303
3282
  return undefined;
2304
3283
  }
2305
3284
  }
3285
+ function jwtClaim(idToken, claim) {
3286
+ if (!claim)
3287
+ return undefined;
3288
+ const payload = idToken.split('.')[1];
3289
+ if (!payload)
3290
+ return undefined;
3291
+ try {
3292
+ const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
3293
+ const path = Array.isArray(claim) ? claim : [claim];
3294
+ let value = decoded;
3295
+ for (const segment of path) {
3296
+ if (!value || typeof value !== 'object' || Array.isArray(value))
3297
+ return undefined;
3298
+ value = value[segment];
3299
+ }
3300
+ return typeof value === 'string' && value.trim() ? value : undefined;
3301
+ }
3302
+ catch {
3303
+ return undefined;
3304
+ }
3305
+ }
3306
+ function scopeListFromSecret(secret) {
3307
+ const scope = stringFrom(secret?.scope);
3308
+ if (!scope)
3309
+ return undefined;
3310
+ const scopes = scope.split(/\s+/u).map((value) => value.trim()).filter(Boolean);
3311
+ return scopes.length > 0 ? scopes : undefined;
3312
+ }
2306
3313
  function defaultOfferingFor(provider, authMode) {
2307
3314
  const normalized = normalizeProvider(provider);
2308
3315
  const product = providerProductFor(normalized);
@@ -2352,16 +3359,26 @@ function offeringMatchesAuthMode(offeringAuthModes, authMode) {
2352
3359
  return offeringAuthModes.includes('oauth') || offeringAuthModes.includes('deviceCode');
2353
3360
  }
2354
3361
  async function updateByCredentialIdAndVersion(params) {
2355
- const { db, credential, credentialId, expectedVersion, patch } = params;
2356
- const updated = await db
2357
- .update(credential)
2358
- .set({ ...patch })
2359
- .where((0, drizzle_solid_1.and)((0, drizzle_solid_1.eq)(credential.id, credentialId), (0, drizzle_solid_1.eq)(credential.keyVersion, expectedVersion)))
2360
- .returning()
2361
- .execute();
2362
- if (updated.length === 1) {
2363
- return updated[0];
2364
- }
2365
- return null;
3362
+ const { owner, db, credential, credentialId, expectedVersion, patch } = params;
3363
+ const key = JSON.stringify([owner, credentialId]);
3364
+ const previous = credentialUpdateLocks.get(key) ?? Promise.resolve();
3365
+ let release;
3366
+ const pending = new Promise((resolve) => { release = resolve; });
3367
+ credentialUpdateLocks.set(key, pending);
3368
+ await previous;
3369
+ try {
3370
+ // The exact API does not accept a version predicate. Serialize local writers
3371
+ // and recheck the exact row immediately before updating it.
3372
+ const current = await db.findById(credential, credentialId);
3373
+ if (!current || String(versionFromRow(current)) !== expectedVersion)
3374
+ return null;
3375
+ const { id: _id, '@id': _iri, ...values } = patch;
3376
+ return await db.updateById(credential, credentialId, values);
3377
+ }
3378
+ finally {
3379
+ release();
3380
+ if (credentialUpdateLocks.get(key) === pending)
3381
+ credentialUpdateLocks.delete(key);
3382
+ }
2366
3383
  }
2367
3384
  //# sourceMappingURL=index.js.map