@opengeni/sdk 3.3.2 → 3.4.0

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 (299) hide show
  1. package/README.md +1007 -72
  2. package/dist/adapters/express.d.ts +31 -0
  3. package/dist/adapters/express.js +87 -0
  4. package/dist/adapters/express.js.map +1 -0
  5. package/dist/adapters/hono.d.ts +16 -0
  6. package/dist/adapters/hono.js +8 -0
  7. package/dist/adapters/hono.js.map +1 -0
  8. package/dist/adapters/next.d.ts +29 -0
  9. package/dist/adapters/next.js +22 -0
  10. package/dist/adapters/next.js.map +1 -0
  11. package/dist/allowance-refusal.d.ts +2 -0
  12. package/dist/allowance-refusal.js +50 -0
  13. package/dist/allowance-refusal.js.map +1 -0
  14. package/dist/app-links.d.ts +42 -0
  15. package/dist/app-links.js +66 -0
  16. package/dist/app-links.js.map +1 -0
  17. package/dist/artifact-catalog.d.ts +40 -0
  18. package/dist/artifact-client.d.ts +20 -8
  19. package/dist/artifacts.d.ts +1 -1
  20. package/dist/artifacts.js +8 -6
  21. package/dist/automations.js +8 -2
  22. package/dist/automations.js.map +1 -1
  23. package/dist/azure-live-transport.d.ts +32 -0
  24. package/dist/browser.d.ts +7 -2
  25. package/dist/browser.js +27 -5
  26. package/dist/chat/fold.d.ts +75 -0
  27. package/dist/chat/handler.d.ts +41 -0
  28. package/dist/chat/http.d.ts +67 -0
  29. package/dist/chat/ids.d.ts +34 -0
  30. package/dist/chat/index.d.ts +9 -0
  31. package/dist/chat/index.js +1369 -0
  32. package/dist/chat/index.js.map +1 -0
  33. package/dist/chat/openai.d.ts +30 -0
  34. package/dist/chat/opengeni.d.ts +129 -0
  35. package/dist/chat/types.d.ts +207 -0
  36. package/dist/chat/vercel.d.ts +49 -0
  37. package/dist/chats.d.ts +8 -0
  38. package/dist/chunk-4AHR4KGA.js +301 -0
  39. package/dist/chunk-4AHR4KGA.js.map +1 -0
  40. package/dist/chunk-6GYGMLX4.js +332 -0
  41. package/dist/chunk-6GYGMLX4.js.map +1 -0
  42. package/dist/chunk-7EWT3PV6.js +368 -0
  43. package/dist/chunk-7EWT3PV6.js.map +1 -0
  44. package/dist/chunk-FTWVVPR6.js +1665 -0
  45. package/dist/chunk-FTWVVPR6.js.map +1 -0
  46. package/dist/{chunk-VYCTL62C.js → chunk-GZQSWAKD.js} +1 -1
  47. package/dist/chunk-GZQSWAKD.js.map +1 -0
  48. package/dist/chunk-KGEABAID.js +71 -0
  49. package/dist/chunk-KGEABAID.js.map +1 -0
  50. package/dist/{chunk-YKZME56C.js → chunk-KWADQ227.js} +3 -3
  51. package/dist/{chunk-YKZME56C.js.map → chunk-KWADQ227.js.map} +1 -1
  52. package/dist/{chunk-JUS2UYRP.js → chunk-NCU5OAN7.js} +27 -2
  53. package/dist/chunk-NCU5OAN7.js.map +1 -0
  54. package/dist/{chunk-YXQX7RQT.js → chunk-OI5YLASH.js} +73 -2
  55. package/dist/chunk-OI5YLASH.js.map +1 -0
  56. package/dist/chunk-OV3POE62.js +359 -0
  57. package/dist/chunk-OV3POE62.js.map +1 -0
  58. package/dist/chunk-P7UWKORG.js +561 -0
  59. package/dist/chunk-P7UWKORG.js.map +1 -0
  60. package/dist/chunk-QGEIL4TR.js +195 -0
  61. package/dist/chunk-QGEIL4TR.js.map +1 -0
  62. package/dist/{chunk-TRNI7KEO.js → chunk-S2NLS3ES.js} +3669 -1281
  63. package/dist/chunk-S2NLS3ES.js.map +1 -0
  64. package/dist/chunk-UM4ZXXJR.js +9 -0
  65. package/dist/chunk-UM4ZXXJR.js.map +1 -0
  66. package/dist/{chunk-QT3KFVDP.js → chunk-UME3X7CP.js} +28 -32
  67. package/dist/chunk-UME3X7CP.js.map +1 -0
  68. package/dist/chunk-VT4YM4XQ.js +210 -0
  69. package/dist/chunk-VT4YM4XQ.js.map +1 -0
  70. package/dist/chunk-WSYYXONG.js +1602 -0
  71. package/dist/chunk-WSYYXONG.js.map +1 -0
  72. package/dist/{chunk-C2H7HBNP.js → chunk-WVCJRSGJ.js} +119 -14
  73. package/dist/chunk-WVCJRSGJ.js.map +1 -0
  74. package/dist/chunk-X5ITQVXE.js +15 -0
  75. package/dist/chunk-X5ITQVXE.js.map +1 -0
  76. package/dist/chunk-XA7HVIHI.js +126 -0
  77. package/dist/chunk-XA7HVIHI.js.map +1 -0
  78. package/dist/{chunk-KKRO2VCP.js → chunk-YJCHB7R3.js} +60 -12
  79. package/dist/chunk-YJCHB7R3.js.map +1 -0
  80. package/dist/chunk-YKIHNLWD.js +153 -0
  81. package/dist/chunk-YKIHNLWD.js.map +1 -0
  82. package/dist/chunk-ZDUBPV33.js +277 -0
  83. package/dist/chunk-ZDUBPV33.js.map +1 -0
  84. package/dist/client.d.ts +1068 -118
  85. package/dist/codex-realtime-controller.d.ts +13 -0
  86. package/dist/codex-realtime-controller.js +4 -2
  87. package/dist/codex-realtime-v3-wire.d.ts +3 -2
  88. package/dist/codex-realtime.d.ts +5 -1
  89. package/dist/company-brain.d.ts +1 -11
  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/external-vault-receiver.d.ts +203 -0
  102. package/dist/external-vault-receiver.js +564 -0
  103. package/dist/external-vault-receiver.js.map +1 -0
  104. package/dist/feedback.d.ts +23 -0
  105. package/dist/gateway-realtime-transport.js +1 -1
  106. package/dist/github-repositories.d.ts +10 -0
  107. package/dist/github-repositories.js +36 -0
  108. package/dist/github-repositories.js.map +1 -0
  109. package/dist/index.d.ts +62 -11
  110. package/dist/index.js +557 -338
  111. package/dist/index.js.map +1 -1
  112. package/dist/insights-usage.d.ts +33 -0
  113. package/dist/insights-usage.js +7 -0
  114. package/dist/insights-usage.js.map +1 -0
  115. package/dist/interaction.d.ts +165 -71
  116. package/dist/interaction.js +2 -2
  117. package/dist/knowledge.d.ts +7 -0
  118. package/dist/knowledge.js +20 -0
  119. package/dist/knowledge.js.map +1 -0
  120. package/dist/known-tools.d.ts +4 -0
  121. package/dist/message-links.d.ts +68 -0
  122. package/dist/model-context.d.ts +68 -0
  123. package/dist/model-display.d.ts +2 -0
  124. package/dist/model-display.js +17 -0
  125. package/dist/model-display.js.map +1 -0
  126. package/dist/model-picker-order.d.ts +1 -0
  127. package/dist/model-picker-order.js +10 -0
  128. package/dist/model-picker-order.js.map +1 -0
  129. package/dist/organization-integration-policy.d.ts +26 -0
  130. package/dist/organization-integration-policy.js +26 -0
  131. package/dist/organization-integration-policy.js.map +1 -0
  132. package/dist/pr-review-client.d.ts +1 -1
  133. package/dist/pr-review.d.ts +1 -1
  134. package/dist/pr-review.js.map +1 -1
  135. package/dist/preference-registry.d.ts +1 -1
  136. package/dist/prepared-mcp-setup.d.ts +5 -0
  137. package/dist/proxy.d.ts +13 -11
  138. package/dist/realtime.d.ts +1 -1
  139. package/dist/realtime.js +253 -7
  140. package/dist/realtime.js.map +1 -1
  141. package/dist/session-history-import.d.ts +22 -0
  142. package/dist/session-history-import.js +36 -0
  143. package/dist/session-history-import.js.map +1 -0
  144. package/dist/session-list-entries.d.ts +1 -0
  145. package/dist/session-list-entries.js +7 -0
  146. package/dist/session-list-entries.js.map +1 -0
  147. package/dist/session-message-search.d.ts +53 -0
  148. package/dist/session-proxy-client.d.ts +28 -0
  149. package/dist/session-proxy.d.ts +334 -0
  150. package/dist/session-proxy.js +24 -0
  151. package/dist/session-proxy.js.map +1 -0
  152. package/dist/session-titles.d.ts +2 -28
  153. package/dist/session-usage-limits.d.ts +10 -0
  154. package/dist/session-usage-limits.js +43 -0
  155. package/dist/session-usage-limits.js.map +1 -0
  156. package/dist/shared-catalog-loader.d.ts +34 -0
  157. package/dist/site-ai.d.ts +35 -0
  158. package/dist/site-bridge-host.d.ts +112 -0
  159. package/dist/site-bridge-host.js +286 -0
  160. package/dist/site-bridge-host.js.map +1 -0
  161. package/dist/site-browser-entry.d.ts +1 -0
  162. package/dist/site-browser-runtime.gen.d.ts +1 -0
  163. package/dist/site-data.d.ts +178 -0
  164. package/dist/site-document.d.ts +5 -0
  165. package/dist/site-document.js +20 -0
  166. package/dist/site-document.js.map +1 -0
  167. package/dist/site-document.test.d.ts +1 -0
  168. package/dist/site-http.d.ts +17 -0
  169. package/dist/site-sql.d.ts +31 -0
  170. package/dist/site-tool-bridge.d.ts +57 -0
  171. package/dist/site.d.ts +186 -0
  172. package/dist/site.js +63 -0
  173. package/dist/site.js.map +1 -0
  174. package/dist/skills.d.ts +75 -0
  175. package/dist/sse.d.ts +1 -1
  176. package/dist/stream.d.ts +2 -0
  177. package/dist/tenant-workspaces.d.ts +47 -0
  178. package/dist/tenant-workspaces.js +8 -0
  179. package/dist/tenant-workspaces.js.map +1 -0
  180. package/dist/tool-auth.d.ts +87 -0
  181. package/dist/tool-auth.js +23 -0
  182. package/dist/tool-auth.js.map +1 -0
  183. package/dist/tool-display-metadata.d.ts +3 -0
  184. package/dist/tools-37DCTCNZ.js +12 -0
  185. package/dist/tools-37DCTCNZ.js.map +1 -0
  186. package/dist/tools.d.ts +81 -0
  187. package/dist/transcription.d.ts +1 -1
  188. package/dist/types.d.ts +2807 -583
  189. package/dist/usage-allowances.d.ts +36 -0
  190. package/dist/usage-allowances.js +80 -0
  191. package/dist/usage-allowances.js.map +1 -0
  192. package/dist/vault-types.d.ts +324 -0
  193. package/dist/vaults-api.d.ts +65 -0
  194. package/dist/workspace-artifacts.d.ts +47 -5
  195. package/dist/workspace-instruction-policies.d.ts +2 -2
  196. package/dist/workspace-integrations.d.ts +74 -0
  197. package/dist/workspace-integrations.js +67 -0
  198. package/dist/workspace-integrations.js.map +1 -0
  199. package/dist/workspace-state.d.ts +8 -71
  200. package/package.json +103 -5
  201. package/src/adapters/express.ts +136 -0
  202. package/src/adapters/hono.ts +18 -0
  203. package/src/adapters/next.ts +42 -0
  204. package/src/allowance-refusal.ts +5 -0
  205. package/src/app-links.ts +117 -0
  206. package/src/artifact-catalog.ts +51 -0
  207. package/src/artifact-client.ts +40 -49
  208. package/src/artifacts.ts +1 -0
  209. package/src/automations-client.ts +8 -2
  210. package/src/azure-live-transport.ts +286 -0
  211. package/src/browser.ts +37 -0
  212. package/src/chat/fold.ts +431 -0
  213. package/src/chat/handler.ts +240 -0
  214. package/src/chat/http.ts +257 -0
  215. package/src/chat/ids.ts +85 -0
  216. package/src/chat/index.ts +88 -0
  217. package/src/chat/openai.ts +439 -0
  218. package/src/chat/opengeni.ts +737 -0
  219. package/src/chat/types.ts +219 -0
  220. package/src/chat/vercel.ts +204 -0
  221. package/src/chats.ts +16 -0
  222. package/src/client.ts +5697 -1833
  223. package/src/codex-realtime-controller.ts +163 -8
  224. package/src/codex-realtime-v3-wire.ts +4 -3
  225. package/src/codex-realtime-v3.ts +12 -3
  226. package/src/codex-realtime.ts +11 -3
  227. package/src/company-brain.ts +0 -40
  228. package/src/core.ts +20 -0
  229. package/src/deprecation.ts +149 -0
  230. package/src/document-authority-client.ts +104 -1
  231. package/src/editable-artifacts/controller.ts +36 -2
  232. package/src/editable-artifacts/index.ts +1 -0
  233. package/src/editable-artifacts/types.ts +29 -16
  234. package/src/embedding-client.ts +519 -0
  235. package/src/errors.ts +303 -4
  236. package/src/external-vault-receiver.ts +914 -0
  237. package/src/feedback.ts +20 -0
  238. package/src/gateway-realtime-transport.ts +3 -3
  239. package/src/github-repositories.ts +60 -0
  240. package/src/index.ts +603 -65
  241. package/src/insights-usage.ts +64 -0
  242. package/src/interaction.ts +315 -93
  243. package/src/knowledge.ts +72 -0
  244. package/src/known-tools.ts +279 -0
  245. package/src/message-links.ts +178 -0
  246. package/src/model-context.ts +113 -0
  247. package/src/model-display.ts +11 -0
  248. package/src/model-picker-order.ts +6 -0
  249. package/src/organization-integration-policy.ts +54 -0
  250. package/src/pr-review-client.ts +1 -1
  251. package/src/pr-review.ts +1 -1
  252. package/src/preference-registry.ts +2 -0
  253. package/src/prepared-mcp-setup.ts +123 -0
  254. package/src/proxy.ts +23 -12
  255. package/src/realtime.ts +15 -6
  256. package/src/session-history-import.ts +84 -0
  257. package/src/session-list-entries.ts +1 -0
  258. package/src/session-message-search.ts +47 -0
  259. package/src/session-proxy-client.ts +68 -0
  260. package/src/session-proxy.ts +2098 -0
  261. package/src/session-titles.ts +8 -82
  262. package/src/session-usage-limits.ts +71 -0
  263. package/src/shared-catalog-loader.ts +169 -0
  264. package/src/site-ai.ts +284 -0
  265. package/src/site-bridge-host.ts +422 -0
  266. package/src/site-browser-entry.ts +6 -0
  267. package/src/site-browser-runtime.gen.ts +2 -0
  268. package/src/site-data.ts +372 -0
  269. package/src/site-document.test.ts +64 -0
  270. package/src/site-document.ts +18 -0
  271. package/src/site-http.ts +171 -0
  272. package/src/site-sql.ts +153 -0
  273. package/src/site-tool-bridge.ts +401 -0
  274. package/src/site.ts +878 -0
  275. package/src/skills.ts +69 -0
  276. package/src/sse.ts +9 -3
  277. package/src/stream.ts +33 -10
  278. package/src/tenant-workspaces.ts +148 -0
  279. package/src/tool-auth.ts +301 -0
  280. package/src/tool-display-metadata.ts +27 -0
  281. package/src/tools.ts +452 -0
  282. package/src/transcription.ts +3 -0
  283. package/src/types.ts +3170 -582
  284. package/src/usage-allowances.ts +189 -0
  285. package/src/vault-types.ts +379 -0
  286. package/src/vaults-api.ts +339 -0
  287. package/src/workspace-artifacts.ts +55 -6
  288. package/src/workspace-instruction-policies.ts +2 -1
  289. package/src/workspace-integrations.ts +402 -0
  290. package/src/workspace-state.ts +8 -82
  291. package/dist/chunk-C2H7HBNP.js.map +0 -1
  292. package/dist/chunk-C4DJPZRU.js +0 -119
  293. package/dist/chunk-C4DJPZRU.js.map +0 -1
  294. package/dist/chunk-JUS2UYRP.js.map +0 -1
  295. package/dist/chunk-KKRO2VCP.js.map +0 -1
  296. package/dist/chunk-QT3KFVDP.js.map +0 -1
  297. package/dist/chunk-TRNI7KEO.js.map +0 -1
  298. package/dist/chunk-VYCTL62C.js.map +0 -1
  299. package/dist/chunk-YXQX7RQT.js.map +0 -1
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Site SQL for Site HTML: run the named, parameterized queries the Site's
3
+ * owner defined on its optional SQLite database. Sites never send SQL. The
4
+ * request travels over the host's Site HTTP channel like Site Data; the API
5
+ * checks who may run the query and binds `:viewer` (and the other `viewer_*`
6
+ * parameters) from the signed-in viewer, never from the page. Returned rows
7
+ * are untrusted input: render them with textContent, never innerHTML.
8
+ */
9
+ import { SITE_DATA_PATH_PREFIX } from "./site-data";
10
+
11
+ export type OpenGeniSiteSqlErrorCode =
12
+ | "validation"
13
+ | "not_found"
14
+ | "forbidden"
15
+ /** This viewer may not do this on this Site (a role the Site's rules exclude). */
16
+ | "not_available_for_viewer"
17
+ | "conflict"
18
+ | "quota_exceeded"
19
+ | "rate_limited"
20
+ | "sql_error"
21
+ | "unavailable"
22
+ | "disabled"
23
+ | "site_sql_unsupported"
24
+ | "request_failed";
25
+
26
+ export class OpenGeniSiteSqlError extends Error {
27
+ constructor(
28
+ readonly code: OpenGeniSiteSqlErrorCode,
29
+ message: string,
30
+ readonly status: number,
31
+ readonly details: Record<string, unknown> = {},
32
+ ) {
33
+ super(message);
34
+ this.name = "OpenGeniSiteSqlError";
35
+ }
36
+ }
37
+
38
+ /** A parameter value. Booleans bind as 1 and 0. */
39
+ export type OpenGeniSiteSqlValue = string | number | boolean | null;
40
+
41
+ export type OpenGeniSiteSqlResult<Row extends Record<string, unknown> = Record<string, unknown>> = {
42
+ columns: string[];
43
+ /** Rows keyed by column name; blobs come back as `{ $base64: string }`. */
44
+ rows: Row[];
45
+ /** True when the query's row or size limit cut the result short. */
46
+ truncated: boolean;
47
+ rowsRead: number;
48
+ rowsWritten: number;
49
+ };
50
+
51
+ export type OpenGeniSiteSql = {
52
+ /**
53
+ * Run the named query `name` with `params` (names without the colon). Do
54
+ * not pass `viewer` or any `viewer_*` parameter: the host binds them.
55
+ */
56
+ run<Row extends Record<string, unknown> = Record<string, unknown>>(
57
+ name: string,
58
+ params?: Record<string, OpenGeniSiteSqlValue>,
59
+ ): Promise<OpenGeniSiteSqlResult<Row>>;
60
+ };
61
+
62
+ const QUERY_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/u;
63
+
64
+ const KNOWN_CODES = new Set<string>([
65
+ "validation",
66
+ "not_found",
67
+ "forbidden",
68
+ "not_available_for_viewer",
69
+ "conflict",
70
+ "quota_exceeded",
71
+ "rate_limited",
72
+ "sql_error",
73
+ "unavailable",
74
+ "disabled",
75
+ ]);
76
+
77
+ type SiteSqlFetch = (path: string, init: RequestInit) => Promise<Response>;
78
+
79
+ /** Build `site.sql` over a Site HTTP fetch; `ready` says whether the host serves the pinned Site. */
80
+ export function createOpenGeniSiteSql(
81
+ fetchSite: SiteSqlFetch,
82
+ ready: () => Promise<boolean>,
83
+ ): OpenGeniSiteSql {
84
+ return {
85
+ async run<Row extends Record<string, unknown> = Record<string, unknown>>(
86
+ name: string,
87
+ params: Record<string, OpenGeniSiteSqlValue> = {},
88
+ ): Promise<OpenGeniSiteSqlResult<Row>> {
89
+ if (!QUERY_NAME.test(name))
90
+ throw new OpenGeniSiteSqlError("validation", "Invalid query name", 0);
91
+ if (Object.keys(params).some((key) => key === "viewer" || key.startsWith("viewer_")))
92
+ throw new OpenGeniSiteSqlError(
93
+ "validation",
94
+ "viewer parameters are bound by the host, not the Site",
95
+ 0,
96
+ );
97
+ if (!(await ready()))
98
+ throw new OpenGeniSiteSqlError(
99
+ "site_sql_unsupported",
100
+ "This Site host does not provide Site SQL",
101
+ 0,
102
+ );
103
+ const response = await fetchSite(
104
+ `${SITE_DATA_PATH_PREFIX}/sql/queries/${encodeURIComponent(name)}`,
105
+ {
106
+ method: "POST",
107
+ headers: { "content-type": "application/json" },
108
+ body: JSON.stringify({ params }),
109
+ },
110
+ );
111
+ const text = await response.text();
112
+ let value: unknown = null;
113
+ try {
114
+ value = text ? JSON.parse(text) : null;
115
+ } catch {
116
+ value = null;
117
+ }
118
+ if (response.ok) return value as OpenGeniSiteSqlResult<Row>;
119
+ throw siteSqlErrorFromResponse(response.status, value);
120
+ },
121
+ };
122
+ }
123
+
124
+ function siteSqlErrorFromResponse(status: number, value: unknown): OpenGeniSiteSqlError {
125
+ const error =
126
+ typeof value === "object" && value !== null && "error" in value
127
+ ? (value as { error: unknown }).error
128
+ : null;
129
+ const record =
130
+ typeof error === "object" && error !== null ? (error as Record<string, unknown>) : {};
131
+ const details =
132
+ typeof record.details === "object" && record.details !== null
133
+ ? (record.details as Record<string, unknown>)
134
+ : {};
135
+ const detailCode = typeof details.code === "string" ? details.code : undefined;
136
+ const code: OpenGeniSiteSqlErrorCode =
137
+ detailCode && KNOWN_CODES.has(detailCode)
138
+ ? (detailCode as OpenGeniSiteSqlErrorCode)
139
+ : status === 404
140
+ ? "not_found"
141
+ : status === 403
142
+ ? "forbidden"
143
+ : status === 413
144
+ ? "quota_exceeded"
145
+ : status === 429
146
+ ? "rate_limited"
147
+ : "request_failed";
148
+ const message =
149
+ typeof record.message === "string" && record.message
150
+ ? record.message
151
+ : `Site SQL request failed with HTTP ${status}`;
152
+ return new OpenGeniSiteSqlError(code, message, status, details);
153
+ }
@@ -0,0 +1,401 @@
1
+ import {
2
+ SITE_FRAME_HEADER,
3
+ SITE_FRAME_ID_HEADER,
4
+ SITE_ID_HEADER,
5
+ SITE_VERSION_HEADER,
6
+ siteFrameBodyRefusal,
7
+ siteFrameOperation,
8
+ siteRequestHeaders,
9
+ } from "@opengeni/contracts/site-session-http";
10
+ import { OpenGeniApiError } from "./errors";
11
+ import { createSharedCatalogLoader } from "./shared-catalog-loader";
12
+ import { siteSessionPath, type SiteHttpRequest } from "./site-http";
13
+ import type { OpenGeniSiteToolCatalog } from "./site";
14
+ import type { OpenGeniWorkspaceTools } from "./tools";
15
+ import type {
16
+ ToolGatewayCallRequest,
17
+ ToolGatewayCallResponse,
18
+ ToolGatewayIdentity,
19
+ ToolGatewayResolveRequest,
20
+ ToolGatewayResolvedTool,
21
+ ToolGatewayInvokeRequest,
22
+ ToolGatewayInvokeResponse,
23
+ } from "./types";
24
+
25
+ export type SiteToolCallRequest = ToolGatewayCallRequest & {
26
+ siteArtifactId: string;
27
+ siteVersionId: string;
28
+ };
29
+ export type SiteToolCaller = (input: {
30
+ workspaceId: string;
31
+ request: ToolGatewayCallRequest &
32
+ Partial<Pick<SiteToolCallRequest, "siteArtifactId" | "siteVersionId">>;
33
+ signal: AbortSignal;
34
+ }) => Promise<ToolGatewayCallResponse>;
35
+ export type SiteToolBridge = {
36
+ resolve?: (
37
+ request: ToolGatewayResolveRequest,
38
+ options: { signal: AbortSignal },
39
+ ) => Promise<ToolGatewayResolvedTool>;
40
+ invoke?: (
41
+ request: ToolGatewayInvokeRequest,
42
+ options: { signal: AbortSignal },
43
+ ) => Promise<ToolGatewayInvokeResponse>;
44
+ fetch?: (request: SiteHttpRequest, signal: AbortSignal) => Promise<Response>;
45
+ /** The host serves Site Data for its pinned published Site through `fetch`. */
46
+ siteData?: boolean;
47
+ catalog: (options: { signal: AbortSignal }) => Promise<OpenGeniSiteToolCatalog>;
48
+ call: (
49
+ request: ToolGatewayCallRequest,
50
+ options: { signal: AbortSignal },
51
+ ) => Promise<ToolGatewayCallResponse>;
52
+ };
53
+ export type CreateSiteToolBridgeOptions = {
54
+ workspaceTools: Pick<OpenGeniWorkspaceTools, "$catalog"> &
55
+ Partial<Pick<OpenGeniWorkspaceTools, "$targetProtocol" | "$resolve" | "$invoke" | "$manifest">>;
56
+ workspaceId: string;
57
+
58
+ callTool: SiteToolCaller;
59
+ isCatalogStale?: (error: unknown) => boolean;
60
+ /** Optional authenticated host transport for the host-bound workspace API.
61
+ * Omit to expose tools only. Site-provided authorization headers are never forwarded. */
62
+ fetchResponse?: (path: string, init: RequestInit) => Promise<Response>;
63
+ } & (
64
+ | { artifactId: string; siteVersionId: string; requestedTools: readonly ToolGatewayIdentity[] }
65
+ | { artifactId?: never; siteVersionId?: never; requestedTools?: never }
66
+ );
67
+
68
+ /** Host-side bridge for the opaque Site frame. Supply an authenticated transport;
69
+ * the server independently checks live viewer access and the exact Site version.
70
+ * Recreate on actor/version change. Never take these pinned fields from HTML.
71
+ *
72
+ * `fetch` forwards only the `SITE_OPERATIONS` allowlist (`siteSessionPath`);
73
+ * every other route is refused before a request is made. Each bridge is one
74
+ * frame: it mints a frame id, so a frame without a published Site reaches only
75
+ * the sessions it started. */
76
+ export function createSiteToolBridge(input: CreateSiteToolBridgeOptions): SiteToolBridge {
77
+ const allowed = input.requestedTools ? new Set(input.requestedTools.map(identityKey)) : null;
78
+ const frameId = crypto.randomUUID();
79
+ const targeted =
80
+ input.workspaceTools.$targetProtocol === 1 &&
81
+ input.workspaceTools.$resolve &&
82
+ input.workspaceTools.$invoke &&
83
+ input.workspaceTools.$manifest;
84
+ const context = input.artifactId
85
+ ? { siteArtifactId: input.artifactId, siteVersionId: input.siteVersionId }
86
+ : {};
87
+ const manifests = new Map<string, readonly ToolGatewayResolvedTool[]>();
88
+ const resolve = async (
89
+ request: ToolGatewayResolveRequest,
90
+ { signal }: { signal: AbortSignal },
91
+ ) => {
92
+ if (
93
+ allowed &&
94
+ "identity" in request.target &&
95
+ !allowed.has(identityKey(request.target.identity))
96
+ )
97
+ throw new Error("This tool is not available to the Site");
98
+ const tool = await input.workspaceTools.$resolve!(request.target, { signal, ...context });
99
+ if (allowed && !allowed.has(identityKey(tool.entry.identity)))
100
+ throw new Error("This tool is not available to the Site");
101
+ return tool;
102
+ };
103
+ const invoke = async (request: ToolGatewayInvokeRequest, { signal }: { signal: AbortSignal }) => {
104
+ if (
105
+ allowed &&
106
+ "identity" in request.target &&
107
+ !allowed.has(identityKey(request.target.identity))
108
+ )
109
+ throw new Error("This tool is not available to the Site");
110
+ return await input.workspaceTools.$invoke!(
111
+ {
112
+ target: request.target,
113
+ operationId: request.operationId,
114
+ arguments: request.arguments,
115
+ ...(request.expectedDefinitionDigest === undefined
116
+ ? {}
117
+ : { expectedDefinitionDigest: request.expectedDefinitionDigest }),
118
+ ...context,
119
+ },
120
+ { signal },
121
+ );
122
+ };
123
+ // One shared load serves every concurrent Site request (cancelled only when
124
+ // all of them abort); a stale rejection reloads once for all requests
125
+ // rejected on that digest.
126
+ const projectedCatalog = createSharedCatalogLoader<OpenGeniSiteToolCatalog>(
127
+ async (refresh, signal) => {
128
+ if (targeted && input.requestedTools) {
129
+ const manifest = await input.workspaceTools.$manifest!(
130
+ { identities: [...input.requestedTools], ...context },
131
+ { signal },
132
+ );
133
+ signal.throwIfAborted();
134
+ if (manifests.size >= 4) manifests.delete(manifests.keys().next().value!);
135
+ manifests.set(manifest.digest, manifest.tools);
136
+ return {
137
+ version: 1,
138
+ generation: 1,
139
+ digest: manifest.digest,
140
+ createdAt: new Date().toISOString(),
141
+ entries: manifest.tools.map((tool) => tool.entry),
142
+ };
143
+ }
144
+ const current = await input.workspaceTools.$catalog({
145
+ signal,
146
+ ...(refresh ? { refresh } : {}),
147
+ });
148
+ if (current.workspaceId !== input.workspaceId)
149
+ throw new Error("Site catalog workspace mismatch");
150
+ return {
151
+ version: current.version,
152
+ generation: current.generation,
153
+ digest: current.digest,
154
+ createdAt: current.createdAt,
155
+ entries: current.entries.filter(
156
+ (entry) => !allowed || allowed.has(identityKey(entry.identity)),
157
+ ),
158
+ };
159
+ },
160
+ );
161
+ const loadCatalog = async ({
162
+ signal,
163
+ staleDigest,
164
+ }: {
165
+ signal: AbortSignal;
166
+ staleDigest?: string;
167
+ }): Promise<OpenGeniSiteToolCatalog> => {
168
+ signal.throwIfAborted();
169
+ const catalog =
170
+ staleDigest === undefined
171
+ ? await projectedCatalog.load({ signal })
172
+ : await projectedCatalog.reloadAfterStale(staleDigest, signal);
173
+ signal.throwIfAborted();
174
+ return catalog;
175
+ };
176
+ return {
177
+ ...(targeted ? { resolve, invoke } : {}),
178
+ ...(input.fetchResponse && input.artifactId ? { siteData: true } : {}),
179
+ ...(input.fetchResponse
180
+ ? {
181
+ fetch: async (message: SiteHttpRequest, signal: AbortSignal) => {
182
+ const path = siteSessionPath(
183
+ message.path,
184
+ input.workspaceId,
185
+ message.method,
186
+ input.artifactId,
187
+ );
188
+ // Site code sends message text, not approvals, answers, resources,
189
+ // credentials or connection choices. The API refuses them too.
190
+ const refused = siteFrameBodyFieldRefusal(path, message, input);
191
+ if (refused) return refused;
192
+ const headers = siteRequestHeaders(message.headers);
193
+ // Host-set marker: the API treats every bridged request as a Site frame,
194
+ // limited to SITE_OPERATIONS and to this frame's own sessions.
195
+ headers.set(SITE_FRAME_HEADER, "1");
196
+ headers.set(SITE_FRAME_ID_HEADER, frameId);
197
+ if (input.artifactId) {
198
+ headers.set(SITE_ID_HEADER, input.artifactId);
199
+ headers.set(SITE_VERSION_HEADER, input.siteVersionId);
200
+ }
201
+
202
+ return input.fetchResponse!(path, {
203
+ method: message.method,
204
+ signal,
205
+ headers: Object.fromEntries(headers),
206
+ ...(message.body === undefined ? {} : { body: message.body }),
207
+ });
208
+ },
209
+ }
210
+ : {}),
211
+ catalog: async ({ signal }) => await loadCatalog({ signal }),
212
+ call: async (request, { signal }) => {
213
+ if (allowed && !allowed.has(identityKey(request.identity)))
214
+ throw new Error("This tool is not available to the Site");
215
+ if (targeted && input.requestedTools) {
216
+ const tool = manifests
217
+ .get(request.catalogDigest)
218
+ ?.find(
219
+ (candidate) => identityKey(candidate.entry.identity) === identityKey(request.identity),
220
+ );
221
+ if (!tool)
222
+ throw new OpenGeniApiError(
223
+ 409,
224
+ JSON.stringify({
225
+ error: { code: "conflict", details: { code: "catalog_stale" }, retryable: true },
226
+ }),
227
+ );
228
+ try {
229
+ const response = await invoke(
230
+ {
231
+ operationId: request.operationId ?? crypto.randomUUID(),
232
+ target: { identity: request.identity },
233
+ arguments: request.arguments,
234
+ expectedDefinitionDigest: tool.definitionDigest,
235
+ },
236
+ { signal },
237
+ );
238
+ return {
239
+ operationId: response.operationId,
240
+ catalogDigest: request.catalogDigest,
241
+ result: legacySiteToolResult(response.result),
242
+ };
243
+ } catch (error) {
244
+ if (
245
+ error instanceof OpenGeniApiError &&
246
+ error.retryable !== false &&
247
+ !error.outcomeUnknown &&
248
+ error.details?.code === "tool_definition_stale"
249
+ ) {
250
+ projectedCatalog.invalidate(request.catalogDigest);
251
+ manifests.delete(request.catalogDigest);
252
+ throw new OpenGeniApiError(
253
+ 409,
254
+ JSON.stringify({
255
+ error: {
256
+ code: "catalog_stale",
257
+ details: { code: "catalog_stale" },
258
+ retryable: true,
259
+ },
260
+ }),
261
+ );
262
+ }
263
+ throw error;
264
+ }
265
+ }
266
+ let usedDigest: string | undefined;
267
+ const call = async (staleDigest?: string) => {
268
+ const catalog = await loadCatalog({
269
+ signal,
270
+ ...(staleDigest === undefined ? {} : { staleDigest }),
271
+ });
272
+ usedDigest = catalog.digest;
273
+ if (
274
+ !catalog.entries.some(
275
+ (entry) => identityKey(entry.identity) === identityKey(request.identity),
276
+ )
277
+ )
278
+ throw new Error("This requested tool is not enabled in the workspace");
279
+ const response = await input.callTool({
280
+ workspaceId: input.workspaceId,
281
+ signal,
282
+ request: {
283
+ ...(request.operationId ? { operationId: request.operationId } : {}),
284
+ catalogDigest: catalog.digest,
285
+ identity: request.identity,
286
+ arguments: request.arguments,
287
+ ...(input.artifactId
288
+ ? { siteArtifactId: input.artifactId, siteVersionId: input.siteVersionId }
289
+ : {}),
290
+ },
291
+ });
292
+ return { ...response, result: legacySiteToolResult(response.result) };
293
+ };
294
+ try {
295
+ return await call();
296
+ } catch (error) {
297
+ // Only pre-execution catalog rejection permits one retry. Never retry
298
+ // transport failures, expired credentials or uncertain tool effects.
299
+ if (
300
+ staleRetryVetoed(error) ||
301
+ !(input.isCatalogStale ?? isSiteCatalogStaleError)(error) ||
302
+ usedDigest === undefined
303
+ )
304
+ throw error;
305
+ return await call(usedDigest);
306
+ }
307
+ },
308
+ };
309
+ }
310
+
311
+ /** Saved Site bundles may treat a provider's error.code as retry authorization.
312
+ * An executed result is data, never a pre-execution control signal. Only wrap
313
+ * colliding error results on the legacy call path; keep the original diagnostic
314
+ * intact and do not project request arguments, tokens, or host credentials. */
315
+ function legacySiteToolResult(
316
+ result: ToolGatewayCallResponse["result"],
317
+ ): ToolGatewayCallResponse["result"] {
318
+ if (!result?.isError) return result;
319
+ const structured = result.structuredContent;
320
+ const error = structured?.error;
321
+ if (
322
+ typeof error !== "object" ||
323
+ error === null ||
324
+ !("code" in error) ||
325
+ (error.code !== "catalog_stale" && error.code !== "tool_definition_stale")
326
+ )
327
+ return result;
328
+ return {
329
+ ...result,
330
+ structuredContent: {
331
+ ...structured,
332
+ error: {
333
+ code: "site_tool_execution_failed",
334
+ message:
335
+ "message" in error && typeof error.message === "string"
336
+ ? error.message
337
+ : "Site tool execution failed",
338
+ retryable: false,
339
+ outcomeUnknown: "outcomeUnknown" in error && error.outcomeUnknown === true,
340
+ providerError: error,
341
+ },
342
+ },
343
+ };
344
+ }
345
+
346
+ export function isSiteCatalogStaleError(error: unknown): boolean {
347
+ return (
348
+ !staleRetryVetoed(error) &&
349
+ error instanceof OpenGeniApiError &&
350
+ error.status === 409 &&
351
+ error.details?.code === "catalog_stale"
352
+ );
353
+ }
354
+ function staleRetryVetoed(error: unknown): boolean {
355
+ return (
356
+ typeof error === "object" &&
357
+ error !== null &&
358
+ (("outcomeUnknown" in error && error.outcomeUnknown === true) ||
359
+ ("retryable" in error && error.retryable === false))
360
+ );
361
+ }
362
+ function identityKey(identity: ToolGatewayIdentity): string {
363
+ return `${identity.serverId}\u0000${identity.toolName}`;
364
+ }
365
+
366
+ /** A 403 like the API's for a body field Site code may not send, or null. */
367
+ function siteFrameBodyFieldRefusal(
368
+ path: string,
369
+ message: SiteHttpRequest,
370
+ input: { workspaceId: string; artifactId?: string | undefined },
371
+ ): Response | null {
372
+ if (typeof message.body !== "string" || message.method === "GET") return null;
373
+ const operation = siteFrameOperation(
374
+ message.method,
375
+ path.split("?")[0]!,
376
+ input.workspaceId,
377
+ input.artifactId ?? null,
378
+ );
379
+ if (!operation) return null;
380
+ let body: unknown;
381
+ try {
382
+ body = JSON.parse(message.body);
383
+ } catch {
384
+ return null;
385
+ }
386
+ const field = siteFrameBodyRefusal(operation.operation.id, operation.suffix, body);
387
+ if (!field) return null;
388
+ return Response.json(
389
+ {
390
+ error: {
391
+ status: 403,
392
+ code: "forbidden",
393
+ message:
394
+ "A Site page can send message text only. Approvals, answers, resources, credentials and connection choices happen in the console.",
395
+ retryable: false,
396
+ details: { code: "site_frame_field_forbidden", field },
397
+ },
398
+ },
399
+ { status: 403 },
400
+ );
401
+ }