@opengeni/sdk 3.1.0 → 3.1.2

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 (295) hide show
  1. package/README.md +1037 -71
  2. package/dist/accounts.d.ts +5 -1
  3. package/dist/accounts.js +13 -1
  4. package/dist/accounts.js.map +1 -1
  5. package/dist/adapters/express.d.ts +31 -0
  6. package/dist/adapters/express.js +87 -0
  7. package/dist/adapters/express.js.map +1 -0
  8. package/dist/adapters/hono.d.ts +16 -0
  9. package/dist/adapters/hono.js +8 -0
  10. package/dist/adapters/hono.js.map +1 -0
  11. package/dist/adapters/next.d.ts +29 -0
  12. package/dist/adapters/next.js +22 -0
  13. package/dist/adapters/next.js.map +1 -0
  14. package/dist/allowance-refusal.d.ts +2 -0
  15. package/dist/allowance-refusal.js +50 -0
  16. package/dist/allowance-refusal.js.map +1 -0
  17. package/dist/app-links.d.ts +42 -0
  18. package/dist/app-links.js +66 -0
  19. package/dist/app-links.js.map +1 -0
  20. package/dist/artifact-catalog.d.ts +40 -0
  21. package/dist/artifact-client.d.ts +20 -8
  22. package/dist/artifacts.d.ts +1 -1
  23. package/dist/artifacts.js +8 -6
  24. package/dist/automations.js +8 -2
  25. package/dist/automations.js.map +1 -1
  26. package/dist/azure-live-transport.d.ts +32 -0
  27. package/dist/browser.d.ts +7 -2
  28. package/dist/browser.js +27 -5
  29. package/dist/chat/fold.d.ts +75 -0
  30. package/dist/chat/handler.d.ts +41 -0
  31. package/dist/chat/http.d.ts +67 -0
  32. package/dist/chat/ids.d.ts +34 -0
  33. package/dist/chat/index.d.ts +9 -0
  34. package/dist/chat/index.js +1703 -0
  35. package/dist/chat/index.js.map +1 -0
  36. package/dist/chat/openai.d.ts +30 -0
  37. package/dist/chat/opengeni.d.ts +129 -0
  38. package/dist/chat/types.d.ts +207 -0
  39. package/dist/chat/vercel.d.ts +49 -0
  40. package/dist/chats.d.ts +8 -0
  41. package/dist/chunk-4AHR4KGA.js +301 -0
  42. package/dist/chunk-4AHR4KGA.js.map +1 -0
  43. package/dist/chunk-6HZGAIUE.js +1665 -0
  44. package/dist/chunk-6HZGAIUE.js.map +1 -0
  45. package/dist/chunk-7EWT3PV6.js +368 -0
  46. package/dist/chunk-7EWT3PV6.js.map +1 -0
  47. package/dist/{chunk-VYCTL62C.js → chunk-GZQSWAKD.js} +1 -1
  48. package/dist/chunk-GZQSWAKD.js.map +1 -0
  49. package/dist/chunk-JXXCTW5W.js +1392 -0
  50. package/dist/chunk-JXXCTW5W.js.map +1 -0
  51. package/dist/{chunk-YTAWBDO2.js → chunk-KADZ5PGG.js} +28 -2
  52. package/dist/chunk-KADZ5PGG.js.map +1 -0
  53. package/dist/{chunk-YKZME56C.js → chunk-KWADQ227.js} +3 -3
  54. package/dist/{chunk-YKZME56C.js.map → chunk-KWADQ227.js.map} +1 -1
  55. package/dist/{chunk-7ZXBPJZP.js → chunk-NEM5N6C6.js} +3902 -1457
  56. package/dist/chunk-NEM5N6C6.js.map +1 -0
  57. package/dist/{chunk-GW3EJ2GP.js → chunk-O7KANXC3.js} +86 -13
  58. package/dist/chunk-O7KANXC3.js.map +1 -0
  59. package/dist/{chunk-VUQZAB3O.js → chunk-OAKHWFSV.js} +28 -32
  60. package/dist/chunk-OAKHWFSV.js.map +1 -0
  61. package/dist/chunk-P7UWKORG.js +561 -0
  62. package/dist/chunk-P7UWKORG.js.map +1 -0
  63. package/dist/chunk-QGEIL4TR.js +195 -0
  64. package/dist/chunk-QGEIL4TR.js.map +1 -0
  65. package/dist/chunk-RJHCBZF7.js +270 -0
  66. package/dist/chunk-RJHCBZF7.js.map +1 -0
  67. package/dist/chunk-S3D75WSQ.js +340 -0
  68. package/dist/chunk-S3D75WSQ.js.map +1 -0
  69. package/dist/chunk-UM4ZXXJR.js +9 -0
  70. package/dist/chunk-UM4ZXXJR.js.map +1 -0
  71. package/dist/chunk-VPVMLM7Q.js +15 -0
  72. package/dist/chunk-VPVMLM7Q.js.map +1 -0
  73. package/dist/{chunk-C2H7HBNP.js → chunk-WVCJRSGJ.js} +119 -14
  74. package/dist/chunk-WVCJRSGJ.js.map +1 -0
  75. package/dist/chunk-XA7HVIHI.js +126 -0
  76. package/dist/chunk-XA7HVIHI.js.map +1 -0
  77. package/dist/{chunk-TX3EIENU.js → chunk-YDVPI7WT.js} +73 -2
  78. package/dist/chunk-YDVPI7WT.js.map +1 -0
  79. package/dist/chunk-YKIHNLWD.js +153 -0
  80. package/dist/chunk-YKIHNLWD.js.map +1 -0
  81. package/dist/chunk-ZDUBPV33.js +277 -0
  82. package/dist/chunk-ZDUBPV33.js.map +1 -0
  83. package/dist/client.d.ts +1065 -124
  84. package/dist/codex-realtime-controller.d.ts +13 -0
  85. package/dist/codex-realtime-controller.js +4 -2
  86. package/dist/codex-realtime-v3-wire.d.ts +3 -2
  87. package/dist/codex-realtime.d.ts +5 -1
  88. package/dist/company-brain.d.ts +1 -11
  89. package/dist/company-profile.d.ts +13 -0
  90. package/dist/core.d.ts +7 -2
  91. package/dist/core.js +25 -6
  92. package/dist/deprecation.d.ts +47 -0
  93. package/dist/document-authority-client.d.ts +24 -2
  94. package/dist/document-authority.js +7 -5
  95. package/dist/editable-artifacts/index.d.ts +1 -1
  96. package/dist/editable-artifacts/types.d.ts +12 -2
  97. package/dist/editable-artifacts.js +19 -3
  98. package/dist/editable-artifacts.js.map +1 -1
  99. package/dist/embedding-client.d.ts +129 -0
  100. package/dist/errors.d.ts +76 -1
  101. package/dist/feedback.d.ts +23 -0
  102. package/dist/gateway-realtime-transport.js +1 -1
  103. package/dist/github-repositories.d.ts +10 -0
  104. package/dist/github-repositories.js +36 -0
  105. package/dist/github-repositories.js.map +1 -0
  106. package/dist/index.d.ts +64 -11
  107. package/dist/index.js +566 -141
  108. package/dist/index.js.map +1 -1
  109. package/dist/insights-usage.d.ts +33 -0
  110. package/dist/insights-usage.js +7 -0
  111. package/dist/insights-usage.js.map +1 -0
  112. package/dist/interaction.d.ts +103 -71
  113. package/dist/interaction.js +4 -2
  114. package/dist/knowledge.d.ts +7 -0
  115. package/dist/knowledge.js +20 -0
  116. package/dist/knowledge.js.map +1 -0
  117. package/dist/known-tools.d.ts +4 -0
  118. package/dist/message-links.d.ts +68 -0
  119. package/dist/model-context.d.ts +68 -0
  120. package/dist/model-display.d.ts +2 -0
  121. package/dist/model-display.js +17 -0
  122. package/dist/model-display.js.map +1 -0
  123. package/dist/model-picker-order.d.ts +1 -0
  124. package/dist/model-picker-order.js +10 -0
  125. package/dist/model-picker-order.js.map +1 -0
  126. package/dist/organization-integration-policy.d.ts +26 -0
  127. package/dist/organization-integration-policy.js +26 -0
  128. package/dist/organization-integration-policy.js.map +1 -0
  129. package/dist/pr-review-client.d.ts +1 -1
  130. package/dist/pr-review.d.ts +1 -1
  131. package/dist/pr-review.js.map +1 -1
  132. package/dist/preference-registry.d.ts +1 -1
  133. package/dist/prepared-mcp-setup.d.ts +5 -0
  134. package/dist/proxy.d.ts +13 -11
  135. package/dist/realtime.d.ts +4 -2
  136. package/dist/realtime.js +253 -7
  137. package/dist/realtime.js.map +1 -1
  138. package/dist/session-history-import.d.ts +22 -0
  139. package/dist/session-history-import.js +36 -0
  140. package/dist/session-history-import.js.map +1 -0
  141. package/dist/session-list-entries.d.ts +1 -0
  142. package/dist/session-list-entries.js +7 -0
  143. package/dist/session-list-entries.js.map +1 -0
  144. package/dist/session-message-search.d.ts +53 -0
  145. package/dist/session-proxy-client.d.ts +28 -0
  146. package/dist/session-proxy.d.ts +334 -0
  147. package/dist/session-proxy.js +24 -0
  148. package/dist/session-proxy.js.map +1 -0
  149. package/dist/session-titles.d.ts +2 -0
  150. package/dist/session-usage-limits.d.ts +10 -0
  151. package/dist/session-usage-limits.js +43 -0
  152. package/dist/session-usage-limits.js.map +1 -0
  153. package/dist/shared-catalog-loader.d.ts +34 -0
  154. package/dist/site-bridge-host.d.ts +112 -0
  155. package/dist/site-bridge-host.js +285 -0
  156. package/dist/site-bridge-host.js.map +1 -0
  157. package/dist/site-browser-entry.d.ts +1 -0
  158. package/dist/site-browser-runtime.gen.d.ts +1 -0
  159. package/dist/site-data.d.ts +178 -0
  160. package/dist/site-document.d.ts +5 -0
  161. package/dist/site-document.js +20 -0
  162. package/dist/site-document.js.map +1 -0
  163. package/dist/site-document.test.d.ts +1 -0
  164. package/dist/site-http.d.ts +17 -0
  165. package/dist/site-sql.d.ts +31 -0
  166. package/dist/site-tool-bridge.d.ts +57 -0
  167. package/dist/site.d.ts +182 -0
  168. package/dist/site.js +62 -0
  169. package/dist/site.js.map +1 -0
  170. package/dist/skills.d.ts +75 -0
  171. package/dist/sse.d.ts +1 -1
  172. package/dist/stream.d.ts +2 -0
  173. package/dist/tenant-workspaces.d.ts +47 -0
  174. package/dist/tenant-workspaces.js +8 -0
  175. package/dist/tenant-workspaces.js.map +1 -0
  176. package/dist/tool-auth.d.ts +87 -0
  177. package/dist/tool-auth.js +23 -0
  178. package/dist/tool-auth.js.map +1 -0
  179. package/dist/tool-display-metadata.d.ts +3 -0
  180. package/dist/tools-37DCTCNZ.js +12 -0
  181. package/dist/tools-37DCTCNZ.js.map +1 -0
  182. package/dist/tools.d.ts +81 -0
  183. package/dist/transcription.d.ts +1 -1
  184. package/dist/types.d.ts +2848 -573
  185. package/dist/usage-allowances.d.ts +36 -0
  186. package/dist/usage-allowances.js +80 -0
  187. package/dist/usage-allowances.js.map +1 -0
  188. package/dist/vault-types.d.ts +236 -0
  189. package/dist/vaults-api.d.ts +57 -0
  190. package/dist/workspace-artifacts.d.ts +47 -5
  191. package/dist/workspace-instruction-policies.d.ts +2 -2
  192. package/dist/workspace-integrations.d.ts +74 -0
  193. package/dist/workspace-integrations.js +65 -0
  194. package/dist/workspace-integrations.js.map +1 -0
  195. package/dist/workspace-state.d.ts +8 -71
  196. package/package.json +99 -5
  197. package/src/accounts.ts +18 -0
  198. package/src/adapters/express.ts +136 -0
  199. package/src/adapters/hono.ts +18 -0
  200. package/src/adapters/next.ts +42 -0
  201. package/src/allowance-refusal.ts +5 -0
  202. package/src/app-links.ts +117 -0
  203. package/src/artifact-catalog.ts +51 -0
  204. package/src/artifact-client.ts +40 -49
  205. package/src/artifacts.ts +1 -0
  206. package/src/automations-client.ts +8 -2
  207. package/src/azure-live-transport.ts +286 -0
  208. package/src/browser.ts +37 -0
  209. package/src/chat/fold.ts +431 -0
  210. package/src/chat/handler.ts +240 -0
  211. package/src/chat/http.ts +257 -0
  212. package/src/chat/ids.ts +85 -0
  213. package/src/chat/index.ts +88 -0
  214. package/src/chat/openai.ts +439 -0
  215. package/src/chat/opengeni.ts +737 -0
  216. package/src/chat/types.ts +219 -0
  217. package/src/chat/vercel.ts +204 -0
  218. package/src/chats.ts +16 -0
  219. package/src/client.ts +5822 -1847
  220. package/src/codex-realtime-controller.ts +163 -8
  221. package/src/codex-realtime-v3-wire.ts +4 -3
  222. package/src/codex-realtime-v3.ts +12 -3
  223. package/src/codex-realtime.ts +11 -3
  224. package/src/company-brain.ts +0 -40
  225. package/src/company-profile.ts +13 -0
  226. package/src/core.ts +20 -0
  227. package/src/deprecation.ts +149 -0
  228. package/src/document-authority-client.ts +104 -1
  229. package/src/editable-artifacts/controller.ts +36 -2
  230. package/src/editable-artifacts/index.ts +1 -0
  231. package/src/editable-artifacts/types.ts +29 -16
  232. package/src/embedding-client.ts +519 -0
  233. package/src/errors.ts +303 -4
  234. package/src/feedback.ts +20 -0
  235. package/src/gateway-realtime-transport.ts +3 -3
  236. package/src/github-repositories.ts +60 -0
  237. package/src/index.ts +611 -64
  238. package/src/insights-usage.ts +61 -0
  239. package/src/interaction.ts +274 -93
  240. package/src/knowledge.ts +72 -0
  241. package/src/known-tools.ts +279 -0
  242. package/src/message-links.ts +178 -0
  243. package/src/model-context.ts +113 -0
  244. package/src/model-display.ts +11 -0
  245. package/src/model-picker-order.ts +6 -0
  246. package/src/organization-integration-policy.ts +54 -0
  247. package/src/pr-review-client.ts +1 -1
  248. package/src/pr-review.ts +1 -1
  249. package/src/preference-registry.ts +2 -0
  250. package/src/prepared-mcp-setup.ts +123 -0
  251. package/src/proxy.ts +23 -12
  252. package/src/realtime.ts +16 -6
  253. package/src/session-history-import.ts +84 -0
  254. package/src/session-list-entries.ts +1 -0
  255. package/src/session-message-search.ts +47 -0
  256. package/src/session-proxy-client.ts +68 -0
  257. package/src/session-proxy.ts +2098 -0
  258. package/src/session-titles.ts +10 -0
  259. package/src/session-usage-limits.ts +71 -0
  260. package/src/shared-catalog-loader.ts +169 -0
  261. package/src/site-bridge-host.ts +422 -0
  262. package/src/site-browser-entry.ts +6 -0
  263. package/src/site-browser-runtime.gen.ts +2 -0
  264. package/src/site-data.ts +372 -0
  265. package/src/site-document.test.ts +64 -0
  266. package/src/site-document.ts +18 -0
  267. package/src/site-http.ts +171 -0
  268. package/src/site-sql.ts +153 -0
  269. package/src/site-tool-bridge.ts +401 -0
  270. package/src/site.ts +867 -0
  271. package/src/skills.ts +69 -0
  272. package/src/sse.ts +9 -3
  273. package/src/stream.ts +33 -10
  274. package/src/tenant-workspaces.ts +148 -0
  275. package/src/tool-auth.ts +301 -0
  276. package/src/tool-display-metadata.ts +27 -0
  277. package/src/tools.ts +452 -0
  278. package/src/transcription.ts +3 -0
  279. package/src/types.ts +3216 -581
  280. package/src/usage-allowances.ts +189 -0
  281. package/src/vault-types.ts +293 -0
  282. package/src/vaults-api.ts +288 -0
  283. package/src/workspace-artifacts.ts +55 -6
  284. package/src/workspace-instruction-policies.ts +2 -1
  285. package/src/workspace-integrations.ts +402 -0
  286. package/src/workspace-state.ts +8 -82
  287. package/dist/chunk-7ZXBPJZP.js.map +0 -1
  288. package/dist/chunk-C2H7HBNP.js.map +0 -1
  289. package/dist/chunk-C4DJPZRU.js +0 -119
  290. package/dist/chunk-C4DJPZRU.js.map +0 -1
  291. package/dist/chunk-GW3EJ2GP.js.map +0 -1
  292. package/dist/chunk-TX3EIENU.js.map +0 -1
  293. package/dist/chunk-VUQZAB3O.js.map +0 -1
  294. package/dist/chunk-VYCTL62C.js.map +0 -1
  295. package/dist/chunk-YTAWBDO2.js.map +0 -1
