@undefineds.co/xpod 0.4.25 → 0.4.27

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 (155) hide show
  1. package/dist/api/ApiServer.d.ts +1 -0
  2. package/dist/api/ApiServer.js +38 -0
  3. package/dist/api/ai-config/AiConfigStore.js +18 -6
  4. package/dist/api/ai-gateway/connect/index.d.ts +1 -0
  5. package/dist/api/ai-gateway/connect/index.js +39 -18
  6. package/dist/api/ai-gateway/models/ProviderModelsAdapter.js +5 -2
  7. package/dist/api/container/common.js +8 -2
  8. package/dist/api/container/index.js +2 -2
  9. package/dist/api/container/local.js +6 -3
  10. package/dist/api/handlers/AiGatewayHandler.d.ts +1 -0
  11. package/dist/api/handlers/AiGatewayHandler.js +53 -3
  12. package/dist/api/handlers/AiGatewayManagementHandler.js +9 -0
  13. package/dist/api/handlers/TaskHandler.js +15 -10
  14. package/dist/api/network/NetworkEnvironmentConfigurationStore.js +1 -1
  15. package/dist/api/runs/ManagedRunWorker.d.ts +0 -1
  16. package/dist/api/runs/ManagedRunWorker.js +2 -14
  17. package/dist/api/runs/PiAgentRuntimeDriver.d.ts +0 -8
  18. package/dist/api/runs/PiAgentRuntimeDriver.js +65 -21
  19. package/dist/api/runs/RunStateCenter.js +1 -0
  20. package/dist/api/runs/store.d.ts +1 -0
  21. package/dist/api/runs/store.js +13 -0
  22. package/dist/api/tasks/TaskMaterializer.js +13 -6
  23. package/dist/api/tasks/TaskResumeDiagnostics.d.ts +28 -0
  24. package/dist/api/tasks/TaskResumeDiagnostics.js +148 -0
  25. package/dist/api/tasks/TaskService.js +40 -23
  26. package/dist/storage/conditions/StorageETagHandler.d.ts +1 -1
  27. package/dist/storage/conditions/StorageETagHandler.js +13 -4
  28. package/dist/util/task-model-diagnostics.d.ts +67 -0
  29. package/dist/util/task-model-diagnostics.js +118 -0
  30. package/node_modules/@undefineds.co/ai-connections/dist/AiApiKeyPool.d.ts +1 -0
  31. package/node_modules/@undefineds.co/ai-connections/dist/AiApiKeyPool.js +2 -1
  32. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectDialog.d.ts +1 -0
  33. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +1 -0
  34. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +10 -10
  35. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.d.ts +3 -1
  36. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.js +2 -2
  37. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +77 -23
  38. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +1 -0
  39. package/node_modules/@undefineds.co/ai-connections/dist/AiSortableCredentialList.js +59 -59
  40. package/node_modules/@undefineds.co/ai-connections/dist/collection-runtime.js +2 -0
  41. package/node_modules/@undefineds.co/ai-connections/dist/collections.js +14 -6
  42. package/node_modules/@undefineds.co/ai-connections/dist/contract/ai-connections-client.d.ts +2 -1
  43. package/node_modules/@undefineds.co/ai-connections/dist/contract/ai-connections-client.js +1 -0
  44. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/gateway-model-roles.d.ts +11 -0
  45. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/gateway-model-roles.js +11 -0
  46. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/normalize.d.ts +2 -1
  47. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/normalize.js +12 -5
  48. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/request.js +10 -2
  49. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/types.d.ts +10 -0
  50. package/node_modules/@undefineds.co/ai-connections/package.json +1 -1
  51. package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +1 -0
  52. package/node_modules/@undefineds.co/extension-sdk/package.json +1 -1
  53. package/node_modules/@undefineds.co/pod-collections/dist/index.cjs +60 -13
  54. package/node_modules/@undefineds.co/pod-collections/dist/mapping.d.ts +16 -1
  55. package/node_modules/@undefineds.co/pod-collections/dist/mapping.js +51 -0
  56. package/node_modules/@undefineds.co/pod-collections/dist/mutations.js +14 -18
  57. package/node_modules/@undefineds.co/pod-collections/package.json +1 -1
  58. package/node_modules/@undefineds.co/shared-ui/dist/list.d.ts +8 -1
  59. package/node_modules/@undefineds.co/shared-ui/dist/list.js +4 -1
  60. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.d.ts +1 -1
  61. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.js +2 -2
  62. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpViews.js +2 -2
  63. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.d.ts +3 -3
  64. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.js +5 -5
  65. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.d.ts +5 -5
  66. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.js +5 -5
  67. package/node_modules/@undefineds.co/shared-ui/dist/section-header.js +1 -1
  68. package/node_modules/@undefineds.co/shared-ui/dist/theme.css +5 -32
  69. package/node_modules/@undefineds.co/shared-ui/package.json +1 -1
  70. package/node_modules/@undefineds.co/solid-sdk/package.json +1 -1
  71. package/package.json +2 -2
  72. package/static/app/assets/main.css +1 -1
  73. package/static/app/assets/main.js +1 -1
  74. package/static/dashboard/assets/AccountAuthBoundary-Dv52SZc6.js +1 -0
  75. package/static/dashboard/assets/DesktopOidcCallback-DaT_Rumf.js +1 -0
  76. package/static/dashboard/assets/DevicePages-HjclpIVm.js +2 -0
  77. package/static/dashboard/assets/ModelsPage-Be2TPd-x.js +11 -0
  78. package/static/dashboard/assets/{PaneListHeader-Dn5O0mwy.js → PaneListHeader-DtvnRmFq.js} +1 -1
  79. package/static/dashboard/assets/{PodDeletionAuthorizationPanel-D3NOG6ZM.js → PodDeletionAuthorizationPanel-BJk_8rJq.js} +3 -3
  80. package/static/dashboard/assets/PodPage-C4gzEspV.js +1 -0
  81. package/static/dashboard/assets/{SystemSettingsPage-BNQP_Yuc.js → SystemSettingsPage-HaNrP1H1.js} +1 -1
  82. package/static/dashboard/assets/TasksPage-BdO5K2L7.js +1 -0
  83. package/static/dashboard/assets/{WebDesktopEntry-vnb4CZWp.js → WebDesktopEntry-Cj9_ZfJU.js} +1 -1
  84. package/static/dashboard/assets/XpodAiConnectionsPodStore-B7gmXBmC.js +3 -0
  85. package/static/dashboard/assets/XpodShellApp-D_KY4nCW.js +4 -0
  86. package/static/dashboard/assets/{account-session-DCUmCop1.js → account-session-DlcKNG7k.js} +1 -1
  87. package/static/dashboard/assets/{button-By5Bgvmb.js → button-wslF55uZ.js} +1 -1
  88. package/static/dashboard/assets/collection-runtime-uxxgEu13.js +1 -0
  89. package/static/dashboard/assets/{createLucideIcon-bunMzPms.js → createLucideIcon-BJQl82Qk.js} +1 -1
  90. package/static/dashboard/assets/dashboard-CbJ-JkrI.css +1 -0
  91. package/static/dashboard/assets/{dashboard-mlsg3yaw.js → dashboard-Or0gnkou.js} +2 -2
  92. package/static/dashboard/assets/index-BqFeOML9.js +1 -0
  93. package/static/dashboard/assets/{index-DvMG5pHk.js → index-DgKn1twH.js} +2 -2
  94. package/static/dashboard/assets/{label-Bj03L9xs.js → label-CS6Sx6Zb.js} +1 -1
  95. package/static/dashboard/assets/list-UyhlCFKk.js +1 -0
  96. package/static/dashboard/assets/{nav-item-style-BNqWQY6p.js → nav-item-style-CW1Nnorq.js} +1 -1
  97. package/static/dashboard/assets/{pod-collections-host-KnnGFZHL.js → pod-collections-host-DjK2xf-b.js} +6 -6
  98. package/static/dashboard/assets/{safe-continuation-D9AhcWiB.js → safe-continuation-ClWAhcma.js} +1 -1
  99. package/static/dashboard/assets/{section-header-CLCzbOp_.js → section-header-CEIEcco-.js} +1 -1
  100. package/static/dashboard/assets/{setting-row-DTCec8A2.js → setting-row-CaYv3dfF.js} +1 -1
  101. package/static/dashboard/assets/xpod-remembered-login-BUF9fin1.js +310 -0
  102. package/static/dashboard/dashboard.html +2 -2
  103. package/static/settings/assets/{AccountAuthBoundary-C0og_gIA.js → AccountAuthBoundary-Chqd38oU.js} +1 -1
  104. package/static/settings/assets/{DesktopOidcCallback-C5q4oFZ0.js → DesktopOidcCallback-VUrOSM_q.js} +1 -1
  105. package/static/settings/assets/DevicePages-C6qwvWwu.js +2 -0
  106. package/static/settings/assets/ModelsPage-Ry_dObRQ.js +11 -0
  107. package/static/settings/assets/{PaneListHeader-DqVlMe2D.js → PaneListHeader-B5RkRqr4.js} +1 -1
  108. package/static/settings/assets/{PodDeletionAuthorizationPanel-DgVvarVY.js → PodDeletionAuthorizationPanel-DPM-5Izt.js} +3 -3
  109. package/static/settings/assets/PodPage-CxND6wwc.js +1 -0
  110. package/static/settings/assets/{SystemSettingsPage-Crzm_DrI.js → SystemSettingsPage-C8EdMkW7.js} +1 -1
  111. package/static/settings/assets/TasksPage-DuqptEm8.js +1 -0
  112. package/static/settings/assets/{WebDesktopEntry-BGC5eRF2.js → WebDesktopEntry-BRcyktkv.js} +1 -1
  113. package/static/settings/assets/XpodAiConnectionsPodStore-CyA_BJVC.js +3 -0
  114. package/static/settings/assets/XpodShellApp-CsivHJl0.js +4 -0
  115. package/static/settings/assets/XpodThemeProvider-CbJ-JkrI.css +1 -0
  116. package/static/settings/assets/{XpodThemeProvider-CB6LepTA.js → XpodThemeProvider-DK3pIdRG.js} +2 -2
  117. package/static/settings/assets/{account-session-BcLjCj0x.js → account-session-DJOZ03xr.js} +1 -1
  118. package/static/settings/assets/auth-callback-GIBYEz_7.js +1 -0
  119. package/static/settings/assets/{button-BObT8pTE.js → button-D3yW-2o1.js} +1 -1
  120. package/static/settings/assets/collection-runtime-DfGo25mh.js +1 -0
  121. package/static/settings/assets/{createLucideIcon-BVsUxXpZ.js → createLucideIcon-CacwLsmB.js} +1 -1
  122. package/static/settings/assets/{index-ktjMkj7-.js → index-r5KRYsKC.js} +1 -1
  123. package/static/settings/assets/{label-BrqO8_m_.js → label-MJjUmvzG.js} +1 -1
  124. package/static/settings/assets/list-Ce-5k1MX.js +1 -0
  125. package/static/settings/assets/{nav-item-style-Di2CxkDU.js → nav-item-style-ZUbL8KOZ.js} +1 -1
  126. package/static/settings/assets/{pod-collections-host-KnnGFZHL.js → pod-collections-host-DjK2xf-b.js} +6 -6
  127. package/static/settings/assets/{safe-continuation-Dx6ceJpY.js → safe-continuation-vlBZFSlU.js} +1 -1
  128. package/static/settings/assets/{section-header-Dtl7yjVr.js → section-header-DGJdEvQw.js} +1 -1
  129. package/static/settings/assets/{setting-row-BL6Hhe8N.js → setting-row-C7tuFiho.js} +1 -1
  130. package/static/settings/assets/{settings-DaRBxoZk.js → settings-CyQPih1O.js} +1 -1
  131. package/static/settings/assets/xpod-remembered-login-D-TzzT5K.js +567 -0
  132. package/static/settings/auth-callback.html +3 -3
  133. package/static/settings/settings.html +3 -3
  134. package/static/dashboard/assets/AccountAuthBoundary-BvoDydRz.js +0 -1
  135. package/static/dashboard/assets/DesktopOidcCallback-BwIj874x.js +0 -1
  136. package/static/dashboard/assets/DevicePages-jOjMcvsr.js +0 -2
  137. package/static/dashboard/assets/ModelsPage-CMTdfqBR.js +0 -11
  138. package/static/dashboard/assets/PodPage-BueBdJ9_.js +0 -1
  139. package/static/dashboard/assets/TasksPage-CxtLJiSF.js +0 -1
  140. package/static/dashboard/assets/XpodAiConnectionsPodStore-wx3Ttupm.js +0 -18
  141. package/static/dashboard/assets/XpodShellApp-5FWY8-d5.js +0 -4
  142. package/static/dashboard/assets/collection-runtime-BnomuB0E.js +0 -1
  143. package/static/dashboard/assets/dashboard-C64R553B.css +0 -1
  144. package/static/dashboard/assets/xpod-remembered-login-Dr69A0Rv.js +0 -295
  145. package/static/settings/assets/DevicePages-fyHjjucT.js +0 -2
  146. package/static/settings/assets/ModelsPage-EyOByLyE.js +0 -11
  147. package/static/settings/assets/PodPage-DY4Oi9wg.js +0 -1
  148. package/static/settings/assets/TasksPage-CfXJbnlt.js +0 -1
  149. package/static/settings/assets/XpodAiConnectionsPodStore-CXcmgKmQ.js +0 -3
  150. package/static/settings/assets/XpodShellApp-D1PS3Iut.js +0 -4
  151. package/static/settings/assets/XpodThemeProvider-C64R553B.css +0 -1
  152. package/static/settings/assets/auth-callback-DSPQ28ar.js +0 -1
  153. package/static/settings/assets/collection-runtime-DzNbdZGe.js +0 -1
  154. package/static/settings/assets/index-browser-3WfrNbcG.js +0 -273
  155. package/static/settings/assets/xpod-remembered-login-Q66DH12t.js +0 -295
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useLayoutEffect, useRef, useState } from 'react';
3
- import { cn } from '@undefineds.co/shared-ui';
3
+ import { cn, interactiveFocusClass, ListSurface } from '@undefineds.co/shared-ui';
4
4
  import { GripVertical } from 'lucide-react';
