@undefineds.co/xpod 0.4.14 → 0.4.15

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 (202) hide show
  1. package/config/cloud.json +0 -3
  2. package/config/local.json +0 -3
  3. package/config/xpod.base.json +0 -24
  4. package/dist/api/ai-config/AiConfigStore.d.ts +2 -2
  5. package/dist/api/ai-config/AiConfigStore.js +4 -1
  6. package/dist/api/ai-gateway/auth/CallerPodAccess.d.ts +2 -5
  7. package/dist/api/ai-gateway/auth/CallerPodAccess.js +3 -42
  8. package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +2 -1
  9. package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +1 -1
  10. package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +3 -8
  11. package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +9 -32
  12. package/dist/api/ai-gateway/connect/index.d.ts +3 -3
  13. package/dist/api/ai-gateway/connect/index.js +9 -38
  14. package/dist/api/ai-gateway/errors.js +2 -1
  15. package/dist/api/ai-gateway/models/PodModelSelectionRepository.d.ts +3 -3
  16. package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +7 -11
  17. package/dist/api/ai-gateway/pod/HostedPodRoute.d.ts +27 -0
  18. package/dist/api/ai-gateway/pod/HostedPodRoute.js +47 -0
  19. package/dist/api/ai-gateway/pod/OwnerPodAccess.d.ts +79 -0
  20. package/dist/api/ai-gateway/pod/OwnerPodAccess.js +192 -0
  21. package/dist/api/ai-gateway/pod/PodInterfaceKeyStore.d.ts +52 -0
  22. package/dist/api/ai-gateway/pod/PodInterfaceKeyStore.js +61 -0
  23. package/dist/api/ai-gateway/quota/ProviderQuotaAdapter.d.ts +3 -3
  24. package/dist/api/ai-gateway/quota/ProviderQuotaAdapter.js +7 -32
  25. package/dist/api/auth/ClientCredentialsAuthenticator.js +5 -28
  26. package/dist/api/auth/TokenEndpointRoute.d.ts +18 -0
  27. package/dist/api/auth/TokenEndpointRoute.js +36 -0
  28. package/dist/api/chatkit/pod-store.d.ts +4 -4
  29. package/dist/api/chatkit/pod-store.js +24 -95
  30. package/dist/api/container/common.js +36 -23
  31. package/dist/api/container/local.js +62 -29
  32. package/dist/api/container/routes.js +39 -7
  33. package/dist/api/container/types.d.ts +2 -2
  34. package/dist/api/handlers/AiGatewayManagementHandler.d.ts +6 -0
  35. package/dist/api/handlers/AiGatewayManagementHandler.js +17 -4
  36. package/dist/api/handlers/NetworkSettingsHandler.d.ts +7 -0
  37. package/dist/api/handlers/NetworkSettingsHandler.js +7 -0
  38. package/dist/api/handlers/PodSettingsHandler.d.ts +3 -3
  39. package/dist/api/handlers/PodSettingsHandler.js +3 -3
  40. package/dist/api/handlers/ProvisionHandler.d.ts +29 -0
  41. package/dist/api/handlers/ProvisionHandler.js +46 -0
  42. package/dist/api/handlers/ReachabilityHandler.js +3 -1
  43. package/dist/api/runtime.d.ts +9 -0
  44. package/dist/api/runtime.js +16 -3
  45. package/dist/cli/commands/start.d.ts +9 -2
  46. package/dist/cli/commands/start.js +15 -17
  47. package/dist/components/components.jsonld +1 -1
  48. package/dist/components/context.jsonld +5 -42
  49. package/dist/edge/EdgeNodeAgent.d.ts +10 -0
  50. package/dist/edge/EdgeNodeAgent.js +55 -0
  51. package/dist/edge/EdgeNodeAgent.jsonld +8 -0
  52. package/dist/edge/reachability/ReachabilitySessionService.js +4 -1
  53. package/dist/edge/reachability/RouteSetBuilder.d.ts +18 -1
  54. package/dist/edge/reachability/RouteSetBuilder.js +47 -0
  55. package/dist/edge/reachability/RouteSetBuilder.jsonld +29 -0
  56. package/dist/edge/reachability/types.d.ts +8 -2
  57. package/dist/http/SubgraphSparqlHttpHandler.d.ts +1 -25
  58. package/dist/http/SubgraphSparqlHttpHandler.js +0 -100
  59. package/dist/http/SubgraphSparqlHttpHandler.jsonld +0 -8
  60. package/dist/identity/drizzle/PodInterfaceKeyRepository.d.ts +33 -0
  61. package/dist/identity/drizzle/PodInterfaceKeyRepository.js +78 -0
  62. package/dist/identity/drizzle/db.d.ts +7 -0
  63. package/dist/identity/drizzle/db.js +30 -0
  64. package/dist/identity/drizzle/schema.pg.d.ts +9 -0
  65. package/dist/identity/drizzle/schema.pg.js +16 -1
  66. package/dist/identity/drizzle/schema.sqlite.d.ts +106 -0
  67. package/dist/identity/drizzle/schema.sqlite.js +16 -1
  68. package/dist/index.d.ts +1 -2
  69. package/dist/index.js +3 -5
  70. package/dist/runtime/GatewayAdminProxyAuth.d.ts +1 -16
  71. package/dist/runtime/GatewayAdminProxyAuth.js +2 -71
  72. package/dist/runtime/Proxy.d.ts +10 -1
  73. package/dist/runtime/Proxy.js +95 -28
  74. package/dist/runtime/bootstrap.d.ts +10 -0
  75. package/dist/runtime/bootstrap.js +24 -0
  76. package/dist/runtime/host/node/NodeRuntimeHost.js +5 -3
  77. package/dist/runtime/index.d.ts +1 -1
  78. package/dist/runtime/index.js +2 -3
  79. package/dist/runtime/js-runtime.js +10 -3
  80. package/dist/runtime/lifecycle.js +5 -4
  81. package/dist/runtime/port-finder.d.ts +9 -13
  82. package/dist/runtime/port-finder.js +18 -37
  83. package/dist/tunnel/AutoTunnelProvider.d.ts +56 -0
  84. package/dist/tunnel/AutoTunnelProvider.js +181 -0
  85. package/dist/tunnel/index.d.ts +1 -0
  86. package/dist/tunnel/index.js +3 -1
  87. package/node_modules/@undefineds.co/ai-connections/dist/error-wording.js +7 -2
  88. package/node_modules/@undefineds.co/solid-sdk/dist/access-route.cjs +230 -0
  89. package/node_modules/@undefineds.co/solid-sdk/dist/access-route.d.ts +76 -0
  90. package/node_modules/@undefineds.co/solid-sdk/dist/access-route.js +115 -0
  91. package/node_modules/@undefineds.co/solid-sdk/dist/index.cjs +89 -0
  92. package/node_modules/@undefineds.co/solid-sdk/dist/index.d.ts +1 -0
  93. package/node_modules/@undefineds.co/solid-sdk/dist/index.js +1 -0
  94. package/node_modules/@undefineds.co/solid-sdk/package.json +6 -1
  95. package/package.json +2 -2
  96. package/static/app/assets/inrupt-smoke.js +23 -23
  97. package/static/auth-callback/assets/{AiConfigContext-DCXpjzpj.js → AiConfigContext-Bx0HCAW5.js} +1 -1
  98. package/static/auth-callback/assets/{AiConfigPage-fe-xMIXa.js → AiConfigPage-ChQImMbm.js} +1 -1
  99. package/static/auth-callback/assets/{DocumentProcessingPanel-BGnkfMuU.js → DocumentProcessingPanel-D830H4SH.js} +1 -1
  100. package/static/auth-callback/assets/{IndexLifecyclePanel-42Jups0j.js → IndexLifecyclePanel-CnlkjMF8.js} +1 -1
  101. package/static/auth-callback/assets/{IndexSubjectPanel-CYyLOlQU.js → IndexSubjectPanel-CdEfg_CO.js} +1 -1
  102. package/static/auth-callback/assets/{ModelAssignmentsPanel-BOvUH4yr.js → ModelAssignmentsPanel-D-8A8dZx.js} +1 -1
  103. package/static/auth-callback/assets/{ModelsPage-DwyITxt7.js → ModelsPage-Cim7cubp.js} +3 -3
  104. package/static/auth-callback/assets/{NetworkPage-CG9RelJA.js → NetworkPage-CfK3x-pb.js} +2 -2
  105. package/static/auth-callback/assets/{PaneListHeader-yvd5ohs6.js → PaneListHeader-Bf_kEd4U.js} +1 -1
  106. package/static/auth-callback/assets/{SearchIndexingPanel-2_a6ezzj.js → SearchIndexingPanel-4kD4RBEQ.js} +1 -1
  107. package/static/auth-callback/assets/{StatusSubjectPanel-8qtVxnzC.js → StatusSubjectPanel-Bw9AiTSf.js} +1 -1
  108. package/static/auth-callback/assets/{StatusWorkspace-CBdRahCF.js → StatusWorkspace-C-301H-0.js} +1 -1
  109. package/static/auth-callback/assets/{SystemSettingsPage-BZJ6vWkg.js → SystemSettingsPage-BbWn-eG6.js} +1 -1
  110. package/static/auth-callback/assets/{SystemSettingsSubjectPanel-xm08UhXP.js → SystemSettingsSubjectPanel-DA4wvNSm.js} +1 -1
  111. package/static/auth-callback/assets/{XpodAiConnectionsPodStore-V6vgGfuw.js → XpodAiConnectionsPodStore-CohdyLoX.js} +1 -1
  112. package/static/auth-callback/assets/{activity-CeM7xly8.js → activity-CCsb9Oam.js} +1 -1
  113. package/static/auth-callback/assets/ai-connections-DexTPyfC.js +1 -0
  114. package/static/auth-callback/assets/{auth-callback-DTG8cSj7.js → auth-callback-Ds-c62W3.js} +5 -5
  115. package/static/auth-callback/assets/{badge-qJo2Zr4d.js → badge-CQm5OyzR.js} +1 -1
  116. package/static/auth-callback/assets/{collection-runtime-Bztsz31a.js → collection-runtime-DlAuU5TW.js} +1 -1
  117. package/static/auth-callback/assets/{dialog-D-h6x5x0.js → dialog-CnGzg5Ff.js} +1 -1
  118. package/static/auth-callback/assets/{download-C2ir4lmR.js → download-DwMONJvm.js} +1 -1
  119. package/static/auth-callback/assets/{external-link-THLDL4On.js → external-link-Bu5AGqCO.js} +1 -1
  120. package/static/auth-callback/assets/{index-BlVZb9A7.js → index-B0T-1YO8.js} +1 -1
  121. package/static/auth-callback/assets/{index-BRpv4yO3.js → index-C8QZf_4W.js} +1 -1
  122. package/static/auth-callback/assets/{index-D983R1FV.js → index-DwIlrIDG.js} +1 -1
  123. package/static/auth-callback/assets/{index-browser-3FvR0aaf.js → index-browser-lRsMcP_7.js} +1 -1
  124. package/static/auth-callback/assets/{key-round-C9U5j1Ly.js → key-round-BDMtU57P.js} +1 -1
  125. package/static/auth-callback/assets/{rotate-ccw-DP45cD4o.js → rotate-ccw-CoKIGCsQ.js} +1 -1
  126. package/static/auth-callback/assets/{skeleton-Dn4EiSCS.js → skeleton-CPoSWHDr.js} +1 -1
  127. package/static/auth-callback/assets/{waypoints-uqFQ3uEG.js → waypoints-BeYHxqHv.js} +1 -1
  128. package/static/auth-callback/assets/{workspace-layout-DLYK0Wwu.js → workspace-layout-xA5YxiK3.js} +1 -1
  129. package/static/auth-callback/auth-callback.html +1 -1
  130. package/static/dashboard/assets/{AiConfigContext-DJcMECNw.js → AiConfigContext-rWx4TyiH.js} +1 -1
  131. package/static/dashboard/assets/{AiConfigPage-BvE7dOTy.js → AiConfigPage-StVJpDFT.js} +1 -1
  132. package/static/dashboard/assets/{DocumentProcessingPanel-D9780krf.js → DocumentProcessingPanel-DJ5yul2O.js} +1 -1
  133. package/static/dashboard/assets/{IndexLifecyclePanel-SjsUcnbM.js → IndexLifecyclePanel-CpjtgezK.js} +1 -1
  134. package/static/dashboard/assets/{IndexSubjectPanel-C-ym2Tqi.js → IndexSubjectPanel-BVKcot1_.js} +1 -1
  135. package/static/dashboard/assets/{ModelAssignmentsPanel-BRjZ-arB.js → ModelAssignmentsPanel-BxVgWZPc.js} +1 -1
  136. package/static/dashboard/assets/{ModelsPage-BtTU6du3.js → ModelsPage-CECBwmeO.js} +3 -3
  137. package/static/dashboard/assets/{NetworkPage-CV9qpkDg.js → NetworkPage-zQgFscEW.js} +2 -2
  138. package/static/dashboard/assets/{PaneListHeader-KyPTTBsv.js → PaneListHeader-DIPe8pS6.js} +1 -1
  139. package/static/dashboard/assets/{SearchIndexingPanel-DolHGawg.js → SearchIndexingPanel-lalFJ3c8.js} +1 -1
  140. package/static/dashboard/assets/{StatusSubjectPanel-_YZvKiYi.js → StatusSubjectPanel-CpY_KU-G.js} +1 -1
  141. package/static/dashboard/assets/{StatusWorkspace-Dlbh_G40.js → StatusWorkspace-BwWCii3A.js} +1 -1
  142. package/static/dashboard/assets/{SystemSettingsPage-Be7QSIoz.js → SystemSettingsPage-BKZxX-_n.js} +1 -1
  143. package/static/dashboard/assets/{SystemSettingsSubjectPanel-DBp0gja7.js → SystemSettingsSubjectPanel-BZrqb2C5.js} +1 -1
  144. package/static/dashboard/assets/{XpodAiConnectionsPodStore-ClpSjzpP.js → XpodAiConnectionsPodStore-Bbpx9dTN.js} +1 -1
  145. package/static/dashboard/assets/{activity-lsNbTXJu.js → activity-BJ15TLVP.js} +1 -1
  146. package/static/dashboard/assets/ai-connections-GG6qkTJt.js +1 -0
  147. package/static/dashboard/assets/{badge-CVatKjaG.js → badge-BBX5FlaB.js} +1 -1
  148. package/static/dashboard/assets/{collection-runtime-Ec4UnY28.js → collection-runtime-UBbHBzdf.js} +1 -1
  149. package/static/dashboard/assets/{dashboard-BEzsRsvF.js → dashboard-7Y6BHyV9.js} +62 -62
  150. package/static/dashboard/assets/{dialog-BuyelQ4V.js → dialog-QrOJ-hB0.js} +1 -1
  151. package/static/dashboard/assets/{download-B4Nf678F.js → download-DK9x-VOP.js} +1 -1
  152. package/static/dashboard/assets/{external-link-PYv9CvKL.js → external-link-Cnkn87mJ.js} +1 -1
  153. package/static/dashboard/assets/{index-C9XPE-mI.js → index-B1z84it5.js} +1 -1
  154. package/static/dashboard/assets/{index-awr7ulur.js → index-DzjauX3N.js} +1 -1
  155. package/static/dashboard/assets/{index-Cx69bTNh.js → index-cIa8_efI.js} +1 -1
  156. package/static/dashboard/assets/{key-round-VsY80U1Z.js → key-round-BoS6UqKz.js} +1 -1
  157. package/static/dashboard/assets/{rotate-ccw-Cz1K4vE9.js → rotate-ccw-BeWV76i0.js} +1 -1
  158. package/static/dashboard/assets/{skeleton-BhNv_8tO.js → skeleton-CypcJL07.js} +1 -1
  159. package/static/dashboard/assets/{waypoints-DYS9XFYV.js → waypoints-zHOOqfhi.js} +1 -1
  160. package/static/dashboard/assets/{workspace-layout-OZJAFx5V.js → workspace-layout-DZ9JuxnM.js} +1 -1
  161. package/static/dashboard/dashboard.html +1 -1
  162. package/static/settings/assets/{AiConfigContext-B8JS3MXF.js → AiConfigContext-D3b1l_Kw.js} +1 -1
  163. package/static/settings/assets/{AiConfigPage-C3Zl8LUl.js → AiConfigPage-DYKw-atP.js} +1 -1
  164. package/static/settings/assets/{DocumentProcessingPanel-DpDZAQvf.js → DocumentProcessingPanel-DguRfsIV.js} +1 -1
  165. package/static/settings/assets/{IndexLifecyclePanel-Ec1XF88t.js → IndexLifecyclePanel-B85qJJA4.js} +1 -1
  166. package/static/settings/assets/{IndexSubjectPanel-BulT9BmO.js → IndexSubjectPanel-CsN7IxaV.js} +1 -1
  167. package/static/settings/assets/{ModelAssignmentsPanel-BcGSmYjb.js → ModelAssignmentsPanel-DEp9byI6.js} +1 -1
  168. package/static/settings/assets/{ModelsPage-CvTCKy1T.js → ModelsPage-CzTnPs_G.js} +3 -3
  169. package/static/settings/assets/{NetworkPage-DQF5u9Bn.js → NetworkPage-BDVnmUU0.js} +2 -2
  170. package/static/settings/assets/{PaneListHeader-D40hqcVl.js → PaneListHeader-Cpe39N76.js} +1 -1
  171. package/static/settings/assets/{SearchIndexingPanel-iCUPOx_0.js → SearchIndexingPanel-DA9w8lLL.js} +1 -1
  172. package/static/settings/assets/{StatusSubjectPanel-E--ZHSql.js → StatusSubjectPanel-BU0Aq0CS.js} +1 -1
  173. package/static/settings/assets/{StatusWorkspace-YjDAx1II.js → StatusWorkspace-D1AC1i5N.js} +1 -1
  174. package/static/settings/assets/{SystemSettingsPage-CDdV3mC9.js → SystemSettingsPage-ChKvOioO.js} +1 -1
  175. package/static/settings/assets/{SystemSettingsSubjectPanel-Br8WazDq.js → SystemSettingsSubjectPanel-DyTpOnD2.js} +1 -1
  176. package/static/settings/assets/{XpodAiConnectionsPodStore-CLr38P_Z.js → XpodAiConnectionsPodStore-VU5Bsz1a.js} +1 -1
  177. package/static/settings/assets/{activity-C5DAHHTE.js → activity-DGpLfsQB.js} +1 -1
  178. package/static/settings/assets/ai-connections-DexTPyfC.js +1 -0
  179. package/static/settings/assets/{badge-D8eIyxKt.js → badge-_hCrrWRP.js} +1 -1
  180. package/static/settings/assets/{collection-runtime-DLc9zil8.js → collection-runtime-E40TZ8Cl.js} +1 -1
  181. package/static/settings/assets/{dialog-CyuR7Va4.js → dialog-DTr2JkHn.js} +1 -1
  182. package/static/settings/assets/{download-c0oxZrCz.js → download-B_D21DDX.js} +1 -1
  183. package/static/settings/assets/{external-link-D3Z34fP_.js → external-link-BATYaM13.js} +1 -1
  184. package/static/settings/assets/{index-CSr3-2Cj.js → index-BAlAVi-J.js} +1 -1
  185. package/static/settings/assets/{index-ComidxAm.js → index-Ckh4h9jj.js} +1 -1
  186. package/static/settings/assets/{index-CKGbRFlc.js → index-DOiwLF4B.js} +1 -1
  187. package/static/settings/assets/{index-browser-CAscEbae.js → index-browser-DFFWN3Kr.js} +1 -1
  188. package/static/settings/assets/{key-round-DyjLmXuB.js → key-round-ZTVU_1O1.js} +1 -1
  189. package/static/settings/assets/{rotate-ccw-679HF_ec.js → rotate-ccw-DDdw8PTU.js} +1 -1
  190. package/static/settings/assets/{settings-DKknqu4C.js → settings-CyLnISOv.js} +62 -62
  191. package/static/settings/assets/{skeleton-CkopNkPB.js → skeleton-DEzA3hc2.js} +1 -1
  192. package/static/settings/assets/{waypoints-CrqfAftE.js → waypoints-BSdfNvEb.js} +1 -1
  193. package/static/settings/assets/{workspace-layout-UN6c4qTG.js → workspace-layout-BOQnl_3d.js} +1 -1
  194. package/static/settings/settings.html +1 -1
  195. package/dist/api/ai-gateway/pod/HostedPodDataAccess.d.ts +0 -31
  196. package/dist/api/ai-gateway/pod/HostedPodDataAccess.js +0 -330
  197. package/dist/http/InternalPodDataHttpHandler.d.ts +0 -57
  198. package/dist/http/InternalPodDataHttpHandler.js +0 -551
  199. package/dist/http/InternalPodDataHttpHandler.jsonld +0 -243
  200. package/static/auth-callback/assets/ai-connections-C6yaE6WQ.js +0 -1
  201. package/static/dashboard/assets/ai-connections-CXp5gd1J.js +0 -1
  202. package/static/settings/assets/ai-connections-C6yaE6WQ.js +0 -1