@@ -0,0 +1,10 @@
1
+ export {
2
+ AUTOMATIC_SESSION_TITLE_FALLBACK,
3
+ deriveAutomaticSessionTitlePreview,
4
+ deriveSessionDisplayTitle,
5
+ sessionTitleIsPending,
6
+ } from "@opengeni/contracts/session-titles";
7
+ export type {
8
+ SessionDisplayTitleInput,
9
+ SessionDisplayTitleOptions,
10
+ } from "@opengeni/contracts/session-titles";
@@ -0,0 +1,71 @@
1
+ // @opengeni/sdk/session-usage-limits — per-task usage limit helpers that are
2
+ // safe in browsers and React Native. Reads and writes use the root client's
3
+ // `getSessionUsageLimit` / `setSessionUsageLimit`, or these free functions over
4
+ // any client's `requestJson`.
5
+ //
6
+ // Amounts are integer USD micros of API-equivalent model usage (the
7
+ // pre-margin provider list-price estimate), not your bill. The limit is
8
+ // checked before each new model request: requests already running can take
9
+ // recorded usage above it. See docs/session-usage-limits.md.
10
+ import type { OpenGeniClient, OpenGeniRequestOptions } from "./client";
11
+ import { sessionUsageLimitNotConfirmed } from "./errors";
12
+ import type {
13
+ SessionUsageLimit,
14
+ SetSessionUsageLimitRequest,
15
+ } from "@opengeni/contracts/session-usage-limits";
16
+
17
+ export type {
18
+ SessionUsageLimit,
19
+ SessionUsageLimitHistory,
20
+ SessionUsageLimitReachedRefusal,
21
+ SessionUsageLimitStatus,
22
+ SetSessionUsageLimitRequest,
23
+ } from "@opengeni/contracts/session-usage-limits";
24
+ export {
25
+ formatSessionUsageMicros,
26
+ parseSessionUsageLimitReachedRefusal,
27
+ SESSION_USAGE_LIMIT_GOAL_PAUSED_REASON,
28
+ SESSION_USAGE_LIMIT_SEGMENT,
29
+ sessionUsageLimitReachedMessage,
30
+ } from "@opengeni/contracts/session-usage-limit-refusal";
31
+ export type { ParsedSessionUsageLimitReachedRefusal } from "@opengeni/contracts/session-usage-limit-refusal";
32
+
33
+ type RequestJsonClient = Pick<OpenGeniClient, "requestJson">;
34
+
35
+ function usageLimitPath(workspaceId: string, sessionId: string): string {
36
+ return `/v1/workspaces/${encodeURIComponent(workspaceId)}/sessions/${encodeURIComponent(sessionId)}/usage-limit`;
37
+ }
38
+
39
+ /** Read a task's usage limit state (a child returns its root's, `inherited: true`). */
40
+ export async function getSessionUsageLimit(
41
+ client: RequestJsonClient,
42
+ workspaceId: string,
43
+ sessionId: string,
44
+ options: OpenGeniRequestOptions = {},
45
+ ): Promise<SessionUsageLimit> {
46
+ return await client.requestJson<SessionUsageLimit>(
47
+ "GET",
48
+ usageLimitPath(workspaceId, sessionId),
49
+ undefined,
50
+ {},
51
+ options,
52
+ );
53
+ }
54
+
55
+ /** Compare-and-set a root task's limit; `limitMicros: null` clears it. Never resumes work. */
56
+ export async function setSessionUsageLimit(
57
+ client: RequestJsonClient,
58
+ workspaceId: string,
59
+ sessionId: string,
60
+ request: SetSessionUsageLimitRequest,
61
+ ): Promise<SessionUsageLimit> {
62
+ const committed = await client.requestJson<SessionUsageLimit>(
63
+ "PUT",
64
+ usageLimitPath(workspaceId, sessionId),
65
+ request,
66
+ );
67
+ if (committed.limitMicros !== request.limitMicros) {
68
+ throw sessionUsageLimitNotConfirmed(sessionId);
69
+ }
70
+ return committed;
71
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Single-flight loader for one digest-pinned tool catalog.
3
+ *
4
+ * - Concurrent cold callers share one request instead of each fetching.
5
+ * - A waiter's AbortSignal cancels only its own wait. The shared request is
6
+ * cancelled once every waiter has aborted, and the next caller then starts
7
+ * a new request. A waiter without a signal keeps the shared request alive
8
+ * for every joiner until it settles (bounded by transport/server timeouts).
9
+ * - A failed load is never cached; the next caller starts a new request.
10
+ * - An explicit refresh, or a stale rejection, always obtains a catalog from a
11
+ * request started after that refresh/rejection. Callers rejected on the same
12
+ * stale digest share one post-rejection request while it is still current.
13
+ * - A load started before an invalidation never overwrites the newer state.
14
+ */
15
+ export type SharedCatalogLoader<T extends { digest: string }> = {
16
+ load(options?: { refresh?: boolean; signal?: AbortSignal }): Promise<T>;
17
+ /** Reload after the server rejected `staleDigest` as a stale catalog. */
18
+ reloadAfterStale(staleDigest: string, signal?: AbortSignal): Promise<T>;
19
+ /** Drop the cached catalog when it is `digest`, without fetching. */
20
+ invalidate(digest: string): void;
21
+ };
22
+
23
+ type SharedLoad<T> = {
24
+ epoch: number;
25
+ promise: Promise<T>;
26
+ controller: AbortController;
27
+ waiters: number;
28
+ settled: boolean;
29
+ };
30
+
31
+ export function createSharedCatalogLoader<T extends { digest: string }>(
32
+ /**
33
+ * `refresh` is true when this load follows an invalidation or explicit
34
+ * refresh. `signal` aborts only when every waiter has abandoned the load.
35
+ */
36
+ fetchCatalog: (refresh: boolean, signal: AbortSignal) => Promise<T>,
37
+ ): SharedCatalogLoader<T> {
38
+ let snapshot: T | null = null;
39
+ let epoch = 0;
40
+ let refreshPending = false;
41
+ let inFlight: SharedLoad<T> | null = null;
42
+ let staleReload: {
43
+ staleDigest: string;
44
+ load: SharedLoad<T>;
45
+ settled: T | null;
46
+ } | null = null;
47
+
48
+ const start = (): SharedLoad<T> => {
49
+ if (inFlight && inFlight.epoch === epoch) return inFlight;
50
+ const refresh = refreshPending;
51
+ const load = {
52
+ epoch,
53
+ controller: new AbortController(),
54
+ waiters: 0,
55
+ settled: false,
56
+ } as SharedLoad<T>;
57
+ inFlight = load;
58
+ load.promise = (async () => {
59
+ try {
60
+ const next = await fetchCatalog(refresh, load.controller.signal);
61
+ // An abandoned load (every waiter aborted) never writes shared state,
62
+ // even when its transport ignored the abort and settled late.
63
+ if (load.epoch === epoch && !load.controller.signal.aborted) {
64
+ snapshot = next;
65
+ refreshPending = false;
66
+ }
67
+ return next;
68
+ } finally {
69
+ load.settled = true;
70
+ if (inFlight === load) inFlight = null;
71
+ }
72
+ })();
73
+ // Every waiter observes rejection itself; never leave an unobserved one.
74
+ load.promise.catch(() => undefined);
75
+ return load;
76
+ };
77
+
78
+ const abandon = (load: SharedLoad<T>, reason: unknown): void => {
79
+ if (inFlight === load) inFlight = null;
80
+ if (staleReload?.load === load) staleReload = null;
81
+ load.controller.abort(reason);
82
+ };
83
+
84
+ const wait = (load: SharedLoad<T>, signal: AbortSignal | undefined): Promise<T> => {
85
+ if (signal?.aborted) return Promise.reject(abortReason(signal));
86
+ load.waiters += 1;
87
+ return new Promise<T>((resolve, reject) => {
88
+ let released = false;
89
+ const release = (): boolean => {
90
+ if (released) return false;
91
+ released = true;
92
+ load.waiters -= 1;
93
+ signal?.removeEventListener("abort", onAbort);
94
+ return true;
95
+ };
96
+ const onAbort = () => {
97
+ if (!release()) return;
98
+ const reason = abortReason(signal!);
99
+ reject(reason);
100
+ if (load.waiters === 0 && !load.settled) abandon(load, reason);
101
+ };
102
+ signal?.addEventListener("abort", onAbort, { once: true });
103
+ load.promise.then(
104
+ (value) => {
105
+ if (release()) resolve(value);
106
+ },
107
+ (error: unknown) => {
108
+ if (release()) reject(error);
109
+ },
110
+ );
111
+ });
112
+ };
113
+
114
+ const advance = (): void => {
115
+ snapshot = null;
116
+ epoch += 1;
117
+ refreshPending = true;
118
+ };
119
+
120
+ return {
121
+ async load(options = {}) {
122
+ options.signal?.throwIfAborted();
123
+ if (options.refresh) advance();
124
+ if (snapshot) return snapshot;
125
+ return await wait(start(), options.signal);
126
+ },
127
+ async reloadAfterStale(staleDigest, signal) {
128
+ signal?.throwIfAborted();
129
+ // Share a post-rejection reload while it is still the current load, or
130
+ // while its result is still the current catalog; never reuse a
131
+ // superseded load or result.
132
+ const shared = staleReload;
133
+ if (
134
+ shared?.staleDigest === staleDigest &&
135
+ (shared.settled === null ? shared.load.epoch === epoch : shared.settled === snapshot)
136
+ ) {
137
+ return await wait(shared.load, signal);
138
+ }
139
+ advance();
140
+ const load = start();
141
+ const reload: { staleDigest: string; load: SharedLoad<T>; settled: T | null } = {
142
+ staleDigest,
143
+ load,
144
+ settled: null,
145
+ };
146
+ staleReload = reload;
147
+ load.promise.then(
148
+ (next) => {
149
+ // The same digest again proves nothing newer; a later rejection must
150
+ // start its own request rather than reuse this one.
151
+ if (staleReload !== reload) return;
152
+ if (next.digest === staleDigest) staleReload = null;
153
+ else reload.settled = next;
154
+ },
155
+ () => {
156
+ if (staleReload === reload) staleReload = null;
157
+ },
158
+ );
159
+ return await wait(load, signal);
160
+ },
161
+ invalidate(digest) {
162
+ if (snapshot?.digest === digest) advance();
163
+ },
164
+ };
165
+ }
166
+
167
+ function abortReason(signal: AbortSignal): unknown {
168
+ return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
169
+ }
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Framework-free host side of the Site MessagePort bridge. The console's
3
+ * `PublishedHtmlArtifactFrame` and the published-Site gateway shell both run
4
+ * this code; each injects its own tool transport, Site HTTP handler, viewer
5
+ * provider and sign-in action.
6
+ *
7
+ * Protocol (v2): the host posts a ready message with a bootstrap port to the
8
+ * frame window once per document load; each SDK client connects through that
9
+ * port with its own MessagePort. Requests are tracked per port so a cancel or
10
+ * a document teardown aborts exactly the requests it owns. A second load of
11
+ * the same frame (a navigation inside the Site) revokes the bridge instead of
12
+ * handing a fresh port to an unknown document.
13
+ */
14
+ import type { SiteViewer } from "@opengeni/contracts";
15
+ import {
16
+ OPENGENI_SITE_BRIDGE_BOOTSTRAP_GLOBAL,
17
+ OPENGENI_SITE_BRIDGE_READY,
18
+ OPENGENI_SITE_BRIDGE_RESPONSE,
19
+ OPENGENI_SITE_BRIDGE_SIGN_IN_METHOD,
20
+ OPENGENI_SITE_BRIDGE_VERSION,
21
+ OPENGENI_SITE_BRIDGE_VIEWER_METHOD,
22
+ isOpenGeniSiteBridgeCancelMessage,
23
+ isOpenGeniSiteBridgeConnectMessage,
24
+ isOpenGeniSiteBridgeRequestMessage,
25
+ sanitizeOpenGeniSiteToolCallRequest,
26
+ sanitizeOpenGeniSiteToolInvokeRequest,
27
+ type OpenGeniSiteBridgeReadyMessage,
28
+ type OpenGeniSiteBridgeRequestMessage,
29
+ type OpenGeniSiteBridgeResponseMessage,
30
+ type OpenGeniSiteSignInResult,
31
+ type OpenGeniSiteToolCatalog,
32
+ } from "./site";
33
+ import { isSiteHttpRequest, serveSiteHttp, type SiteHttpRequest } from "./site-http";
34
+ import type {
35
+ ToolGatewayCallRequest,
36
+ ToolGatewayCallResponse,
37
+ ToolGatewayDeclarationsResponse,
38
+ ToolGatewayInvokeRequest,
39
+ ToolGatewayInvokeResponse,
40
+ ToolGatewayResolveRequest,
41
+ ToolGatewayResolvedTool,
42
+ } from "./types";
43
+
44
+ export type { SiteHttpRequest } from "./site-http";
45
+
46
+ /** Tool and Site HTTP handlers a host supplies. Credentials stay inside these functions. */
47
+ export type SiteBridgeToolHandlers = {
48
+ resolve?: (
49
+ request: ToolGatewayResolveRequest,
50
+ options: { signal: AbortSignal },
51
+ ) => Promise<ToolGatewayResolvedTool>;
52
+ invoke?: (
53
+ request: ToolGatewayInvokeRequest,
54
+ options: { signal: AbortSignal },
55
+ ) => Promise<ToolGatewayInvokeResponse>;
56
+ /** Serves the ordinary SDK's HTTP requests (sessions, Site Data) over the Site HTTP channel. */
57
+ fetch?: (request: SiteHttpRequest, signal: AbortSignal) => Promise<Response>;
58
+ /**
59
+ * Advertise Site Data and Site SQL: `fetch` serves `published-artifacts/current`
60
+ * routes (data, files and `sql/queries/<name>`) for the pinned Site.
61
+ */
62
+ siteData?: boolean;
63
+ catalog: (options: { signal: AbortSignal }) => Promise<OpenGeniSiteToolCatalog>;
64
+ call: (
65
+ request: ToolGatewayCallRequest,
66
+ options: { signal: AbortSignal },
67
+ ) => Promise<ToolGatewayCallResponse>;
68
+ declarations?: (options: { signal: AbortSignal }) => Promise<ToolGatewayDeclarationsResponse>;
69
+ };
70
+
71
+ /** Viewer identity and sign-in, answered by the host itself (advertised as `viewer: 1`). */
72
+ export type SiteBridgeViewerHandlers = {
73
+ viewer: (options: { signal: AbortSignal }) => Promise<SiteViewer> | SiteViewer;
74
+ /** Defaults to `{ started: false }`: the viewer is already signed in or the host has no sign-in. */
75
+ signIn?: (options: {
76
+ signal: AbortSignal;
77
+ }) => Promise<OpenGeniSiteSignInResult> | OpenGeniSiteSignInResult;
78
+ };
79
+
80
+ export type SiteBridgeRequestValue =
81
+ | OpenGeniSiteToolCatalog
82
+ | ToolGatewayCallResponse
83
+ | ToolGatewayDeclarationsResponse
84
+ | ToolGatewayResolvedTool
85
+ | ToolGatewayInvokeResponse
86
+ | SiteViewer
87
+ | OpenGeniSiteSignInResult;
88
+
89
+ /** Capabilities the host advertises when a Site connects; Sites never ask for more. */
90
+ export function siteBridgeReadyMessage(
91
+ bridge: SiteBridgeToolHandlers | undefined,
92
+ viewer?: SiteBridgeViewerHandlers,
93
+ ): OpenGeniSiteBridgeReadyMessage {
94
+ return {
95
+ type: OPENGENI_SITE_BRIDGE_READY,
96
+ version: OPENGENI_SITE_BRIDGE_VERSION,
97
+ ...(bridge?.resolve && bridge?.invoke ? { targetTools: 1 as const } : {}),
98
+ ...(bridge?.fetch && bridge.siteData ? { siteData: 1 as const } : {}),
99
+ ...(viewer ? { viewer: 1 as const } : {}),
100
+ };
101
+ }
102
+
103
+ /**
104
+ * The inline script a host puts at the top of Site HTML. It accepts exactly
105
+ * one ready message with one port from the parent window and keeps that port
106
+ * on a window global so SDK clients created later can still connect.
107
+ */
108
+ export const SITE_BRIDGE_BOOTSTRAP_SCRIPT = `<script>(()=>{const key=${JSON.stringify(
109
+ OPENGENI_SITE_BRIDGE_BOOTSTRAP_GLOBAL,
110
+ )};const parentWindow=window.parent;if(parentWindow===window)return;window.addEventListener("message",event=>{const data=event.data;if(event.source!==parentWindow||!data||data.type!==${JSON.stringify(
111
+ OPENGENI_SITE_BRIDGE_READY,
112
+ )}||data.version!==${OPENGENI_SITE_BRIDGE_VERSION}||event.ports.length!==1)return;const port=event.ports[0];const previous=window[key]?.port;if(previous&&previous!==port)previous.close();Object.defineProperty(window,key,{configurable:true,value:{port}});port.start();});})();</script>`;
113
+
114
+ /** Insert the bootstrap script right after a leading doctype (or at the start). */
115
+ export function injectSiteBridgeBootstrap(html: string): string {
116
+ const doctypeEnd = leadingDoctypeEnd(html);
117
+ if (doctypeEnd >= 0) {
118
+ return `${html.slice(0, doctypeEnd + 1)}${SITE_BRIDGE_BOOTSTRAP_SCRIPT}${html.slice(doctypeEnd + 1)}`;
119
+ }
120
+ return `${SITE_BRIDGE_BOOTSTRAP_SCRIPT}${html}`;
121
+ }
122
+
123
+ function leadingDoctypeEnd(html: string): number {
124
+ const lower = html.toLowerCase();
125
+ let cursor = html.charCodeAt(0) === 0xfeff ? 1 : 0;
126
+ while (cursor < html.length) {
127
+ while (/\s/.test(html[cursor] ?? "")) cursor += 1;
128
+ if (!html.startsWith("<!--", cursor)) break;
129
+ const commentEnd = html.indexOf("-->", cursor + 4);
130
+ if (commentEnd < 0) return -1;
131
+ cursor = commentEnd + 3;
132
+ }
133
+ if (!lower.startsWith("<!doctype", cursor)) return -1;
134
+ return html.indexOf(">", cursor + 9);
135
+ }
136
+
137
+ export type SiteBridgeHostSource = {
138
+ /** Read on every request, so a host can swap handlers without reconnecting the Site. */
139
+ tools: () => SiteBridgeToolHandlers | undefined;
140
+ viewer?: () => SiteBridgeViewerHandlers | undefined;
141
+ };
142
+
143
+ /**
144
+ * One frame's bridge host. Call `load(frame.contentWindow)` from the frame's
145
+ * load event and `close()` when the frame goes away.
146
+ */
147
+ export class SiteBridgeHost {
148
+ private readonly requests = new SiteBridgeRequestRegistry();
149
+ private readonly lease: SiteBridgeDocumentLease;
150
+
151
+ constructor(
152
+ private readonly source: SiteBridgeHostSource,
153
+ options: { createMessageChannel?: () => MessageChannel } = {},
154
+ ) {
155
+ this.lease = new SiteBridgeDocumentLease(
156
+ (data, ports) => this.attachToolPort(data, ports),
157
+ () => this.requests.closeAll(),
158
+ options.createMessageChannel,
159
+ );
160
+ }
161
+
162
+ /** Offer the bootstrap port to a freshly loaded document. Returns false (and revokes) on reload. */
163
+ load(frameWindow: Pick<Window, "postMessage">): boolean {
164
+ return this.lease.load(frameWindow);
165
+ }
166
+
167
+ close(): void {
168
+ this.lease.close();
169
+ }
170
+
171
+ private attachToolPort(data: unknown, ports: readonly MessagePort[]): void {
172
+ const port = openGeniSiteBridgePortFromBootstrap(data, ports);
173
+ if (!port) return;
174
+ const requests = this.requests;
175
+ requests.addPort(port);
176
+ port.addEventListener("message", (portEvent: MessageEvent<unknown>) => {
177
+ const message = portEvent.data;
178
+ if (isOpenGeniSiteBridgeCancelMessage(message)) {
179
+ requests.cancel(port, message.requestId);
180
+ return;
181
+ }
182
+ if (isSiteHttpRequest(message) && portEvent.ports.length === 1) {
183
+ const controller = requests.start(port, message.requestId);
184
+ if (!controller) {
185
+ portEvent.ports[0]!.close();
186
+ return;
187
+ }
188
+ void serveSiteHttp(
189
+ message,
190
+ portEvent.ports[0]!,
191
+ async (request, signal) => {
192
+ const fetch = this.source.tools()?.fetch;
193
+ if (!fetch) throw new Error("This Site host does not support the session SDK");
194
+ return fetch(request, signal);
195
+ },
196
+ controller.signal,
197
+ ).finally(() => requests.complete(port, message.requestId, controller));
198
+ return;
199
+ }
200
+ if (!isOpenGeniSiteBridgeRequestMessage(message)) return;
201
+ const controller = requests.start(port, message.requestId);
202
+ if (!controller) {
203
+ port.postMessage({
204
+ type: OPENGENI_SITE_BRIDGE_RESPONSE,
205
+ version: OPENGENI_SITE_BRIDGE_VERSION,
206
+ requestId: message.requestId,
207
+ ok: false,
208
+ error: {
209
+ code: "duplicate_request_id",
210
+ message: "A Site tool request with this id is already running",
211
+ retryable: false,
212
+ },
213
+ } satisfies OpenGeniSiteBridgeResponseMessage);
214
+ return;
215
+ }
216
+ void handleSiteBridgeRequest(
217
+ this.source.tools(),
218
+ message,
219
+ controller.signal,
220
+ this.source.viewer?.(),
221
+ )
222
+ .then((value) => {
223
+ if (!controller.signal.aborted) {
224
+ port.postMessage({
225
+ type: OPENGENI_SITE_BRIDGE_RESPONSE,
226
+ version: OPENGENI_SITE_BRIDGE_VERSION,
227
+ requestId: message.requestId,
228
+ ok: true,
229
+ value,
230
+ } satisfies OpenGeniSiteBridgeResponseMessage);
231
+ }
232
+ })
233
+ .catch((error) => {
234
+ if (!controller.signal.aborted) {
235
+ port.postMessage({
236
+ type: OPENGENI_SITE_BRIDGE_RESPONSE,
237
+ version: OPENGENI_SITE_BRIDGE_VERSION,
238
+ requestId: message.requestId,
239
+ ok: false,
240
+ error: siteBridgeError(error, message.method === "call"),
241
+ } satisfies OpenGeniSiteBridgeResponseMessage);
242
+ }
243
+ })
244
+ .finally(() => requests.complete(port, message.requestId, controller));
245
+ });
246
+ port.addEventListener("messageerror", () => requests.closePort(port));
247
+ port.start();
248
+ port.postMessage(siteBridgeReadyMessage(this.source.tools(), this.source.viewer?.()));
249
+ }
250
+ }
251
+
252
+ export class SiteBridgeRequestRegistry {
253
+ private readonly controllersByPort = new Map<MessagePort, Map<string, AbortController>>();
254
+
255
+ addPort(port: MessagePort): void {
256
+ // Several SDK clients may share one document bootstrap. Document teardown,
257
+ // not another client connecting, revokes all of that document's ports.
258
+ if (!this.controllersByPort.has(port)) this.controllersByPort.set(port, new Map());
259
+ }
260
+
261
+ start(port: MessagePort, requestId: string): AbortController | null {
262
+ const controllers = this.controllersByPort.get(port);
263
+ if (!controllers || controllers.has(requestId)) return null;
264
+ const controller = new AbortController();
265
+ controllers.set(requestId, controller);
266
+ return controller;
267
+ }
268
+
269
+ cancel(port: MessagePort, requestId: string): void {
270
+ const controllers = this.controllersByPort.get(port);
271
+ const controller = controllers?.get(requestId);
272
+ if (!controller) return;
273
+ controller.abort(new Error("Site tool request cancelled"));
274
+ controllers!.delete(requestId);
275
+ }
276
+
277
+ complete(port: MessagePort, requestId: string, controller: AbortController): void {
278
+ const controllers = this.controllersByPort.get(port);
279
+ if (controllers?.get(requestId) === controller) controllers.delete(requestId);
280
+ }
281
+
282
+ closePort(port: MessagePort): void {
283
+ const controllers = this.controllersByPort.get(port);
284
+ if (!controllers) return;
285
+ for (const controller of controllers.values()) {
286
+ controller.abort(new Error("Site tool bridge port closed"));
287
+ }
288
+ controllers.clear();
289
+ this.controllersByPort.delete(port);
290
+ port.close();
291
+ }
292
+
293
+ closeAll(): void {
294
+ for (const port of [...this.controllersByPort.keys()]) this.closePort(port);
295
+ }
296
+ }
297
+
298
+ export class SiteBridgeDocumentLease {
299
+ private loaded = false;
300
+ private bootstrapPort: MessagePort | null = null;
301
+
302
+ constructor(
303
+ private readonly attachToolPort: (data: unknown, ports: readonly MessagePort[]) => void,
304
+ private readonly closeActivePorts: () => void,
305
+ private readonly createMessageChannel: () => MessageChannel = () => new MessageChannel(),
306
+ ) {}
307
+
308
+ load(frameWindow: Pick<Window, "postMessage">): boolean {
309
+ if (this.loaded) {
310
+ this.close();
311
+ return false;
312
+ }
313
+ this.loaded = true;
314
+ const channel = this.createMessageChannel();
315
+ this.bootstrapPort = channel.port1;
316
+ channel.port1.addEventListener("message", (event: MessageEvent<unknown>) => {
317
+ this.attachToolPort(event.data, event.ports);
318
+ });
319
+ channel.port1.start();
320
+ frameWindow.postMessage(
321
+ {
322
+ type: OPENGENI_SITE_BRIDGE_READY,
323
+ version: OPENGENI_SITE_BRIDGE_VERSION,
324
+ },
325
+ "*",
326
+ [channel.port2],
327
+ );
328
+ return true;
329
+ }
330
+
331
+ close(): void {
332
+ this.bootstrapPort?.close();
333
+ this.bootstrapPort = null;
334
+ this.closeActivePorts();
335
+ }
336
+ }
337
+
338
+ export function openGeniSiteBridgePortFromBootstrap(
339
+ data: unknown,
340
+ ports: readonly MessagePort[],
341
+ ): MessagePort | null {
342
+ if (!isOpenGeniSiteBridgeConnectMessage(data)) return null;
343
+ return ports.length === 1 ? ports[0]! : null;
344
+ }
345
+
346
+ export async function handleSiteBridgeRequest(
347
+ bridge: SiteBridgeToolHandlers | undefined,
348
+ message: OpenGeniSiteBridgeRequestMessage,
349
+ signal: AbortSignal,
350
+ viewer?: SiteBridgeViewerHandlers,
351
+ ): Promise<SiteBridgeRequestValue> {
352
+ if (message.method === OPENGENI_SITE_BRIDGE_VIEWER_METHOD) {
353
+ if (!viewer) throw siteBridgeHostError("viewer_unavailable", "This Site host has no viewer");
354
+ return await viewer.viewer({ signal });
355
+ }
356
+ if (message.method === OPENGENI_SITE_BRIDGE_SIGN_IN_METHOD) {
357
+ if (!viewer?.signIn) return { started: false };
358
+ const result = await viewer.signIn({ signal });
359
+ return { started: result?.started === true };
360
+ }
361
+ if (!bridge) throw new Error("Site tool bridge is unavailable");
362
+ if (message.method === "resolve") {
363
+ if (!bridge.resolve) throw new Error("Targeted Site tools are unavailable");
364
+ return await bridge.resolve({ target: message.payload.target }, { signal });
365
+ }
366
+ if (message.method === "invoke") {
367
+ if (!bridge.invoke) throw new Error("Targeted Site tools are unavailable");
368
+ return await bridge.invoke(sanitizeOpenGeniSiteToolInvokeRequest(message.payload), { signal });
369
+ }
370
+ if (message.method === "catalog") return await bridge.catalog({ signal });
371
+ if (message.method === "declarations") {
372
+ if (!bridge.declarations) throw new Error("Site tool declarations are unavailable");
373
+ return await bridge.declarations({ signal });
374
+ }
375
+ return await bridge.call(sanitizeOpenGeniSiteToolCallRequest(message.payload!), { signal });
376
+ }
377
+
378
+ function siteBridgeHostError(code: string, message: string): Error & { code: string } {
379
+ return Object.assign(new Error(message), { code });
380
+ }
381
+
382
+ export function siteBridgeError(
383
+ error: unknown,
384
+ legacyCall = false,
385
+ ): {
386
+ code: string;
387
+ message: string;
388
+ retryable: boolean;
389
+ outcomeUnknown: boolean;
390
+ } {
391
+ const candidate = error as {
392
+ code?: unknown;
393
+ message?: unknown;
394
+ retryable?: unknown;
395
+ outcomeUnknown?: unknown;
396
+ details?: { code?: unknown };
397
+ };
398
+ const nestedCode = candidate?.details?.code;
399
+ const knownPreexecution = candidate?.retryable === true && candidate?.outcomeUnknown === false;
400
+ const stale =
401
+ knownPreexecution && (nestedCode === "tool_definition_stale" || nestedCode === "catalog_stale");
402
+ // Historical saved bundles inspect code alone. A stale-named transport error
403
+ // without known pre-execution proof must remain an error, not invite replay.
404
+ const unsafeControl =
405
+ legacyCall &&
406
+ !knownPreexecution &&
407
+ (candidate?.code === "catalog_stale" || candidate?.code === "tool_definition_stale");
408
+ const message =
409
+ typeof candidate?.message === "string" ? candidate.message : "Site tool request failed";
410
+ return {
411
+ code: stale
412
+ ? (nestedCode as string)
413
+ : unsafeControl
414
+ ? "site_tool_call_failed"
415
+ : typeof candidate?.code === "string"
416
+ ? candidate.code
417
+ : "site_tool_call_failed",
418
+ message: unsafeControl ? `[${candidate.code}] ${message}` : message,
419
+ retryable: candidate?.retryable === true,
420
+ outcomeUnknown: candidate?.outcomeUnknown === true,
421
+ };
422
+ }
@@ -0,0 +1,6 @@
1
+ import { createOpenGeniSiteClient } from "./site";
2
+
3
+ Object.defineProperty(globalThis, "createOpenGeniSiteClient", {
4
+ configurable: true,
5
+ value: createOpenGeniSiteClient,
6
+ });