5
5
  import { credentialDisplayLabel } from './credential-labels.js';
6
6
  export function AiSortableCredentialList({ credentials, disabled, onMove, children }) {
@@ -133,64 +133,64 @@ export function AiSortableCredentialList({ credentials, disabled, onMove, childr
133
133
  row.style.transform = `translateY(${shift}px)`;
134
134
  });
135
135
  };
136
- return (_jsxs("div", { ref: listRef, className: cn(credentials.length > 0 && 'rounded-xl border border-border/70'), children: [_jsx("span", { className: "sr-only", role: "status", children: announcement }), credentials.map((credential, index) => (_jsx("div", { "data-sortable-credential": credential.id, className: "border-b border-border/60 first-of-type:rounded-t-xl last-of-type:rounded-b-xl last:border-b-0", children: children(credential, onMove ? (_jsx("button", { type: "button", disabled: unavailable, "aria-label": `拖动排序 ${credentialDisplayLabel(credential)}`, title: credentials.length < 2 ? '至少添加两条连接后可拖动排序' : '拖动调整优先级,或使用方向键、Home / End 排序', className: "flex h-8 w-6 shrink-0 touch-none select-none items-center justify-center rounded text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:opacity-40 enabled:cursor-grab active:cursor-grabbing", onKeyDown: (event) => {
137
- if (event.key === 'Escape') {
138
- finish(false);
139
- return;
140
- }
141
- const next = event.key === 'ArrowUp' ? index - 1 : event.key === 'ArrowDown' ? index + 1
142
- : event.key === 'Home' ? 0 : event.key === 'End' ? credentials.length - 1 : undefined;
143
- if (next === undefined)
144
- return;
145
- event.preventDefault();
146
- move(index, next);
147
- }, onPointerDown: (event) => {
148
- if (unavailable || event.button !== 0 || dragRef.current)
149
- return;
150
- event.preventDefault();
151
- dropRef.current = null;
152
- clearMotion();
153
- event.currentTarget.focus();
154
- event.currentTarget.setPointerCapture?.(event.pointerId);
155
- const elements = rows();
156
- const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
157
- dragRef.current = { id: credential.id, from: index, to: index, pointerId: event.pointerId,
158
- startY: event.clientY, y: event.clientY, handle: event.currentTarget, rows: elements,
159
- rects: elements.map((row) => row.getBoundingClientRect()) };
160
- for (const [rowIndex, row] of elements.entries()) {
161
- row.style.willChange = 'transform';
162
- row.style.transition = reduced || rowIndex === index ? 'none' : 'transform 160ms ease-out';
163
- if (rowIndex === index) {
164
- row.dataset.dragging = 'true';
165
- row.style.position = 'relative';
166
- row.style.zIndex = '10';
167
- row.style.boxShadow = '0 8px 24px rgb(0 0 0 / 0.18)';
168
- row.style.backgroundColor = 'hsl(var(--card))';
136
+ return (_jsx(ListSurface, { asChild: true, ref: listRef, className: cn('divide-y-0 bg-transparent', credentials.length > 0 ? 'border-border/70' : 'border-0 rounded-none'), children: _jsxs("div", { children: [_jsx("span", { className: "sr-only", role: "status", children: announcement }), credentials.map((credential, index) => (_jsx("div", { "data-sortable-credential": credential.id, className: "border-b border-border/60 first-of-type:rounded-t-xl last-of-type:rounded-b-xl last:border-b-0", children: children(credential, onMove ? (_jsx("button", { type: "button", disabled: unavailable, "aria-label": `拖动排序 ${credentialDisplayLabel(credential)}`, title: credentials.length < 2 ? '至少添加两条连接后可拖动排序' : '拖动调整优先级,或使用方向键、Home / End 排序', className: cn('flex h-8 w-6 shrink-0 touch-none select-none items-center justify-center rounded text-muted-foreground hover:text-foreground disabled:opacity-40 enabled:cursor-grab active:cursor-grabbing', interactiveFocusClass), onKeyDown: (event) => {
137
+ if (event.key === 'Escape') {
138
+ finish(false);
139
+ return;
169
140
  }
170
- }
171
- }, onPointerMove: (event) => {
172
- const drag = dragRef.current;
173
- if (!drag || drag.pointerId !== event.pointerId || unavailable)
174
- return;
175
- drag.y = event.clientY;
176
- let distance = Infinity;
177
- drag.rects.forEach((rect, rowIndex) => {
178
- const delta = Math.abs(event.clientY - (rect.top + rect.bottom) / 2);
179
- if (delta < distance) {
180
- distance = delta;
181
- drag.to = rowIndex;
141
+ const next = event.key === 'ArrowUp' ? index - 1 : event.key === 'ArrowDown' ? index + 1
142
+ : event.key === 'Home' ? 0 : event.key === 'End' ? credentials.length - 1 : undefined;
143
+ if (next === undefined)
144
+ return;
145
+ event.preventDefault();
146
+ move(index, next);
147
+ }, onPointerDown: (event) => {
148
+ if (unavailable || event.button !== 0 || dragRef.current)
149
+ return;
150
+ event.preventDefault();
151
+ dropRef.current = null;
152
+ clearMotion();
153
+ event.currentTarget.focus();
154
+ event.currentTarget.setPointerCapture?.(event.pointerId);
155
+ const elements = rows();
156
+ const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
157
+ dragRef.current = { id: credential.id, from: index, to: index, pointerId: event.pointerId,
158
+ startY: event.clientY, y: event.clientY, handle: event.currentTarget, rows: elements,
159
+ rects: elements.map((row) => row.getBoundingClientRect()) };
160
+ for (const [rowIndex, row] of elements.entries()) {
161
+ row.style.willChange = 'transform';
162
+ row.style.transition = reduced || rowIndex === index ? 'none' : 'transform 160ms ease-out';
163
+ if (rowIndex === index) {
164
+ row.dataset.dragging = 'true';
165
+ row.style.position = 'relative';
166
+ row.style.zIndex = '10';
167
+ row.style.boxShadow = '0 8px 24px rgb(0 0 0 / 0.18)';
168
+ row.style.backgroundColor = 'hsl(var(--card))';
169
+ }
182
170
  }
183
- });
184
- if (frameRef.current === undefined)
185
- frameRef.current = requestAnimationFrame(paintDrag);
186
- }, onPointerUp: (event) => {
187
- if (dragRef.current?.pointerId === event.pointerId)
188
- finish(true);
189
- }, onPointerCancel: (event) => {
190
- if (dragRef.current?.pointerId === event.pointerId)
191
- finish(false);
192
- }, onLostPointerCapture: (event) => {
193
- if (dragRef.current?.pointerId === event.pointerId)
194
- finish(false);
195
- }, children: _jsx(GripVertical, { "aria-hidden": "true", className: "h-4 w-4" }) })) : null) }, credential.id)))] }));
171
+ }, onPointerMove: (event) => {
172
+ const drag = dragRef.current;
173
+ if (!drag || drag.pointerId !== event.pointerId || unavailable)
174
+ return;
175
+ drag.y = event.clientY;
176
+ let distance = Infinity;
177
+ drag.rects.forEach((rect, rowIndex) => {
178
+ const delta = Math.abs(event.clientY - (rect.top + rect.bottom) / 2);
179
+ if (delta < distance) {
180
+ distance = delta;
181
+ drag.to = rowIndex;
182
+ }
183
+ });
184
+ if (frameRef.current === undefined)
185
+ frameRef.current = requestAnimationFrame(paintDrag);
186
+ }, onPointerUp: (event) => {
187
+ if (dragRef.current?.pointerId === event.pointerId)
188
+ finish(true);
189
+ }, onPointerCancel: (event) => {
190
+ if (dragRef.current?.pointerId === event.pointerId)
191
+ finish(false);
192
+ }, onLostPointerCapture: (event) => {
193
+ if (dragRef.current?.pointerId === event.pointerId)
194
+ finish(false);
195
+ }, children: _jsx(GripVertical, { "aria-hidden": "true", className: "h-4 w-4" }) })) : null) }, credential.id)))] }) }));
196
196
  }
@@ -188,7 +188,9 @@ export function collectionCredentialMutations(credentials, store) {
188
188
  const before = collection.get(key);
189
189
  if (Object.keys(patch).length > 0)
190
190
  await updateCredentialRow(collection, key, patch);
191
+ // Replacement secrets belong only to store completion, never to projected rows.
191
192
  const attributes = {
193
+ ...(input.apiKey === undefined ? {} : { apiKey: input.apiKey }),
192
194
  ...(input.priority === undefined ? {} : { priority: input.priority }),
193
195
  ...(input.baseUrl === undefined ? {} : { baseUrl: input.baseUrl }),
194
196
  ...(input.proxyUrl === undefined ? {} : { proxyUrl: input.proxyUrl }),
@@ -105,6 +105,10 @@ export function credentialSummaryFromRow(row, provider, carrier) {
105
105
  enabled: status === undefined ? carrier?.enabled ?? true : status === 'active',
106
106
  priority: carrier?.priority ?? 100,
107
107
  health: reauthRequired ? 'expired' : carrier?.health ?? 'unknown',
108
+ lastFailureCode: stringValue(row.lastFailureCode),
109
+ lastFailureAt: isoStringValue(row.lastFailureAt),
110
+ rateLimitResetAt: isoStringValue(row.rateLimitResetAt),
111
+ failCount: numberValue(row.failCount),
108
112
  maskedHint: carrier?.maskedHint,
109
113
  baseUrl: carrier?.baseUrl,
110
114
  proxyUrl: carrier?.proxyUrl,
@@ -115,7 +119,7 @@ export function credentialSummaryFromRow(row, provider, carrier) {
115
119
  }
116
120
  /** The provider's credential list, rebuilt from the live rows of one table document. */
117
121
  export function credentialSummariesForProvider(provider, rows, carrier = []) {
118
- const byId = new Map(carrier.map((credential) => [credential.id, credential]));
122
+ const byId = new Map(carrier.map((credential) => [credentialResource.buildId({ id: credential.id }), credential]));
119
123
  const summaries = [];
120
124
  for (const row of rows) {
121
125
  if (providerOfCredentialRow(row) !== provider)
@@ -137,11 +141,15 @@ export function credentialCarriers(storeProducts, overlayProducts) {
137
141
  const carriers = {};
138
142
  for (const provider of AI_CONNECTIONS_PROVIDERS) {
139
143
  const byId = new Map();
140
- for (const credential of overlayProducts[provider]?.credentials ?? []) {
141
- byId.set(credential.id, credential);
142
- }
143
- for (const credential of storeProducts[provider]?.credentials ?? []) {
144
- byId.set(credential.id, credential);
144
+ for (const credential of [
145
+ ...(storeProducts[provider]?.credentials ?? []),
146
+ ...(overlayProducts[provider]?.credentials ?? []),
147
+ ]) {
148
+ const id = credentialResource.buildId({ id: credential.id });
149
+ const current = byId.get(id);
150
+ // Each persisted mutation advances the version; a later store echo can supersede it.
151
+ if (!current || credential.version >= current.version)
152
+ byId.set(id, credential);
145
153
  }
146
154
  carriers[provider] = [...byId.values()];
147
155
  }
@@ -11,7 +11,8 @@
11
11
  * parsers under `./client` are internal, and `export *` would publish them.
12
12
  */
13
13
  export { AI_CONNECTIONS_PROVIDERS } from './client/types.js';
14
- export type { AiConnectAttempt, AiConnectionBeginOptions, AiConnectionsClient, AiConnectionsCredential, AiConnectionsMode, AiConnectionsProvider, AiConnectStatus, AiGatewayModel, AiProviderAuthorizationMethod, AiProviderAuthorizationMethodId, AiProviderAuthorizationMethodsSummary, AiProviderConnectionSummary, AiProviderCredentialSummary, AiProviderOffering, AiProviderSummary, AiProviderSummaryStatus, AiQuotaSnapshot, AiQuotaWindow, CreateApiKeyCredentialInput, CreatedGatewayKey, CustomProviderModel, DiscoveredProviderModel, GatewayKeyRecord, ProviderModelDiscovery, TestProviderCredentialInput, UpdateProviderCredentialInput, } from './client/types.js';
14
+ export { PLATFORM_MODEL_ROLES, matchesPlatformModelRole } from './client/gateway-model-roles.js';
15
+ export type { AiConnectAttempt, AiConnectionBeginOptions, AiConnectionsClient, AiConnectionsCredential, AiConnectionsMode, AiConnectionsProvider, AiConnectStatus, AiGatewayModel, AiGatewayCatalogModel, AiProviderAuthorizationMethod, AiProviderAuthorizationMethodId, AiProviderAuthorizationMethodsSummary, AiProviderConnectionSummary, AiProviderCredentialSummary, AiProviderOffering, AiProviderSummary, AiProviderSummaryStatus, AiQuotaSnapshot, AiQuotaWindow, CreateApiKeyCredentialInput, CreatedGatewayKey, CustomProviderModel, DiscoveredProviderModel, GatewayKeyRecord, ProviderModelDiscovery, TestProviderCredentialInput, UpdateProviderCredentialInput, } from './client/types.js';
15
16
  export { AiConnectionsRequestError, aiConnectionsErrorCode, normalizeProxyUrl, redactProxyUrl, } from './client/normalize.js';
16
17
  export { createAiConnectionsClient, resolveAiConnectionsApiBase } from './client/request.js';
17
18
  /**
@@ -11,6 +11,7 @@
11
11
  * parsers under `./client` are internal, and `export *` would publish them.
12
12
  */
13
13
  export { AI_CONNECTIONS_PROVIDERS } from './client/types.js';
14
+ export { PLATFORM_MODEL_ROLES, matchesPlatformModelRole } from './client/gateway-model-roles.js';
14
15
  export { AiConnectionsRequestError, aiConnectionsErrorCode, normalizeProxyUrl, redactProxyUrl, } from './client/normalize.js';
15
16
  export { createAiConnectionsClient, resolveAiConnectionsApiBase } from './client/request.js';
16
17
  /**
@@ -0,0 +1,11 @@
1
+ /** Platform routing roles published by the Gateway, independent of Pod model resources. */
2
+ export declare const PLATFORM_MODEL_ROLES: {
3
+ readonly smart: {
4
+ readonly id: "linx";
5
+ };
6
+ readonly fast: {
7
+ readonly id: "linx-lite";
8
+ };
9
+ };
10
+ /** Match only the Gateway's published bare IDs or its existing protocol namespace. */
11
+ export declare function matchesPlatformModelRole(modelId: string, role: keyof typeof PLATFORM_MODEL_ROLES): boolean;
@@ -0,0 +1,11 @@
1
+ /** Platform routing roles published by the Gateway, independent of Pod model resources. */
2
+ export const PLATFORM_MODEL_ROLES = {
3
+ smart: { id: 'linx' },
4
+ fast: { id: 'linx-lite' },
5
+ };
6
+ /** Match only the Gateway's published bare IDs or its existing protocol namespace. */
7
+ export function matchesPlatformModelRole(modelId, role) {
8
+ const normalized = modelId.toLowerCase();
9
+ const id = PLATFORM_MODEL_ROLES[role].id;
10
+ return normalized === id || normalized === `undefineds/${id}`;
11
+ }
@@ -1,4 +1,4 @@
1
- import type { AiConnectAttempt, AiConnectionsCredential, AiConnectionsProvider, AiGatewayModel, AiProviderAuthorizationMethodsSummary, AiProviderCredentialSummary, AiProviderSummary, CustomProviderModel, GatewayKeyRecord, ProviderModelDiscovery } from './types.js';
1
+ import type { AiConnectAttempt, AiConnectionsCredential, AiConnectionsProvider, AiGatewayModel, AiGatewayCatalogModel, AiProviderAuthorizationMethodsSummary, AiProviderCredentialSummary, AiProviderSummary, CustomProviderModel, GatewayKeyRecord, ProviderModelDiscovery } from './types.js';
2
2
  /**
3
3
  * Everything that turns untrusted API payloads, user input and failures into the
4
4
  * shapes the rest of the app can rely on: proxy/pod URL normalising, error text,
@@ -41,6 +41,7 @@ export declare function assertProvider(provider: string): asserts provider is Ai
41
41
  export declare function parseGatewayKeyRecord(value: unknown): GatewayKeyRecord | undefined;
42
42
  export declare function parseCustomModelList(value: unknown): CustomProviderModel[];
43
43
  export declare function parseGatewayModel(value: unknown): AiGatewayModel | undefined;
44
+ export declare function parseGatewayCatalogModel(value: unknown): AiGatewayCatalogModel | undefined;
44
45
  export declare function parseProviderSummaries(values: unknown[]): AiProviderSummary[];
45
46
  export declare function parseAuthorizationMethodsSummary(value: unknown): AiProviderAuthorizationMethodsSummary | undefined;
46
47
  export declare function parseProviderCredentialSummary(value: unknown): AiProviderCredentialSummary | undefined;
@@ -1,6 +1,7 @@
1
1
  import { PROVIDER_LABELS } from '../provider-catalog.js';
2
2
  import { AI_MODEL_CLASS } from '@undefineds.co/models';
3
3
  import { AI_CONNECTIONS_PROVIDERS } from './types.js';
4
+ import { PLATFORM_MODEL_ROLES, matchesPlatformModelRole } from './gateway-model-roles.js';
4
5
  /**
5
6
  * Everything that turns untrusted API payloads, user input and failures into the
6
7
  * shapes the rest of the app can rely on: proxy/pod URL normalising, error text,
@@ -188,6 +189,15 @@ export function parseGatewayModel(value) {
188
189
  capabilities: modelCapabilitiesFromWire(value),
189
190
  });
190
191
  }
192
+ export function parseGatewayCatalogModel(value) {
193
+ if (!isRecord(value) || typeof value.id !== 'string' || !value.id)
194
+ return undefined;
195
+ return compactObject({
196
+ id: value.id,
197
+ displayName: stringValue(value.displayName) ?? stringValue(value.display_name) ?? stringValue(value.name),
198
+ provider: stringValue(value.provider) ?? stringValue(value.providerId) ?? stringValue(value.owned_by),
199
+ });
200
+ }
191
201
  function modalitiesFromWire(value, direction) {
192
202
  if (!isRecord(value))
193
203
  return undefined;
@@ -226,11 +236,8 @@ function modelCapabilitiesFromWire(value) {
226
236
  return capabilities.length > 0 ? capabilities : undefined;
227
237
  }
228
238
  function isPlatformModelId(modelId) {
229
- const normalized = modelId.toLowerCase();
230
- return normalized === 'linx'
231
- || normalized === 'linx-lite'
232
- || normalized === 'undefineds/linx'
233
- || normalized === 'undefineds/linx-lite';
239
+ return Object.keys(PLATFORM_MODEL_ROLES)
240
+ .some(role => matchesPlatformModelRole(modelId, role));
234
241
  }
235
242
  function providerValue(value) {
236
243
  if (typeof value !== 'string')
@@ -1,4 +1,4 @@
1
- import { AiConnectionsRequestError, assertProvider, compactObject, isDefined, isRecord, parseAuthorizationMethodsSummary, parseConnectAttempt, parseCredential, parseCustomModelList, parseGatewayKeyRecord, parseGatewayModel, parseModelDiscovery, parseProviderCredentialSummary, parseProviderSummaries, readJson, sanitizePublicObject, } from './normalize.js';
1
+ import { AiConnectionsRequestError, assertProvider, compactObject, isDefined, isRecord, parseAuthorizationMethodsSummary, parseConnectAttempt, parseCredential, parseCustomModelList, parseGatewayKeyRecord, parseGatewayModel, parseGatewayCatalogModel, parseModelDiscovery, parseProviderCredentialSummary, parseProviderSummaries, readJson, sanitizePublicObject, } from './normalize.js';
2
2
  /**
3
3
  * The HTTP client itself: one factory over the Pod base URL and an authenticated
4
4
  * fetch, with every response normalised by `normalize.ts`.
@@ -37,8 +37,9 @@ export function createAiConnectionsClient({ webId, podBaseUrl, authenticatedFetc
37
37
  const payload = await request(`${providerPath(provider)}${path}`, method, body, { provider });
38
38
  return parseConnectAttempt(payload, provider);
39
39
  };
40
+ const requestGatewayModels = () => request('/v1/models', 'GET');
40
41
  const listGatewayModels = async () => {
41
- const payload = await request('/v1/models', 'GET');
42
+ const payload = await requestGatewayModels();
42
43
  return Array.isArray(payload.data)
43
44
  ? payload.data.map(parseGatewayModel).filter(isDefined)
44
45
  : [];
@@ -72,6 +73,13 @@ export function createAiConnectionsClient({ webId, podBaseUrl, authenticatedFetc
72
73
  },
73
74
  listModels: listGatewayModels,
74
75
  listGatewayModels,
76
+ async listGatewayCatalogModels() {
77
+ const payload = await requestGatewayModels();
78
+ if (!isRecord(payload) || !Array.isArray(payload.data)) {
79
+ throw new Error('AI Connection returned an invalid Gateway model catalog response');
80
+ }
81
+ return payload.data.map(parseGatewayCatalogModel).filter(isDefined);
82
+ },
75
83
  async listGatewayKeys() {
76
84
  const payload = await request('/api/ai/gateway/keys', 'GET');
77
85
  return Array.isArray(payload.data)
@@ -106,6 +106,12 @@ export interface AiGatewayModel extends AiConnectionsModelSelection {
106
106
  outputModalities?: string[];
107
107
  capabilities?: string[];
108
108
  }
109
+ /** Public Gateway directory entries can belong to a platform or an unknown provider. */
110
+ export interface AiGatewayCatalogModel {
111
+ id: string;
112
+ displayName?: string;
113
+ provider?: string;
114
+ }
109
115
  /**
110
116
  * What the provider's own model list said a model is.
111
117
  *
@@ -238,6 +244,8 @@ export interface CreateApiKeyCredentialInput {
238
244
  compatibility?: 'auto' | 'openai' | 'anthropic';
239
245
  }
240
246
  export interface UpdateProviderCredentialInput {
247
+ /** Nonempty replacement only; omission preserves the existing secret. */
248
+ apiKey?: string;
241
249
  expectedVersion: number;
242
250
  label?: string;
243
251
  enabled?: boolean;
@@ -273,6 +281,8 @@ export interface AiConnectionsClient {
273
281
  listModels(): Promise<AiGatewayModel[]>;
274
282
  /** Active Gateway routing projection, independent of a host's Pod catalog. */
275
283
  listGatewayModels?(): Promise<AiGatewayModel[]>;
284
+ /** Complete Gateway directory, including platform routing roles and unattributed entries. */
285
+ listGatewayCatalogModels?(): Promise<AiGatewayCatalogModel[]>;
276
286
  listGatewayKeys(): Promise<GatewayKeyRecord[]>;
277
287
  createGatewayKey(input: {
278
288
  name: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/ai-connections",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -115,6 +115,7 @@ export interface AiConnectionsPodStore {
115
115
  saveOAuthCredential?(provider: string, input: AiConnectionsOAuthCredential): Promise<unknown>;
116
116
  updateOAuthCredential?(provider: string, credentialId: string, expectedVersion: number, input: AiConnectionsOAuthCredential): Promise<unknown>;
117
117
  updateProviderCredential?(provider: string, credentialId: string, input: {
118
+ apiKey?: string;
118
119
  expectedVersion: number;
119
120
  label?: string;
120
121
  enabled?: boolean;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/extension-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -216,6 +216,7 @@ function rowBelongsToDocument(descriptor, documentUrl, podUrl, row) {
216
216
 
217
217
  // src/mapping.ts
218
218
  var XSD = "http://www.w3.org/2001/XMLSchema#";
219
+ var ABSOLUTE_URI = /^[a-z][a-z0-9+.-]*:/iu;
219
220
  function tableColumnPredicates(table) {
220
221
  const mapping = table.getMapping();
221
222
  const predicates = new Map;
@@ -403,6 +404,47 @@ function descriptorRowToColumnValues(descriptor, table, row, options) {
403
404
  function isUriArrayField(field) {
404
405
  return field.array === true && field.type === "uri";
405
406
  }
407
+ function normalizeUriArray(value, document) {
408
+ return (Array.isArray(value) ? value : [value]).filter((item) => typeof item === "string" && item.length > 0).map((item) => ABSOLUTE_URI.test(item) ? item : new URL(item, document).toString());
409
+ }
410
+ function normalizeMutationRow(descriptor, table, row, options) {
411
+ const normalized = { ...row };
412
+ const record = descriptorRowToColumnValues(descriptor, table, row, { resourceId: options.resourceId });
413
+ const bindings = fieldBindings(descriptor, table);
414
+ const tables = new Set([table]);
415
+ for (const candidate of Object.values(options.database.schema ?? {})) {
416
+ if (candidate && typeof candidate === "object" && "config" in candidate && "columns" in candidate) {
417
+ tables.add(candidate);
418
+ }
419
+ }
420
+ const tableNameRegistry = new Map;
421
+ const tableRegistry = new Map;
422
+ for (const entry of tables) {
423
+ tableNameRegistry.set(entry.config.name, entry);
424
+ const type = entry.getType();
425
+ tableRegistry.set(type, [...tableRegistry.get(type) ?? [], entry]);
426
+ }
427
+ for (const [field, binding] of bindings) {
428
+ const value = normalized[field];
429
+ if (binding.descriptor.type !== "uri" || value === undefined || value === null)
430
+ continue;
431
+ if (binding.descriptor.array) {
432
+ normalized[field] = normalizeUriArray(value, options.document);
433
+ continue;
434
+ }
435
+ if (!binding.column || typeof value !== "string" || !value || ABSOLUTE_URI.test(value))
436
+ continue;
437
+ const resolver = options.database.getDialect().getUriResolver();
438
+ normalized[field] = resolver.resolveLink(value, table.columns[binding.column], {
439
+ baseUri: options.podUrl,
440
+ tableRegistry,
441
+ tableNameRegistry,
442
+ currentTable: table,
443
+ record
444
+ });
445
+ }
446
+ return normalized;
447
+ }
406
448
 
407
449
  // src/read.ts
408
450
  async function selectSubjectRows(table, database) {
@@ -609,21 +651,14 @@ function reconcilePendingWrites(pending, next, hashOf, writeOnly = EMPTY_WRITE_O
609
651
  }
610
652
  }
611
653
  var EMPTY_WRITE_ONLY = new Set;
612
- function absoluteUri(value, base) {
613
- if (typeof value !== "string" || value.length === 0)
614
- return;
615
- if (/^[a-z][a-z0-9+.-]*:/iu.test(value))
616
- return value;
617
- return new URL(value, base).toString();
618
- }
619
654
  async function writeField(context, fieldName, options) {
620
655
  const field = context.descriptor.fields[fieldName];
621
656
  if (!field || !isUriArrayField(field)) {
622
657
  throw new PodCollectionError("write_field_not_uri_array", `field ${fieldName} is not array+uri; it must go through the ORM`);
623
658
  }
624
659
  const documentUrl = documentOfIri(context.document);
625
- const previous = (Array.isArray(options.previous) ? options.previous : [options.previous]).map((value) => absoluteUri(value, documentUrl)).filter((value) => value !== undefined);
626
- const next = (Array.isArray(options.next) ? options.next : [options.next]).map((value) => absoluteUri(value, documentUrl)).filter((value) => value !== undefined);
660
+ const previous = normalizeUriArray(options.previous, documentUrl);
661
+ const next = normalizeUriArray(options.next, documentUrl);
627
662
  const triples = (iris) => iris.map((iri) => `<${options.subjectIri}> <${field.predicate}> <${iri}> .`).join(`
628
663
  `);
629
664
  const operations = [
@@ -669,10 +704,16 @@ function createMutationHandlers(context) {
669
704
  return {
670
705
  onInsert: async ({ transaction }) => {
671
706
  for (const mutation of transaction.mutations) {
672
- const row = mutation.modified;
673
707
  const key = String(mutation.key);
674
708
  const resourceId = resourceIdForRow(descriptor, context.document, context.podUrl, key);
675
709
  const subjectIri = subjectIriForRow(descriptor, context.document, key);
710
+ const row = normalizeMutationRow(descriptor, table, mutation.modified, {
711
+ database: context.database,
712
+ podUrl: context.podUrl,
713
+ document: context.document,
714
+ resourceId
715
+ });
716
+ Object.assign(mutation.modified, row);
676
717
  pending.register({
677
718
  key,
678
719
  intent: row,
@@ -693,10 +734,16 @@ function createMutationHandlers(context) {
693
734
  for (const mutation of transaction.mutations) {
694
735
  const key = String(mutation.key);
695
736
  const original = mutation.original;
696
- const modified = mutation.modified;
697
737
  const changes = mutation.changes;
698
738
  const resourceId = resourceIdForRow(descriptor, context.document, context.podUrl, key);
699
739
  const subjectIri = subjectIriForRow(descriptor, context.document, key);
740
+ const modified = normalizeMutationRow(descriptor, table, mutation.modified, {
741
+ database: context.database,
742
+ podUrl: context.podUrl,
743
+ document: context.document,
744
+ resourceId
745
+ });
746
+ Object.assign(mutation.modified, modified);
700
747
  const beforeHash = original && Object.keys(original).length > 0 ? hashOf(original) : undefined;
701
748
  pending.register({
702
749
  key,
@@ -706,13 +753,13 @@ function createMutationHandlers(context) {
706
753
  });
707
754
  await runWrite(key, async () => {
708
755
  const columnChanges = {};
709
- for (const [field, value] of Object.entries(changes)) {
756
+ for (const field of Object.keys(changes)) {
710
757
  if (uriArrayFieldNames.has(field))
711
758
  continue;
712
759
  const column = columnOf(field);
713
760
  if (column === undefined)
714
761
  continue;
715
- columnChanges[column] = value;
762
+ columnChanges[column] = modified[field];
716
763
  }
717
764
  if (Object.keys(columnChanges).length > 0) {
718
765
  const updated = await context.database.updateById(table, resourceId, columnChanges);
@@ -1,4 +1,4 @@
1
- import type { AnyPodTable } from '@undefineds.co/drizzle-solid';
1
+ import type { AnyPodTable, SolidDatabase } from '@undefineds.co/drizzle-solid';
2
2
  import type { PodModelDescriptor, PodModelFieldDescriptor } from '@undefineds.co/models';
3
3
  import type { PodSubjectRow, RowOf } from './types.js';
4
4
  /** RDF term(只覆盖本层会产生的两种)。 */
@@ -74,5 +74,20 @@ export declare function descriptorRowToColumnValues(descriptor: PodModelDescript
74
74
  }): Record<string, unknown>;
75
75
  /** 该字段是否走 §4.1 的 PATCH 旁路:`array: true` + `type: 'uri'`。 */
76
76
  export declare function isUriArrayField(field: PodModelFieldDescriptor): boolean;
77
+ /** URI arrays use the existing document-relative PATCH representation. */
78
+ export declare function normalizeUriArray(value: unknown, document: string): string[];
79
+ /**
80
+ * The pending intent must use the same URI representation as the ORM's RDF read.
81
+ * Resolve declared scalar links with the current database's public resolver/schema;
82
+ * array links retain writeField's document-relative PATCH semantics.
83
+ */
84
+ export declare function normalizeMutationRow<D extends PodModelDescriptor, R extends object>(descriptor: D, table: AnyPodTable, row: R, options: {
85
+ database: Pick<SolidDatabase, 'getDialect'> & {
86
+ schema?: Record<string, unknown>;
87
+ };
88
+ podUrl: string;
89
+ document: string;
90
+ resourceId: string;
91
+ }): R;
77
92
  /** descriptor 里所有走 PATCH 旁路的字段。 */
78
93
  export declare function uriArrayFields(descriptor: PodModelDescriptor): string[];
@@ -19,6 +19,7 @@ import { rowKeyOf, rowKeyVariable } from './layout.js';
19
19
  * 交给 drizzle-solid 自己格式化。
20
20
  */
21
21
  const XSD = 'http://www.w3.org/2001/XMLSchema#';
22
+ const ABSOLUTE_URI = /^[a-z][a-z0-9+.-]*:/iu;
22
23
  /** 表列 → 谓词(跳过 `@id` 虚列)。 */
23
24
  export function tableColumnPredicates(table) {
24
25
  const mapping = table.getMapping();
@@ -279,6 +280,56 @@ export function descriptorRowToColumnValues(descriptor, table, row, options) {
279
280
  export function isUriArrayField(field) {
280
281
  return field.array === true && field.type === 'uri';
281
282
  }
283
+ /** URI arrays use the existing document-relative PATCH representation. */
284
+ export function normalizeUriArray(value, document) {
285
+ return (Array.isArray(value) ? value : [value])
286
+ .filter((item) => typeof item === 'string' && item.length > 0)
287
+ .map(item => ABSOLUTE_URI.test(item) ? item : new URL(item, document).toString());
288
+ }
289
+ /**
290
+ * The pending intent must use the same URI representation as the ORM's RDF read.
291
+ * Resolve declared scalar links with the current database's public resolver/schema;
292
+ * array links retain writeField's document-relative PATCH semantics.
293
+ */
294
+ export function normalizeMutationRow(descriptor, table, row, options) {
295
+ const normalized = { ...row };
296
+ const record = descriptorRowToColumnValues(descriptor, table, row, { resourceId: options.resourceId });
297
+ const bindings = fieldBindings(descriptor, table);
298
+ // Registries are derived only from the actual schema registered with this DB.
299
+ const tables = new Set([table]);
300
+ for (const candidate of Object.values(options.database.schema ?? {})) {
301
+ if (candidate && typeof candidate === 'object' && 'config' in candidate && 'columns' in candidate) {
302
+ tables.add(candidate);
303
+ }
304
+ }
305
+ const tableNameRegistry = new Map();
306
+ const tableRegistry = new Map();
307
+ for (const entry of tables) {
308
+ tableNameRegistry.set(entry.config.name, entry);
309
+ const type = entry.getType();
310
+ tableRegistry.set(type, [...(tableRegistry.get(type) ?? []), entry]);
311
+ }
312
+ for (const [field, binding] of bindings) {
313
+ const value = normalized[field];
314
+ if (binding.descriptor.type !== 'uri' || value === undefined || value === null)
315
+ continue;
316
+ if (binding.descriptor.array) {
317
+ normalized[field] = normalizeUriArray(value, options.document);
318
+ continue;
319
+ }
320
+ if (!binding.column || typeof value !== 'string' || !value || ABSOLUTE_URI.test(value))
321
+ continue;
322
+ const resolver = options.database.getDialect().getUriResolver();
323
+ normalized[field] = resolver.resolveLink(value, table.columns[binding.column], {
324
+ baseUri: options.podUrl,
325
+ tableRegistry,
326
+ tableNameRegistry,
327
+ currentTable: table,
328
+ record,
329
+ });
330
+ }
331
+ return normalized;
332
+ }
282
333
  /** descriptor 里所有走 PATCH 旁路的字段。 */
283
334
  export function uriArrayFields(descriptor) {
284
335
  return Object.entries(descriptor.fields)