@@ -22,6 +22,7 @@ const store_1 = require("../runs/store");
22
22
  const schema_3 = require("../tasks/schema");
23
23
  const store_2 = require("../tasks/store");
24
24
  const TaskAuthBinding_1 = require("../tasks/TaskAuthBinding");
25
+ const OwnerPodAccess_1 = require("../ai-gateway/pod/OwnerPodAccess");
25
26
  const AuthContext_1 = require("../auth/AuthContext");
26
27
  const provider_1 = require("../../ai/schema/provider");
27
28
  const model_1 = require("../../ai/schema/model");
@@ -60,7 +61,7 @@ class PodChatKitStore {
60
61
  this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
61
62
  // Track recently created message IDs to avoid SELECT cache timing issues
62
63
  this.recentlyCreatedIds = new Set();
63
- this.tokenEndpoint = options.tokenEndpoint;
64
+ this.podAccess = options.podAccess;
64
65
  this.credentialSecretDecoder = options.credentialSecretDecoder ?? AiCredentialSecret_1.defaultAiCredentialSecretDecoder;
65
66
  this.deployment = options.deployment;
66
67
  this.serverGroupReconcilerService = options.serverGroupReconcilerService;
@@ -92,110 +93,38 @@ class PodChatKitStore {
92
93
  return context._cachedDb;
93
94
  }
94
95
  const auth = context.auth;
95
- if (!auth || !(0, AuthContext_1.isSolidAuth)(auth)) {
96
+ if (!auth || !(0, AuthContext_1.isSolidAuth)(auth) || !auth.webId) {
96
97
  this.logger.warn('No valid solid auth in context, cannot access Pod');
97
98
  return null;
98
99
  }
99
- // Preferred path: directly use caller's Solid access token.
100
- if (auth.accessToken && auth.webId) {
101
- try {
102
- if (auth.tokenType === 'DPoP') {
103
- this.logger.warn('Using DPoP access token without proof key; Pod access may fail if issuer enforces DPoP proof');
104
- }
105
- this.logger.info(`[getDb] Using access token path for webId: ${auth.webId}`);
106
- const authFetch = this.createAccessTokenFetch(auth.accessToken, auth.tokenType);
107
- const db = (0, drizzle_solid_1.drizzle)({ fetch: authFetch, info: { webId: auth.webId, isLoggedIn: true } }, { schema });
108
- this.logger.info(`Initializing tables for Pod (access token): ${auth.webId}`);
109
- try {
110
- await db.init(schema_1.Chat, schema_1.Thread, schema_1.Message, schema_2.Run, schema_2.RunStep, schema_3.Task, config_1.AIConfig, tables_1.Credential);
111
- this.logger.info('Tables initialized successfully');
112
- }
113
- catch (initError) {
114
- this.logger.error(`Failed to init tables: ${initError}`);
115
- }
116
- context._cachedDb = db;
117
- context._cachedFetch = authFetch;
118
- context._cachedWebId = auth.webId;
119
- this.ensurePodBaseUrlCache(context, db, auth.webId);
120
- return db;
121
- }
122
- catch (error) {
123
- this.logger.error(`Failed to get Pod db with access token: ${error}`);
124
- return null;
125
- }
126
- }
127
- if (!auth.clientId || !auth.clientSecret) {
128
- this.logger.warn('No accessToken and no valid client credentials in context, cannot access Pod');
129
- return null;
130
- }
131
- // Fallback path: exchange client credentials for an access token directly.
132
- this.logger.info(`[getDb] Using client credentials path for clientId: ${auth.clientId}`);
100
+ // One credential path for every caller: the owner's own Pod key, exchanged for a
101
+ // token this process can prove, or the caller's reusable token.
102
+ let podFetch;
133
103
  try {
134
- const token = await this.getClientCredentialsAccessToken(auth.clientId, auth.clientSecret);
135
- const webId = auth.webId ?? this.getWebId(context);
136
- if (!webId) {
137
- throw new Error('Missing webId for client credentials auth');
138
- }
139
- this.logger.info(`[getDb] Client credentials token acquired, webId: ${webId}`);
140
- const db = (0, drizzle_solid_1.drizzle)({ fetch: this.createAccessTokenFetch(token.accessToken, token.tokenType), info: { webId, isLoggedIn: true } }, { schema });
141
- const authFetch = this.createAccessTokenFetch(token.accessToken, token.tokenType);
142
- this.logger.info(`Initializing tables for Pod: ${webId}`);
143
- try {
144
- await db.init(schema_1.Chat, schema_1.Thread, schema_1.Message, schema_2.Run, schema_2.RunStep, schema_3.Task, config_1.AIConfig, tables_1.Credential);
145
- this.logger.info('Tables initialized successfully');
146
- }
147
- catch (initError) {
148
- this.logger.error(`Failed to init tables: ${initError}`);
149
- }
150
- context._cachedDb = db;
151
- context._cachedFetch = authFetch;
152
- context._cachedWebId = webId;
153
- this.ensurePodBaseUrlCache(context, db, webId);
154
- context._cachedAccessToken = token.accessToken;
155
- context._cachedTokenType = token.tokenType;
156
- return db;
104
+ podFetch = await this.podAccess?.getPodFetch(auth.webId, { auth });
157
105
  }
158
106
  catch (error) {
159
- this.logger.error(`Failed to get Pod db: ${error}`);
107
+ this.logger.error(`Failed to obtain Pod access for ${auth.webId}: ${error}`);
160
108
  return null;
161
109
  }
162
- }
163
- async getClientCredentialsAccessToken(clientId, clientSecret) {
164
- const response = await fetch(this.tokenEndpoint, {
165
- method: 'POST',
166
- headers: {
167
- 'Content-Type': 'application/x-www-form-urlencoded',
168
- },
169
- body: new URLSearchParams({
170
- grant_type: 'client_credentials',
171
- client_id: clientId,
172
- client_secret: clientSecret,
173
- }),
174
- });
175
- if (!response.ok) {
176
- throw new Error(`Client credentials token request failed: ${response.status} ${await response.text().catch(() => '')}`);
110
+ if (!podFetch) {
111
+ this.logger.warn(`No usable Pod credential for ${auth.webId}: ${(0, OwnerPodAccess_1.podAccessError)(auth.webId, auth)}`);
112
+ return null;
177
113
  }
178
- const token = await response.json();
179
- if (!token.access_token) {
180
- throw new Error(`Client credentials token response missing access_token: ${JSON.stringify(token)}`);
114
+ const db = (0, drizzle_solid_1.drizzle)({ fetch: podFetch, info: { webId: auth.webId, isLoggedIn: true } }, { schema });
115
+ this.logger.info(`Initializing tables for Pod: ${auth.webId}`);
116
+ try {
117
+ await db.init(schema_1.Chat, schema_1.Thread, schema_1.Message, schema_2.Run, schema_2.RunStep, schema_3.Task, config_1.AIConfig, tables_1.Credential);
118
+ this.logger.info('Tables initialized successfully');
181
119
  }
182
- return {
183
- accessToken: token.access_token,
184
- tokenType: token.token_type?.toUpperCase() === 'DPOP' ? 'DPoP' : 'Bearer',
185
- };
186
- }
187
- createAccessTokenFetch(accessToken, tokenType) {
188
- const scheme = tokenType ?? 'Bearer';
189
- return async (input, init) => {
190
- const headers = new Headers(init?.headers);
191
- if (!headers.has('Authorization')) {
192
- headers.set('Authorization', `${scheme} ${accessToken}`);
193
- }
194
- return fetch(input, {
195
- ...init,
196
- headers,
197
- });
198
- };
120
+ catch (initError) {
121
+ this.logger.error(`Failed to init tables: ${initError}`);
122
+ }
123
+ context._cachedDb = db;
124
+ context._cachedFetch = podFetch;
125
+ context._cachedWebId = auth.webId;
126
+ this.ensurePodBaseUrlCache(context, db, auth.webId);
127
+ return db;
199
128
  }
200
129
  /**
201
130
  * 从 context 获取 webId
@@ -27,7 +27,10 @@ const InvocationTokenCodec_1 = require("../ai-gateway/auth/InvocationTokenCodec"
27
27
  const GatewayApiKeyAuthenticator_1 = require("../ai-gateway/auth/GatewayApiKeyAuthenticator");
28
28
  const GatewayKeyLocatorCodec_1 = require("../ai-gateway/auth/GatewayKeyLocatorCodec");
29
29
  const PodGatewayAccessKeyRepository_1 = require("../ai-gateway/auth/PodGatewayAccessKeyRepository");
30
- const HostedPodDataAccess_1 = require("../ai-gateway/pod/HostedPodDataAccess");
30
+ const OwnerPodAccess_1 = require("../ai-gateway/pod/OwnerPodAccess");
31
+ const HostedPodRoute_1 = require("../ai-gateway/pod/HostedPodRoute");
32
+ const PodInterfaceKeyRepository_1 = require("../../identity/drizzle/PodInterfaceKeyRepository");
33
+ const PodInterfaceKeyStore_1 = require("../ai-gateway/pod/PodInterfaceKeyStore");
31
34
  const AiGatewayService_1 = require("../ai-gateway/AiGatewayService");
32
35
  const PlaintextCredentialVault_1 = require("../ai-gateway/credentials/PlaintextCredentialVault");
33
36
  const AiCredentialSecretDecoder_1 = require("../ai-gateway/credentials/AiCredentialSecretDecoder");
@@ -139,10 +142,21 @@ function registerCommonServices(container) {
139
142
  scopes: ['quota:write', 'usage:read', 'account:manage', 'network:read', 'network:write'],
140
143
  });
141
144
  }).singleton(),
142
- hostedPodDataAccess: (0, awilix_1.asFunction)(({ config }) => {
143
- return new HostedPodDataAccess_1.HostedPodDataAccess({
144
- cssBaseUrl: resolveHostedPodCssBaseUrl(),
145
- gatewayAdminProxyAuthSecret: config.gatewayAdminProxyAuthSecret,
145
+ ownerPodAccess: (0, awilix_1.asFunction)(({ config, db }) => {
146
+ return new OwnerPodAccess_1.OwnerPodAccess({
147
+ keys: new PodInterfaceKeyStore_1.PodInterfaceKeyStore({
148
+ repository: new PodInterfaceKeyRepository_1.PodInterfaceKeyRepository(db),
149
+ vault: credentialVaultForConfig(config),
150
+ }),
151
+ tokenEndpoint: config.cssTokenEndpoint,
152
+ publicBaseUrl: config.solidBaseUrl,
153
+ route: (0, HostedPodRoute_1.resolveHostedPodRoute)({
154
+ canonicalBaseUrl: config.solidBaseUrl,
155
+ // API_HOST is the address the runtime bound its services to; XPOD_MAIN_PORT is the
156
+ // Gateway's. Both are read here, while the runtime still has its environment applied.
157
+ gatewayHost: process.env.API_HOST,
158
+ gatewayPort: process.env.XPOD_MAIN_PORT,
159
+ }),
146
160
  });
147
161
  }).singleton(),
148
162
  invocationTokenCodec: (0, awilix_1.asFunction)(() => {
@@ -156,7 +170,7 @@ function registerCommonServices(container) {
156
170
  });
157
171
  }).singleton(),
158
172
  gatewayAccessKeyRepository: (0, awilix_1.asFunction)((cradle) => {
159
- const { config, hostedPodDataAccess } = cradle;
173
+ const { config, ownerPodAccess } = cradle;
160
174
  return new PodGatewayAccessKeyRepository_1.PodGatewayAccessKeyRepository({
161
175
  locatorCodec: new GatewayKeyLocatorCodec_1.AesGatewayKeyLocatorCodec({
162
176
  active: {
@@ -165,7 +179,7 @@ function registerCommonServices(container) {
165
179
  },
166
180
  previous: config.gatewayPreviousLocatorSecrets,
167
181
  }),
168
- internalPodAccess: hostedPodDataAccess,
182
+ podAccess: ownerPodAccess,
169
183
  podBaseUrlResolver: podBaseUrlResolver(cradle),
170
184
  });
171
185
  }).singleton(),
@@ -180,9 +194,8 @@ function registerCommonServices(container) {
180
194
  }).singleton(),
181
195
  providerConnectService: (0, awilix_1.asFunction)((cradle) => {
182
196
  const { config } = cradle;
183
- const internalPodAccess = cradle.hostedPodDataAccess;
184
197
  const credentialRepository = new connect_1.PodConnectedCredentialRepository({
185
- internalPodAccess,
198
+ podAccess: cradle.ownerPodAccess,
186
199
  podBaseUrlResolver: podBaseUrlResolver(cradle),
187
200
  });
188
201
  const vault = credentialVaultForConfig(config);
@@ -253,24 +266,24 @@ function registerCommonServices(container) {
253
266
  systemProxy: config.edition === 'local' ? (0, provider_http_transport_1.discoverSystemProviderProxy)() : undefined,
254
267
  })).singleton(),
255
268
  gatewayCredentialStore: (0, awilix_1.asFunction)((cradle) => {
256
- const { hostedPodDataAccess } = cradle;
269
+ const { ownerPodAccess } = cradle;
257
270
  return new connect_1.PodConnectedCredentialRepository({
258
- internalPodAccess: hostedPodDataAccess,
271
+ podAccess: ownerPodAccess,
259
272
  podBaseUrlResolver: podBaseUrlResolver(cradle),
260
273
  });
261
274
  }).singleton(),
262
275
  podModelSelectionRepository: (0, awilix_1.asFunction)((cradle) => {
263
- const { hostedPodDataAccess } = cradle;
276
+ const { ownerPodAccess } = cradle;
264
277
  return new PodModelSelectionRepository_1.PodModelSelectionRepository({
265
- internalPodAccess: hostedPodDataAccess,
278
+ podAccess: ownerPodAccess,
266
279
  podBaseUrlResolver: podBaseUrlResolver(cradle),
267
280
  });
268
281
  }).singleton(),
269
282
  providerModelSelectionService: (0, awilix_1.asFunction)((cradle) => {
270
- const { config, gatewayProviderRegistry, hostedPodDataAccess, podModelSelectionRepository, providerModelsService, } = cradle;
283
+ const { config, gatewayProviderRegistry, ownerPodAccess, podModelSelectionRepository, providerModelsService, } = cradle;
271
284
  return new ProviderModelSelectionService_1.ProviderModelSelectionService({
272
285
  credentialRepository: new connect_1.PodConnectedCredentialRepository({
273
- internalPodAccess: hostedPodDataAccess,
286
+ podAccess: ownerPodAccess,
274
287
  podBaseUrlResolver: podBaseUrlResolver(cradle),
275
288
  }),
276
289
  selectionRepository: podModelSelectionRepository,
@@ -346,14 +359,14 @@ function registerCommonServices(container) {
346
359
  }).singleton(),
347
360
  providerQuotaService: (0, awilix_1.asFunction)((cradle) => {
348
361
  const { config } = cradle;
349
- const internalPodAccess = cradle.hostedPodDataAccess;
362
+ const podAccess = cradle.ownerPodAccess;
350
363
  return new quota_1.ProviderQuotaService({
351
364
  repository: new quota_1.PodQuotaSnapshotRepository({
352
- internalPodAccess,
365
+ podAccess,
353
366
  podBaseUrlResolver: podBaseUrlResolver(cradle),
354
367
  }),
355
368
  credentialRepository: new connect_1.PodConnectedCredentialRepository({
356
- internalPodAccess,
369
+ podAccess,
357
370
  podBaseUrlResolver: podBaseUrlResolver(cradle),
358
371
  }),
359
372
  vault: credentialVaultForConfig(config),
@@ -373,7 +386,7 @@ function registerCommonServices(container) {
373
386
  }).singleton(),
374
387
  providerModelsService: (0, awilix_1.asFunction)((cradle) => {
375
388
  const { config } = cradle;
376
- const internalPodAccess = cradle.hostedPodDataAccess;
389
+ const podAccess = cradle.ownerPodAccess;
377
390
  const registry = cradle.gatewayProviderRegistry;
378
391
  const safeBaseUrls = (provider) => [
379
392
  ...registry.requireProvider(provider).safeBaseUrls,
@@ -381,7 +394,7 @@ function registerCommonServices(container) {
381
394
  ];
382
395
  return new models_1.ProviderModelsService({
383
396
  credentialRepository: new connect_1.PodConnectedCredentialRepository({
384
- internalPodAccess,
397
+ podAccess,
385
398
  podBaseUrlResolver: podBaseUrlResolver(cradle),
386
399
  }),
387
400
  vault: credentialVaultForConfig(config),
@@ -446,7 +459,7 @@ function registerCommonServices(container) {
446
459
  providerCustomModelsService: (0, awilix_1.asFunction)((cradle) => {
447
460
  return new models_1.ProviderCustomModelsService({
448
461
  credentialRepository: new connect_1.PodConnectedCredentialRepository({
449
- internalPodAccess: cradle.hostedPodDataAccess,
462
+ podAccess: cradle.ownerPodAccess,
450
463
  podBaseUrlResolver: podBaseUrlResolver(cradle),
451
464
  }),
452
465
  embeddingModelPolicy: cradle.embeddingModelPolicy,
@@ -511,9 +524,9 @@ function registerCommonServices(container) {
511
524
  });
512
525
  }).singleton(),
513
526
  // ChatKit 存储与服务
514
- chatKitStore: (0, awilix_1.asFunction)(({ config, serverGroupReconcilerService }) => {
527
+ chatKitStore: (0, awilix_1.asFunction)(({ config, ownerPodAccess, serverGroupReconcilerService }) => {
515
528
  return new chatkit_1.PodChatKitStore({
516
- tokenEndpoint: config.cssTokenEndpoint,
529
+ podAccess: ownerPodAccess,
517
530
  serverGroupReconcilerService,
518
531
  deployment: config.edition,
519
532
  credentialSecretDecoder: (0, AiCredentialSecretDecoder_1.createAiCredentialSecretDecoder)({
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.registerLocalServices = registerLocalServices;
11
11
  const awilix_1 = require("awilix");
12
12
  const SubdomainClient_1 = require("../../subdomain/SubdomainClient");
13
+ const AutoTunnelProvider_1 = require("../../tunnel/AutoTunnelProvider");
13
14
  const LocalTunnelProvider_1 = require("../../tunnel/LocalTunnelProvider");
14
15
  const NgrokTunnelProvider_1 = require("../../tunnel/NgrokTunnelProvider");
15
16
  const SakuraFrpTunnelProvider_1 = require("../../tunnel/SakuraFrpTunnelProvider");
@@ -49,40 +50,72 @@ function registerLocalServices(container) {
49
50
  const activeCredential = activeTunnel.profile?.credentialEnvKey
50
51
  ? readCredential(process.env[activeTunnel.profile.credentialEnvKey])
51
52
  : undefined;
52
- if (activeTunnelProvider === 'ngrok') {
53
- container.register({
54
- localTunnelProvider: (0, awilix_1.asFunction)(() => {
55
- return new NgrokTunnelProvider_1.NgrokTunnelProvider({
56
- authtoken: activeCredential ?? ngrokAuthToken,
57
- url: activeTunnel.profile?.publicUrl ?? ngrokUrl,
58
- ngrokPath,
59
- });
60
- }).singleton(),
61
- });
62
- console.log('[Local] Tunnel provider registered (ngrok configured)');
63
- }
64
- else if (activeTunnelProvider === 'cloudflare') {
53
+ // Every provider the operator has credentials for is a candidate; which one
54
+ // can reach its control plane is a property of the network, so an implicit
55
+ // selection is settled by readiness instead of by list order. An explicitly
56
+ // pinned profile or provider still starts exactly that one - including when it
57
+ // has no credential, because then its own failure is the answer the operator
58
+ // needs rather than a silently absent provider.
59
+ const profileFor = (provider) => (config.tunnelProfiles ?? []).find((entry) => entry.provider === provider);
60
+ const profileCredential = (provider) => {
61
+ const key = profileFor(provider)?.credentialEnvKey;
62
+ return key ? readCredential(process.env[key]) : undefined;
63
+ };
64
+ const credentialFor = (provider) => (activeTunnelProvider === provider ? activeCredential : undefined) ?? profileCredential(provider);
65
+ const candidates = [
66
+ {
67
+ id: 'ngrok',
68
+ provider: 'ngrok',
69
+ configured: Boolean(credentialFor('ngrok') ?? ngrokAuthToken ?? ngrokUrl),
70
+ create: () => new NgrokTunnelProvider_1.NgrokTunnelProvider({
71
+ authtoken: credentialFor('ngrok') ?? ngrokAuthToken,
72
+ url: profileFor('ngrok')?.publicUrl ?? ngrokUrl,
73
+ ngrokPath,
74
+ }),
75
+ },
76
+ {
77
+ id: 'cloudflare',
78
+ provider: 'cloudflare',
79
+ configured: Boolean(credentialFor('cloudflare') ?? cloudflareTunnelToken),
80
+ create: () => new LocalTunnelProvider_1.LocalTunnelProvider({
81
+ tunnelToken: (credentialFor('cloudflare') ?? cloudflareTunnelToken),
82
+ publicUrl: profileFor('cloudflare')?.publicUrl,
83
+ }),
84
+ },
85
+ {
86
+ id: 'sakura_frp',
87
+ provider: 'sakura_frp',
88
+ configured: Boolean(credentialFor('sakura_frp') ?? sakuraTunnelToken),
89
+ create: () => new SakuraFrpTunnelProvider_1.SakuraFrpTunnelProvider({
90
+ token: (credentialFor('sakura_frp') ?? sakuraTunnelToken),
91
+ publicUrl: profileFor('sakura_frp')?.publicUrl,
92
+ frpcPath,
93
+ }),
94
+ },
95
+ ];
96
+ const pinnedProvider = config.tunnelActiveProfileId || config.tunnelProvider || process.env.XPOD_TUNNEL_PROVIDER
97
+ ? activeTunnelProvider
98
+ : undefined;
99
+ const selected = pinnedProvider
100
+ ? candidates.filter((candidate) => candidate.provider === pinnedProvider)
101
+ : candidates.filter((candidate) => candidate.configured);
102
+ if (selected.length === 1) {
103
+ const [only] = selected;
65
104
  container.register({
66
- localTunnelProvider: (0, awilix_1.asFunction)(() => {
67
- return new LocalTunnelProvider_1.LocalTunnelProvider({
68
- tunnelToken: (activeCredential ?? cloudflareTunnelToken),
69
- publicUrl: activeTunnel.profile?.publicUrl,
70
- });
71
- }).singleton(),
105
+ localTunnelProvider: (0, awilix_1.asFunction)(only.create).singleton(),
72
106
  });
73
- console.log('[Local] Tunnel provider registered (CLOUDFLARE_TUNNEL_TOKEN configured)');
107
+ console.log(`[Local] Tunnel provider registered (${only.id} configured)`);
74
108
  }
75
- else if (activeTunnelProvider === 'sakura_frp') {
109
+ else if (selected.length > 1) {
76
110
  container.register({
77
- localTunnelProvider: (0, awilix_1.asFunction)(() => {
78
- return new SakuraFrpTunnelProvider_1.SakuraFrpTunnelProvider({
79
- token: (activeCredential ?? sakuraTunnelToken),
80
- publicUrl: activeTunnel.profile?.publicUrl,
81
- frpcPath,
82
- });
83
- }).singleton(),
111
+ localTunnelProvider: (0, awilix_1.asFunction)(() => new AutoTunnelProvider_1.AutoTunnelProvider({
112
+ candidates: selected.map((candidate) => ({
113
+ id: candidate.id,
114
+ provider: candidate.create(),
115
+ })),
116
+ })).singleton(),
84
117
  });
85
- console.log('[Local] Tunnel provider registered (SAKURA_TUNNEL_TOKEN configured)');
118
+ console.log(`[Local] Tunnel providers registered (auto: ${selected.map((candidate) => candidate.id).join(', ')})`);
86
119
  }
87
120
  // 2. 自适应 DNS 管理 (Self-Hosted DNS)
88
121
  // 如果配置了 Cloudflare API Token 和 Base Domain,启用本地 DNS 管理
@@ -34,6 +34,7 @@ exports.resolveLocalStorageProviderBaseUrl = resolveLocalStorageProviderBaseUrl;
34
34
  const EdgeNodeSignalHandler_1 = require("../handlers/EdgeNodeSignalHandler");
35
35
  const ReachabilityHandler_1 = require("../handlers/ReachabilityHandler");
36
36
  const NodeAccessResolver_1 = require("../../edge/reachability/NodeAccessResolver");
37
+ const RouteSetBuilder_1 = require("../../edge/reachability/RouteSetBuilder");
37
38
  const NodeHandler_1 = require("../handlers/NodeHandler");
38
39
  const ChatHandler_1 = require("../handlers/ChatHandler");
39
40
  const SubdomainHandler_1 = require("../handlers/SubdomainHandler");
@@ -131,7 +132,7 @@ function registerSharedRoutes(container, server) {
131
132
  const rdfStorageStatsService = container.resolve('rdfStorageStatsService');
132
133
  const rdfEngine = container.resolve('rdfEngine', { allowUnregistered: true });
133
134
  const rdfSearchIndexingService = container.resolve('rdfSearchIndexingService', { allowUnregistered: true });
134
- const hostedPodDataAccess = container.resolve('hostedPodDataAccess');
135
+ const ownerPodAccess = container.resolve('ownerPodAccess');
135
136
  const aiConnectionInvocationKeyIssuer = container.resolve('aiConnectionInvocationKeyIssuer');
136
137
  const gatewayAccessKeyRepository = container.resolve('gatewayAccessKeyRepository', { allowUnregistered: true });
137
138
  const providerConnectService = container.resolve('providerConnectService');
@@ -195,6 +196,7 @@ function registerSharedRoutes(container, server) {
195
196
  providerModelSelectionService,
196
197
  customModelsService: providerCustomModelsService,
197
198
  gatewayAccessKeyRepository,
199
+ podInterfaceKeys: ownerPodAccess,
198
200
  validateClientCredential: (apiKey) => container.resolve('authenticator').authenticate({
199
201
  headers: { authorization: `Bearer ${apiKey}` },
200
202
  method: 'POST',
@@ -214,15 +216,15 @@ function registerSharedRoutes(container, server) {
214
216
  (0, PodSettingsHandler_1.registerPodSettingsRoutes)(server, {
215
217
  podLookupRepository,
216
218
  usageRepo: new UsageRepository_1.UsageRepository(container.resolve('db')),
217
- aiConnectionStatusReader: new PodSettingsHandler_1.DrizzlePodAiConnectionsStatusReader(hostedPodDataAccess, config.edition),
219
+ aiConnectionStatusReader: new PodSettingsHandler_1.DrizzlePodAiConnectionsStatusReader(ownerPodAccess, config.edition),
218
220
  });
219
221
  const aiConfigStore = new AiConfigStore_1.DrizzlePodAiConfigStore({
220
- internalPodAccess: hostedPodDataAccess,
222
+ podAccess: ownerPodAccess,
221
223
  });
222
- const ftsRebuildAvailable = Boolean(hostedPodDataAccess && rdfEngine?.indexTextSource);
223
- const vectorRebuildAvailable = Boolean(hostedPodDataAccess && rdfSearchIndexingService && chatKitStore.createTrustedContext);
224
+ const ftsRebuildAvailable = Boolean(ownerPodAccess && rdfEngine?.indexTextSource);
225
+ const vectorRebuildAvailable = Boolean(ownerPodAccess && rdfSearchIndexingService && chatKitStore.createTrustedContext);
224
226
  const rebuildFts = async (owner) => {
225
- const trustedFetch = await hostedPodDataAccess.getTrustedFetch(owner.webId, undefined, { podBaseUrl: owner.podUrl });
227
+ const trustedFetch = await ownerPodAccess.getPodFetch(owner.webId, { podBaseUrl: owner.podUrl });
226
228
  if (!trustedFetch || !rdfEngine?.indexTextSource)
227
229
  throw new Error('fts_rebuild_unavailable');
228
230
  const result = await new PodSearchIndexRebuilder_1.PodSearchIndexRebuilder({
@@ -235,7 +237,7 @@ function registerSharedRoutes(container, server) {
235
237
  throw new Error('fts_rebuild_incomplete');
236
238
  };
237
239
  const rebuildVector = async (owner) => {
238
- const trustedFetch = await hostedPodDataAccess.getTrustedFetch(owner.webId, undefined, { podBaseUrl: owner.podUrl });
240
+ const trustedFetch = await ownerPodAccess.getPodFetch(owner.webId, { podBaseUrl: owner.podUrl });
239
241
  if (!trustedFetch || !rdfSearchIndexingService)
240
242
  throw new Error('vector_rebuild_unavailable');
241
243
  const context = await chatKitStore.createTrustedContext({ ...owner, fetch: trustedFetch });
@@ -523,6 +525,8 @@ function registerLocalRoutes(container, server) {
523
525
  // SP 状态查询 (供 Linx 查询 SP 配置状态)
524
526
  try {
525
527
  const config = container.resolve('config');
528
+ const statusTunnelProvider = (container.resolve('localTunnelProvider', { allowUnregistered: true })
529
+ ?? container.resolve('tunnelProvider', { allowUnregistered: true }));
526
530
  (0, ProvisionHandler_1.registerProvisionStatusRoute)(server, {
527
531
  cloudUrl: config.cloudApiEndpoint,
528
532
  nodeId: config.nodeId,
@@ -538,7 +542,35 @@ function registerLocalRoutes(container, server) {
538
542
  ?? process.env.SAKURA_TOKEN,
539
543
  cloudBaseUrl: config.oidcIssuer || config.cloudApiEndpoint,
540
544
  provisionCode: process.env.XPOD_PROVISION_CODE ?? config.provisionCode,
545
+ // A configured tunnel only becomes a public route once its provider
546
+ // reports a proxy-ready endpoint; clients use this to decide whether a
547
+ // canonical request can leave the machine at all.
548
+ readPublicRoute: () => {
549
+ if (!statusTunnelProvider) {
550
+ return { configured: false, available: false };
551
+ }
552
+ const status = statusTunnelProvider.getStatus();
553
+ const endpoint = status.endpoint ?? statusTunnelProvider.getEndpoint();
554
+ return {
555
+ configured: true,
556
+ available: Boolean(status.connected),
557
+ ...(statusTunnelProvider.name ? { provider: statusTunnelProvider.name } : {}),
558
+ ...(endpoint ? { endpoint } : {}),
559
+ };
560
+ },
541
561
  persistState: (0, ProvisionHandler_1.createLocalSetupProvisionStateWriter)(config.localSetupPath, config.localSetupProviderId),
562
+ // A client ranks access points by where it stands, so the node reports the
563
+ // paths it can prove: its canonical public route, plus the tunnel endpoint
564
+ // whenever a provider confirms one that is not simply the canonical URL.
565
+ readAccessRoutes: () => {
566
+ const status = statusTunnelProvider?.getStatus();
567
+ const publicUrl = process.env.XPOD_PUBLIC_URL ?? config.publicUrl ?? process.env.CSS_BASE_URL;
568
+ return (0, RouteSetBuilder_1.buildPublicAccessRoutes)({
569
+ canonicalUrl: publicUrl,
570
+ publicRouteAvailable: Boolean(status?.connected),
571
+ tunnelEndpoint: status?.endpoint ?? statusTunnelProvider?.getEndpoint(),
572
+ });
573
+ },
542
574
  });
543
575
  console.log('[Local] Provision status route registered (/provision/status)');
544
576
  }
@@ -51,7 +51,7 @@ import type { ProviderRegistry as GatewayProviderRegistry } from '../ai-gateway/
51
51
  import type { SessionAffinityStore } from '../ai-gateway/routing/SessionAffinityStore';
52
52
  import type { AiConnectionsInvocationKeyIssuer } from '../ai-gateway/auth/AiConnectionsInvocationKeyIssuer';
53
53
  import type { InvocationTokenCodec } from '../ai-gateway/auth/InvocationTokenCodec';
54
- import type { InternalPodAccessTokenProvider } from '../ai-gateway/pod/HostedPodDataAccess';
54
+ import type { OwnerPodAccess } from '../ai-gateway/pod/OwnerPodAccess';
55
55
  import type { GatewayAccessKeyRepository } from '../ai-gateway/auth/GatewayApiKeyAuthenticator';
56
56
  import type { PodModelSelectionRepository } from '../ai-gateway/models/PodModelSelectionRepository';
57
57
  import type { ProviderModelSelectionService } from '../ai-gateway/models/ProviderModelSelectionService';
@@ -204,7 +204,7 @@ export interface ApiContainerCradle {
204
204
  authenticator: Authenticator;
205
205
  nodeRepo: EdgeNodeRepository;
206
206
  serviceTokenRepo: ServiceTokenRepositoryPort;
207
- hostedPodDataAccess: InternalPodAccessTokenProvider;
207
+ ownerPodAccess: OwnerPodAccess;
208
208
  invocationTokenCodec?: InvocationTokenCodec;
209
209
  gatewayAccessKeyRepository?: GatewayAccessKeyRepository;
210
210
  aiConnectionInvocationKeyIssuer?: AiConnectionsInvocationKeyIssuer;
@@ -6,6 +6,7 @@ import type { ProviderConnectService } from '../ai-gateway/connect';
6
6
  import type { ProviderModelSelectionService } from '../ai-gateway/models/ProviderModelSelectionService';
7
7
  import type { ProviderQuotaService } from '../ai-gateway/quota';
8
8
  import { type ProviderCustomModelsService, type ProviderModelsService } from '../ai-gateway/models';
9
+ import type { PodInterfaceKeyGrant } from '../ai-gateway/pod/PodInterfaceKeyStore';
9
10
  import type { AiConnectionsInvocationKeyIssuer } from '../ai-gateway/auth/AiConnectionsInvocationKeyIssuer';
10
11
  import { type AiClientConfigurationCapabilityDescriptor } from '../service/AiClientConfigurationService';
11
12
  export interface AiGatewayManagementHandlerOptions {
@@ -25,6 +26,11 @@ export interface AiGatewayManagementHandlerOptions {
25
26
  gatewayAccessKeyRepository?: GatewayAccessKeyRepository;
26
27
  /** Reuses the configured CSS authenticator; never trusts the claimed registration owner. */
27
28
  validateClientCredential?: (apiKey: string) => Promise<AuthResult>;
29
+ /**
30
+ * Server-side Pod access for the owner. The wrapper being registered is the owner's own
31
+ * interface key, so registering it is also the moment Xpod is granted its use.
32
+ */
33
+ podInterfaceKeys?: PodInterfaceKeyGrant;
28
34
  aiClientConfiguration?: AiClientConfigurationCapabilityDescriptor;
29
35
  aiConnectionInvocationKeyIssuer?: Pick<AiConnectionsInvocationKeyIssuer, 'issue' | 'issueClientConfiguration'>;
30
36
  jsonBodyLimitBytes?: number;
@@ -5,8 +5,10 @@ const node_crypto_1 = require("node:crypto");
5
5
  const global_logger_factory_1 = require("global-logger-factory");
6
6
  const readBoundedJsonBody_1 = require("../http/readBoundedJsonBody");
7
7
  const GatewayPrincipal_1 = require("../ai-gateway/auth/GatewayPrincipal");
8
+ const AuthContext_1 = require("../auth/AuthContext");
8
9
  const models_1 = require("../ai-gateway/models");
9
10
  const AiConnectionsServiceAccess_1 = require("../ai-gateway/service-access/AiConnectionsServiceAccess");
11
+ const OwnerPodAccess_1 = require("../ai-gateway/pod/OwnerPodAccess");
10
12
  const AiClientConfigurationService_1 = require("../service/AiClientConfigurationService");
11
13
  const errors_1 = require("../ai-gateway/errors");
12
14
  const EmbeddingModelPolicy_1 = require("../../ai/service/EmbeddingModelPolicy");
@@ -117,6 +119,15 @@ function registerAiGatewayManagementRoutes(server, options) {
117
119
  sendJson(response, 403, { error: 'CSS client credential belongs to another WebID' });
118
120
  return;
119
121
  }
122
+ if ((0, AuthContext_1.hasSolidClientCredentialsAuthority)(verified.context)) {
123
+ // Sealed before the record is written, because writing the record is the first thing
124
+ // that needs it: background components reach this Pod through the standard interface
125
+ // as the owner, never through the caller's network position.
126
+ await options.podInterfaceKeys?.saveKey(owner, {
127
+ clientId: verified.context.clientId,
128
+ clientSecret: verified.context.clientSecret,
129
+ });
130
+ }
120
131
  const name = normalizeOptionalString(body.name) ?? 'Xpod API Key';
121
132
  const keyId = repository.createKeyId(owner, options.deployment);
122
133
  const createdAt = new Date();
@@ -1329,7 +1340,7 @@ function sendCredentialPoolError(response, error) {
1329
1340
  }
1330
1341
  function sendGatewayAccessKeyError(response, error) {
1331
1342
  const message = error instanceof Error ? error.message : String(error);
1332
- if (message === 'service_access_missing') {
1343
+ if ((0, OwnerPodAccess_1.isPodAccessFailure)(message)) {
1333
1344
  sendJson(response, 403, { error: 'service_access_missing' });
1334
1345
  return;
1335
1346
  }
@@ -1465,9 +1476,11 @@ function sendModelSelectionError(response, error) {
1465
1476
  model_selection_exact_update_failed: { status: 409, error: 'model_selection_conflict' },
1466
1477
  model_selection_provider_update_failed: { status: 409, error: 'model_selection_conflict' },
1467
1478
  service_access_missing: { status: 403, error: 'service_access_missing' },
1468
- hosted_pod_auth_required: { status: 401, error: 'authentication_required' },
1469
- hosted_pod_solid_principal_required: { status: 403, error: 'solid_principal_required' },
1470
- hosted_pod_owner_mismatch: { status: 403, error: 'pod_owner_mismatch' },
1479
+ pod_interface_key_missing: { status: 403, error: 'service_access_missing' },
1480
+ pod_interface_key_rejected: { status: 403, error: 'service_access_missing' },
1481
+ caller_dpop_replay_unsupported: { status: 403, error: 'service_access_missing' },
1482
+ caller_owner_mismatch: { status: 403, error: 'pod_owner_mismatch' },
1483
+ caller_pod_access_unavailable: { status: 401, error: 'authentication_required' },
1471
1484
  };
1472
1485
  const mapped = stable[message];
1473
1486
  if (mapped) {
@@ -164,6 +164,13 @@ export interface NetworkSettingsHandlerOptions {
164
164
  logger?: Pick<ReturnType<typeof getLoggerFor>, 'warn' | 'error'>;
165
165
  }
166
166
  /** The ingress address this runtime listens on, when it published one. */
167
+ /**
168
+ * The address a provider console is configured with: the Gateway's tunnel entry.
169
+ *
170
+ * It is the listener that never treats a caller as local, so a tunnel reaching the runtime
171
+ * through it cannot inherit local trust whatever headers it carries. The banner the user
172
+ * copies is this one number; the Gateway's own port is what they type in a browser.
173
+ */
167
174
  export declare function readIngressAddress(env?: NodeJS.ProcessEnv): {
168
175
  ingress?: {
169
176
  port: number;
@@ -16,6 +16,13 @@ const readBoundedJsonBody_1 = require("../http/readBoundedJsonBody");
16
16
  const AdminHandler_1 = require("./AdminHandler");
17
17
  const TunnelProviderCatalog_1 = require("../../tunnel/TunnelProviderCatalog");
18
18
  /** The ingress address this runtime listens on, when it published one. */
19
+ /**
20
+ * The address a provider console is configured with: the Gateway's tunnel entry.
21
+ *
22
+ * It is the listener that never treats a caller as local, so a tunnel reaching the runtime
23
+ * through it cannot inherit local trust whatever headers it carries. The banner the user
24
+ * copies is this one number; the Gateway's own port is what they type in a browser.
25
+ */
19
26
  function readIngressAddress(env = process.env) {
20
27
  const port = Number.parseInt(env.XPOD_GATEWAY_INGRESS_PORT ?? '', 10);
21
28
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {