@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,737 @@
1
+ import { OpenGeniEmbeddingClient as OpenGeniClient } from "../embedding-client";
2
+ import { OpenGeniApiError } from "../errors";
3
+ import { chatDefaults } from "../chats";
4
+ import {
5
+ createWorkspaceIdResolver,
6
+ type WorkspaceIdOptions,
7
+ type WorkspaceIdTarget,
8
+ } from "../tenant-workspaces";
9
+ import type { SendMessageInput } from "../client";
10
+ import type { CreateSessionRequest, CreateSessionResponse, Session, SessionEvent } from "../types";
11
+ import { ChatPendingFold, ChatTurnFold, asRecord, stringValue } from "./fold";
12
+ import { chatIdempotencyKey, chatSessionId } from "./ids";
13
+ import {
14
+ OpenGeniChatError,
15
+ type ChatChunk,
16
+ type ChatImportedMessage,
17
+ type ChatMessage,
18
+ type ChatOptions,
19
+ type ChatReply,
20
+ type ChatRespondInput,
21
+ type ChatSendOptions,
22
+ type ChatSessionListOptions,
23
+ type ChatSnapshot,
24
+ type ChatTarget,
25
+ type WorkspaceTarget,
26
+ type OpenGeniOptions,
27
+ } from "./types";
28
+
29
+ export const DEFAULT_OPENGENI_BASE_URL = "https://app.opengeni.ai";
30
+ export const DEFAULT_CHAT_SOURCE = "app";
31
+
32
+ type SubmittedTurn = { after: number; turnId: string | null };
33
+
34
+ const MISSING_API_KEY_MESSAGE =
35
+ "Opengeni requires an apiKey. Set OPENGENI_API_KEY in the server environment.";
36
+
37
+ async function missingApiKeyFetch(): Promise<Response> {
38
+ throw new TypeError(MISSING_API_KEY_MESSAGE);
39
+ }
40
+
41
+ type BuildCreate = (text: string, send: ChatSendOptions) => CreateSessionRequest;
42
+ type SubmitCreate = (request: CreateSessionRequest) => Promise<CreateSessionResponse>;
43
+
44
+ type ChatInit = {
45
+ workspaceId: string;
46
+ sessionId: string;
47
+ conversation: string | null;
48
+ session: Session | null;
49
+ buildCreate: BuildCreate | null;
50
+ submitCreate?: SubmitCreate | undefined;
51
+ };
52
+
53
+ /** Upper bound on the `modelContext` built from `importedHistory`, header included. */
54
+ export const IMPORTED_HISTORY_MAX_CHARS = 30_000;
55
+ /** Server limit for one message's `modelContext`. */
56
+ const MODEL_CONTEXT_MAX_CHARS = 32_768;
57
+ const IMPORTED_HISTORY_HEADER = "Earlier conversation imported from the product, oldest first:";
58
+ const IMPORTED_HISTORY_ROLES: ReadonlySet<string> = new Set(["user", "assistant", "system"]);
59
+
60
+ /**
61
+ * One-option-object entry point for products that already have a chat. Wraps
62
+ * `OpenGeniClient` (exposed as `client`) with tenant workspaces, deterministic
63
+ * conversation sessions, and text-first replies.
64
+ */
65
+ export class OpenGeni {
66
+ readonly client: OpenGeniClient;
67
+ /**
68
+ * The organization id: the one passed to the constructor, else the one
69
+ * derived from the API key once {@link resolveOrganizationId} (or any
70
+ * workspace lookup) has run. An empty string before that; prefer
71
+ * `await og.resolveOrganizationId()`.
72
+ */
73
+ readonly organizationId: string;
74
+ readonly source: string;
75
+ readonly sessions: {
76
+ /** Sessions visible to the selected canonical user, or explicit service caller. */
77
+ list: (options: ChatSessionListOptions) => Promise<Session[]>;
78
+ };
79
+ private readonly resolveWorkspaceId: ReturnType<typeof createWorkspaceIdResolver>;
80
+ private implicitAgentAdmission: boolean | undefined;
81
+ private pendingOrganizationId: Promise<string> | undefined;
82
+
83
+ constructor(options: OpenGeniOptions) {
84
+ this.client = new OpenGeniClient({
85
+ // An empty value (a blank `OPENGENI_API_BASE_URL=` in .env) means the default.
86
+ baseUrl: options.baseUrl?.trim() || DEFAULT_OPENGENI_BASE_URL,
87
+ // A missing key fails each request, not construction: a module-scope
88
+ // `new Opengeni({ apiKey: process.env.OPENGENI_API_KEY! })` must not
89
+ // break `next build` (or any import) where the secret only exists at
90
+ // runtime.
91
+ ...(options.apiKey
92
+ ? { apiKey: options.apiKey, ...(options.fetch ? { fetch: options.fetch } : {}) }
93
+ : { fetch: missingApiKeyFetch }),
94
+ });
95
+ this.organizationId = options.organizationId?.trim() ?? "";
96
+ this.source = options.source ?? DEFAULT_CHAT_SOURCE;
97
+ this.resolveWorkspaceId = createWorkspaceIdResolver(this.client, {
98
+ organizationId: () => this.resolveOrganizationId(),
99
+ source: this.source,
100
+ workspaceName: options.workspaceName,
101
+ memberPermissions: options.memberPermissions,
102
+ });
103
+ this.sessions = { list: (listOptions) => this.listSessions(listOptions) };
104
+ }
105
+
106
+ /**
107
+ * The organization that owns every workspace this facade creates. Uses the
108
+ * constructor's `organizationId`, else reads it once from the API key
109
+ * (`GET /v1/access/me`) and caches it.
110
+ */
111
+ async resolveOrganizationId(): Promise<string> {
112
+ if (this.organizationId) return this.organizationId;
113
+ if (!this.pendingOrganizationId) {
114
+ const pending = this.client.getAccessContext().then((access) => {
115
+ const credential = access.credential;
116
+ if (credential && credential.kind !== "organization_api_key") {
117
+ throw new TypeError(
118
+ "Opengeni needs an organization API key to create workspaces; this is a workspace key.",
119
+ );
120
+ }
121
+ const organizationId = credential?.accountId ?? access.defaultAccountId;
122
+ if (!organizationId) {
123
+ throw new TypeError(
124
+ "Could not derive the organization from this API key. Pass organizationId.",
125
+ );
126
+ }
127
+ // Readonly to callers; set once here, from the key's own organization.
128
+ (this as { organizationId: string }).organizationId = organizationId;
129
+ return organizationId;
130
+ });
131
+ pending.catch(() => {
132
+ if (this.pendingOrganizationId === pending) this.pendingOrganizationId = undefined;
133
+ });
134
+ this.pendingOrganizationId = pending;
135
+ }
136
+ return await this.pendingOrganizationId;
137
+ }
138
+
139
+ /**
140
+ * Translate your own ids to the Opengeni workspace id, creating the
141
+ * workspace on first use (cached per instance):
142
+ * `{ tenant }` is one workspace per tenant, `{ user }` (no tenant) is one
143
+ * workspace per user, and `{ workspaceId }` is returned as is. Opengeni adds
144
+ * a tenant workspace's users on their first request; a per-user workspace
145
+ * gets its one owner from the SDK and admits nobody else.
146
+ */
147
+ async workspaceId(
148
+ target:
149
+ | ChatTarget
150
+ | { tenant?: string | undefined; workspaceId?: string | undefined }
151
+ | WorkspaceTarget,
152
+ ): Promise<string> {
153
+ if (target.workspaceId === "" || target.tenant === "") {
154
+ throw new TypeError("tenant and workspaceId must be non-empty ids.");
155
+ }
156
+ if (target.workspaceId) return target.workspaceId;
157
+ if (target.tenant) {
158
+ return await this.workspaceIdFor({ tenant: target.tenant }, { isolation: "tenant" });
159
+ }
160
+ if ("tenant" in target || "workspaceId" in target) {
161
+ // A tenant/workspaceId key with no value is a host bug: never silently
162
+ // fall back to the user's own workspace.
163
+ throw new TypeError(
164
+ "tenant or workspaceId is undefined. Pass a non-empty id, or omit the key for one workspace per user.",
165
+ );
166
+ }
167
+ const user = "user" in target ? target.user : undefined;
168
+ if (user) {
169
+ return await this.workspaceIdFor({ user }, { isolation: "user" });
170
+ }
171
+ throw new TypeError("Pass tenant, user, or workspaceId.");
172
+ }
173
+
174
+ /**
175
+ * Resolve a tenant workspace or a user's own workspace (user isolation, per
176
+ * tenant when one is given). A user's own workspace is provisioned with that
177
+ * user as its only explicit member.
178
+ */
179
+ async workspaceIdFor(target: WorkspaceIdTarget, options: WorkspaceIdOptions): Promise<string> {
180
+ return await this.resolveWorkspaceId(target, options);
181
+ }
182
+
183
+ /** `chats: "isolated"` keeps its tenant-plus-user workspace; a user alone gets their own. */
184
+ private async isolatedWorkspaceId(target: WorkspaceTarget): Promise<string> {
185
+ if (target.workspaceId) {
186
+ throw new TypeError('chats: "isolated" requires a tenant or user, not a workspaceId.');
187
+ }
188
+ return await this.workspaceIdFor(
189
+ { tenant: target.tenant, user: target.user },
190
+ { isolation: "user" },
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Address one conversation; the session is created lazily on the first send.
196
+ * Identity selects authority, not the conversation address. The workspace
197
+ * is the tenant's, the user's own (user only), or the explicit id; Opengeni
198
+ * adds the user to it on their first request.
199
+ * Legacy user-namespaced conversations remain accessible by their session ID.
200
+ */
201
+ async chat(options: ChatOptions): Promise<Chat> {
202
+ if (!options.conversation) throw new TypeError("chat() requires a conversation id.");
203
+ if (options.chats === "private" && !options.user) {
204
+ throw new OpenGeniChatError(
205
+ "chats_requires_user",
206
+ 'chats: "private" requires an authenticated product user. Pass user or use chats: "shared".',
207
+ );
208
+ }
209
+ const legacy = options.chats === undefined && !options.user;
210
+ const defaults = legacy
211
+ ? {
212
+ visibility: "workspace" as const,
213
+ agentAccess: "session" as const,
214
+ memoryScope: "off" as const,
215
+ }
216
+ : chatDefaults(options.chats ?? "private");
217
+ const explicitAgent = options.agent !== undefined || options.create?.agent !== undefined;
218
+ const agentAccess = options.agentAccess ?? defaults.agentAccess;
219
+ const memoryScope =
220
+ options.memory === false
221
+ ? "off"
222
+ : (options.memory ??
223
+ (legacy ? (agentAccess === "session" ? "off" : agentAccess) : defaults.memoryScope));
224
+ if (memoryScope === "user" && !options.user) {
225
+ throw new OpenGeniChatError(
226
+ "memory_scope_requires_user",
227
+ 'memory: "user" requires an authenticated product user.',
228
+ );
229
+ }
230
+ const client = options.user
231
+ ? this.client.asUser(options.user, { source: this.source })
232
+ : this.client;
233
+ const workspaceId =
234
+ options.chats === "isolated"
235
+ ? await this.isolatedWorkspaceId(options)
236
+ : await this.workspaceId(options);
237
+ const sessionId = options.sessionId ?? (await chatSessionId(workspaceId, options.conversation));
238
+ const session = await this.findSession(client, workspaceId, sessionId);
239
+ const buildCreate: BuildCreate = (text, send) => {
240
+ const { modelContext: createContext, ...create } = options.create ?? {};
241
+ const messageContext = send.modelContext?.trim() || undefined;
242
+ const reserved = messageContext ? messageContext.length + 2 : 0;
243
+ const baseContext =
244
+ createContext ??
245
+ (send.importedHistory
246
+ ? formatImportedHistory(
247
+ send.importedHistory,
248
+ Math.min(IMPORTED_HISTORY_MAX_CHARS, MODEL_CONTEXT_MAX_CHARS - reserved),
249
+ )
250
+ : undefined);
251
+ const context = [baseContext, messageContext].filter(Boolean).join("\n\n") || undefined;
252
+ return {
253
+ ...(options.model !== undefined ? { model: options.model } : {}),
254
+ ...(options.instructions !== undefined ? { instructions: options.instructions } : {}),
255
+ ...(options.skills !== undefined ? { skills: options.skills } : {}),
256
+ ...(options.tools !== undefined ? { tools: options.tools } : {}),
257
+ ...create,
258
+ visibility: create.visibility ?? defaults.visibility,
259
+ agentAccess: create.agentAccess ?? agentAccess,
260
+ memoryScope: create.memoryScope ?? memoryScope,
261
+ agent: {
262
+ capabilities: create.agent?.capabilities ?? options.agent?.capabilities,
263
+ identity:
264
+ create.agent?.identity !== undefined ? create.agent.identity : options.agent?.identity,
265
+ instructions: create.agent?.instructions ?? options.agent?.instructions,
266
+ renderer: create.agent?.renderer ?? options.agent?.renderer ?? "markdown",
267
+ },
268
+ ...(context !== undefined ? { modelContext: context } : {}),
269
+ ...messagePolicy(send),
270
+ initialMessage: text,
271
+ requestedSessionId: sessionId,
272
+ idempotencyKey: chatIdempotencyKey(sessionId),
273
+ };
274
+ };
275
+ return new Chat(client, {
276
+ workspaceId,
277
+ sessionId,
278
+ conversation: options.conversation,
279
+ session,
280
+ buildCreate,
281
+ submitCreate: (request) =>
282
+ this.createChatSession(client, workspaceId, request, explicitAgent),
283
+ });
284
+ }
285
+
286
+ private async createChatSession(
287
+ client: OpenGeniClient,
288
+ workspaceId: string,
289
+ request: CreateSessionRequest,
290
+ explicitAgent: boolean,
291
+ ): Promise<CreateSessionResponse> {
292
+ const { agent, ...withoutAgent } = request;
293
+ const implicitAgent = !explicitAgent && this.implicitAgentAdmission !== false;
294
+ try {
295
+ return await client.createSession(
296
+ workspaceId,
297
+ explicitAgent || implicitAgent ? { ...withoutAgent, agent } : withoutAgent,
298
+ );
299
+ } catch (error) {
300
+ // Current servers always admit `agent`. This 422 comes only from older
301
+ // servers that still had agent configuration behind a deployment flag.
302
+ if (
303
+ !(error instanceof OpenGeniApiError) ||
304
+ error.status !== 422 ||
305
+ !(
306
+ error.code === "agent_config_not_enabled" ||
307
+ (error.code === "SESSION_CREATE_REJECTED" &&
308
+ error.details?.code === "agent_config_not_enabled")
309
+ ) ||
310
+ error.outcomeUnknown
311
+ )
312
+ throw error;
313
+ if (explicitAgent) {
314
+ throw new OpenGeniApiError(error.status, error.body, {
315
+ code: error.code,
316
+ retryable: false,
317
+ correlationId: error.correlationId,
318
+ displayMessage:
319
+ "Explicit agent configuration requires the deployment operator to set " +
320
+ "OPENGENI_AGENT_CONFIG_ADMISSION_ENABLED=true. The SDK has kept your agent settings unchanged.",
321
+ });
322
+ }
323
+ if (!implicitAgent) throw error;
324
+ // This exact 422 refused the create. Retry only the implicit renderer,
325
+ // retaining the session/idempotency keys and every caller-owned field.
326
+ this.implicitAgentAdmission = false;
327
+ return await client.createSession(workspaceId, withoutAgent);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Address an existing session with native API authorization. The same shared
333
+ * session can be opened by different authorized users. Pass null only for an
334
+ * explicitly service-owned operation; the API still enforces service access.
335
+ */
336
+ async chatBySessionId(target: {
337
+ workspaceId: string;
338
+ sessionId: string;
339
+ user: string | null;
340
+ }): Promise<Chat> {
341
+ const client =
342
+ target.user !== null ? this.client.asUser(target.user, { source: this.source }) : this.client;
343
+ const session = await client.getSession(target.workspaceId, target.sessionId);
344
+ return new Chat(client, {
345
+ workspaceId: target.workspaceId,
346
+ sessionId: target.sessionId,
347
+ conversation: null,
348
+ session,
349
+ buildCreate: null,
350
+ });
351
+ }
352
+
353
+ private async findSession(
354
+ client: OpenGeniClient,
355
+ workspaceId: string,
356
+ sessionId: string,
357
+ ): Promise<Session | null> {
358
+ try {
359
+ return await client.getSession(workspaceId, sessionId);
360
+ } catch (error) {
361
+ if (error instanceof OpenGeniApiError && error.status === 404) return null;
362
+ throw error;
363
+ }
364
+ }
365
+
366
+ private async listSessions(options: ChatSessionListOptions): Promise<Session[]> {
367
+ const workspaceId =
368
+ options.chats === "isolated"
369
+ ? await this.isolatedWorkspaceId(options)
370
+ : await this.workspaceId(options);
371
+ const client = options.user
372
+ ? this.client.asUser(options.user, { source: this.source })
373
+ : this.client;
374
+ return await client.requestJson<Session[]>(
375
+ "GET",
376
+ `/v1/workspaces/${workspaceId}/sessions`,
377
+ undefined,
378
+ {
379
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
380
+ },
381
+ );
382
+ }
383
+ }
384
+
385
+ /** One conversation bound to one deterministic session. */
386
+ export class Chat {
387
+ readonly workspaceId: string;
388
+ readonly sessionId: string;
389
+ /** The conversation id this chat was opened with; null when addressed by session id. */
390
+ readonly conversation: string | null;
391
+ private session: Session | null;
392
+ private readonly buildCreate: BuildCreate | null;
393
+ private readonly submitCreate: SubmitCreate;
394
+ private pendingTurnId: string | null = null;
395
+
396
+ constructor(
397
+ private readonly client: OpenGeniClient,
398
+ init: ChatInit,
399
+ ) {
400
+ this.workspaceId = init.workspaceId;
401
+ this.sessionId = init.sessionId;
402
+ this.conversation = init.conversation;
403
+ this.session = init.session;
404
+ this.buildCreate = init.buildCreate;
405
+ this.submitCreate =
406
+ init.submitCreate ?? ((request) => this.client.createSession(this.workspaceId, request));
407
+ }
408
+
409
+ /** True once the session exists on the server. */
410
+ get created(): boolean {
411
+ return this.session !== null;
412
+ }
413
+
414
+ /** Send a message and wait for the agent's reply text. */
415
+ async send(text: string, options: ChatSendOptions = {}): Promise<ChatReply> {
416
+ return await settle(this.stream(text, options));
417
+ }
418
+
419
+ /** Send a message and observe the reply as it streams. Ends with a `done` chunk. */
420
+ async *stream(
421
+ text: string,
422
+ options: ChatSendOptions = {},
423
+ ): AsyncGenerator<ChatChunk, void, void> {
424
+ const submitted = options.steer
425
+ ? await this.submitSteer(text, options)
426
+ : await this.submit(text, options);
427
+ yield* this.streamTurn(submitted, options.signal);
428
+ }
429
+
430
+ /** Supersede the current inference with this message and wait for the reply. */
431
+ async steer(text: string, options: Omit<ChatSendOptions, "steer"> = {}): Promise<ChatReply> {
432
+ return await this.send(text, { ...options, steer: true });
433
+ }
434
+
435
+ /** Answer a pending approval or human-input request, then wait for the reply. */
436
+ async respond(
437
+ input: ChatRespondInput,
438
+ options: Omit<ChatSendOptions, "steer"> = {},
439
+ ): Promise<ChatReply> {
440
+ return await settle(this.respondStream(input, options));
441
+ }
442
+
443
+ async *respondStream(
444
+ input: ChatRespondInput,
445
+ options: Omit<ChatSendOptions, "steer"> = {},
446
+ ): AsyncGenerator<ChatChunk, void, void> {
447
+ const submitted = await this.submitResponse(input);
448
+ yield* this.streamTurn(submitted, options.signal);
449
+ }
450
+
451
+ /** User and assistant text in order, from the durable event log. */
452
+ async history(): Promise<ChatMessage[]> {
453
+ return (await this.snapshot()).messages;
454
+ }
455
+
456
+ /** Restore history and pending decisions from the complete ordered timeline. */
457
+ async snapshot(): Promise<ChatSnapshot> {
458
+ if (!this.session) return { messages: [], pending: [], status: null };
459
+ const pending = new ChatPendingFold();
460
+ let status = this.session.status;
461
+ const messages: ChatMessage[] = [];
462
+ let lastAssistantTurn: string | null = null;
463
+ // Commentary is activity, not history. Like the live fold, a turn that
464
+ // settles without an answer keeps only its latest commentary.
465
+ let heldCommentary: { turnId: string | null; text: string; sequence: number } | null = null;
466
+ const releaseHeldCommentary = (turnId: string | null) => {
467
+ if (!heldCommentary || heldCommentary.turnId !== turnId) return;
468
+ const held = heldCommentary;
469
+ heldCommentary = null;
470
+ const index = messages.findIndex((message) => message.sequence > held.sequence);
471
+ const message: ChatMessage = { role: "assistant", text: held.text, sequence: held.sequence };
472
+ if (index === -1) messages.push(message);
473
+ else messages.splice(index, 0, message);
474
+ };
475
+ let after = 0;
476
+ while (true) {
477
+ const result = await this.client.listEventPage(this.workspaceId, this.sessionId, {
478
+ after,
479
+ includeTypes: [
480
+ "user.message",
481
+ "agent.message.completed",
482
+ "session.requiresAction",
483
+ "session.humanInput.requested",
484
+ "user.approvalDecision",
485
+ "user.humanInputResponse",
486
+ "turn.completed",
487
+ "turn.failed",
488
+ "turn.cancelled",
489
+ "session.status.changed",
490
+ ],
491
+ });
492
+ for (const event of result.events) {
493
+ pending.push(event);
494
+ if (event.type === "session.status.changed") {
495
+ const next = asRecord(event.payload).status;
496
+ if (typeof next === "string") status = next as Session["status"];
497
+ }
498
+ const turnId = typeof event.turnId === "string" ? event.turnId : null;
499
+ if (
500
+ event.type === "turn.completed" ||
501
+ event.type === "turn.failed" ||
502
+ event.type === "turn.cancelled" ||
503
+ event.type === "session.requiresAction" ||
504
+ event.type === "session.humanInput.requested"
505
+ ) {
506
+ releaseHeldCommentary(turnId);
507
+ }
508
+ const text = stringValue(asRecord(event.payload).text);
509
+ if (!text) continue;
510
+ if (event.type === "user.message") {
511
+ messages.push({ role: "user", text, sequence: event.sequence });
512
+ lastAssistantTurn = null;
513
+ continue;
514
+ }
515
+ if (event.type !== "agent.message.completed") continue;
516
+ if (asRecord(event.payload).phase === "commentary") {
517
+ heldCommentary = { turnId, text, sequence: event.sequence };
518
+ continue;
519
+ }
520
+ if (heldCommentary?.turnId === turnId) heldCommentary = null;
521
+ const last = messages.at(-1);
522
+ if (
523
+ last?.role === "assistant" &&
524
+ turnId !== null &&
525
+ turnId === lastAssistantTurn &&
526
+ text.startsWith(last.text)
527
+ ) {
528
+ last.text = text;
529
+ last.sequence = event.sequence;
530
+ continue;
531
+ }
532
+ messages.push({ role: "assistant", text, sequence: event.sequence });
533
+ lastAssistantTurn = turnId;
534
+ }
535
+ if (!result.hasMore || result.nextAfter === null || result.events.length === 0) break;
536
+ if (result.nextAfter <= after) {
537
+ throw new OpenGeniChatError(
538
+ "history_cursor_stalled",
539
+ "History pagination did not advance.",
540
+ );
541
+ }
542
+ after = result.nextAfter;
543
+ }
544
+ return { messages, pending: pending.pending(), status };
545
+ }
546
+
547
+ private async submit(text: string, send: ChatSendOptions): Promise<SubmittedTurn> {
548
+ if (!this.session) {
549
+ if (!this.buildCreate) {
550
+ throw new OpenGeniChatError("session_missing", "This session no longer exists.");
551
+ }
552
+ const created = await this.submitCreate(this.buildCreate(text, send));
553
+ this.session = created;
554
+ if (created.initialMessage === text) {
555
+ return { after: 0, turnId: created.initialTurnId };
556
+ }
557
+ // The idempotent create replayed an earlier session; deliver this message too.
558
+ }
559
+ const event = await this.client.sendMessage(
560
+ this.workspaceId,
561
+ this.sessionId,
562
+ messageInput(text, send),
563
+ );
564
+ return submittedFrom(event);
565
+ }
566
+
567
+ private async submitSteer(text: string, send: ChatSendOptions): Promise<SubmittedTurn> {
568
+ if (!this.session) return await this.submit(text, send);
569
+ const result = await this.client.steerMessage(
570
+ this.workspaceId,
571
+ this.sessionId,
572
+ messageInput(text, send),
573
+ );
574
+ return { after: result.accepted.sequence, turnId: result.turn.id ?? null };
575
+ }
576
+
577
+ private async submitResponse(input: ChatRespondInput): Promise<SubmittedTurn> {
578
+ let event: SessionEvent;
579
+ if ("decision" in input) {
580
+ event = await this.client.sendApprovalDecision(this.workspaceId, this.sessionId, {
581
+ approvalId: input.requestId,
582
+ decision: input.decision,
583
+ ...(input.message !== undefined ? { message: input.message } : {}),
584
+ });
585
+ } else if ("answers" in input) {
586
+ event = await this.client.submitHumanInputResponse(
587
+ this.workspaceId,
588
+ this.sessionId,
589
+ input.requestId,
590
+ { outcome: "answered", answers: input.answers },
591
+ );
592
+ } else {
593
+ event = await this.client.submitHumanInputResponse(
594
+ this.workspaceId,
595
+ this.sessionId,
596
+ input.requestId,
597
+ { outcome: "skipped" },
598
+ );
599
+ }
600
+ const submitted = submittedFrom(event);
601
+ return { ...submitted, turnId: submitted.turnId ?? this.pendingTurnId };
602
+ }
603
+
604
+ private async *streamTurn(
605
+ submitted: SubmittedTurn,
606
+ signal: AbortSignal | undefined,
607
+ ): AsyncGenerator<ChatChunk, void, void> {
608
+ const upstream = new AbortController();
609
+ const onAbort = (): void => upstream.abort();
610
+ if (signal?.aborted) upstream.abort();
611
+ else signal?.addEventListener("abort", onAbort, { once: true });
612
+ const fold = new ChatTurnFold(this.workspaceId, this.sessionId, submitted.turnId);
613
+ // Iterate by hand rather than with for-await: leaving a for-await loop
614
+ // awaits the inner iterator's return(), which awaits the SSE body's
615
+ // cancel(), and some fetch implementations (Next.js on Node) settle that
616
+ // cancel only after the request is aborted. The finally below aborts
617
+ // first and never waits on the inner unwinding.
618
+ const events = this.client.streamEvents(this.workspaceId, this.sessionId, {
619
+ after: submitted.after,
620
+ signal: upstream.signal,
621
+ });
622
+ try {
623
+ while (true) {
624
+ const next = await events.next();
625
+ if (next.done) break;
626
+ const event = next.value;
627
+ const step = fold.push(event);
628
+ for (const chunk of step.chunks) yield chunk;
629
+ // Resuming one member of a parallel interruption group re-emits the
630
+ // waiting status, but existing human-input requests are not recreated.
631
+ // Recover the remaining decision instead of waiting forever for new text.
632
+ if (
633
+ !step.terminal &&
634
+ event.type === "session.status.changed" &&
635
+ asRecord(event.payload).status === "requires_action" &&
636
+ (event.turnId == null || fold.turnId === null || event.turnId === fold.turnId)
637
+ ) {
638
+ const pending = (await this.snapshot()).pending[0];
639
+ if (pending) {
640
+ fold.pending = pending;
641
+ this.pendingTurnId = fold.turnId;
642
+ yield { type: "pending", pending };
643
+ yield { type: "done", reply: fold.reply("pending") };
644
+ return;
645
+ }
646
+ }
647
+ if (!step.terminal) continue;
648
+ if (step.terminal === "failed") throw fold.failureError();
649
+ this.pendingTurnId = step.terminal === "pending" ? fold.turnId : null;
650
+ yield { type: "done", reply: fold.reply(step.terminal) };
651
+ return;
652
+ }
653
+ if (signal?.aborted) throw abortError();
654
+ throw new OpenGeniChatError(
655
+ "stream_ended",
656
+ "The event stream ended before the turn settled.",
657
+ );
658
+ } finally {
659
+ signal?.removeEventListener("abort", onAbort);
660
+ upstream.abort();
661
+ void Promise.resolve(events.return(undefined)).catch(() => undefined);
662
+ }
663
+ }
664
+ }
665
+
666
+ async function settle(chunks: AsyncIterable<ChatChunk>): Promise<ChatReply> {
667
+ for await (const chunk of chunks) {
668
+ if (chunk.type === "done") return chunk.reply;
669
+ }
670
+ throw new OpenGeniChatError("stream_ended", "The event stream ended before the turn settled.");
671
+ }
672
+
673
+ /** Per-message model policy fields that the send options carry. */
674
+ function messagePolicy(send: ChatSendOptions): Partial<SendMessageInput> {
675
+ return {
676
+ ...(send.model !== undefined ? { model: send.model } : {}),
677
+ ...(send.reasoningEffort !== undefined ? { reasoningEffort: send.reasoningEffort } : {}),
678
+ ...(send.latencyMode !== undefined ? { latencyMode: send.latencyMode } : {}),
679
+ };
680
+ }
681
+
682
+ function messageInput(text: string, send: ChatSendOptions): SendMessageInput {
683
+ const modelContext = send.modelContext?.trim();
684
+ return { text, ...messagePolicy(send), ...(modelContext ? { modelContext } : {}) };
685
+ }
686
+
687
+ function submittedFrom(event: SessionEvent): SubmittedTurn {
688
+ return {
689
+ after: event.sequence,
690
+ turnId: typeof event.turnId === "string" ? event.turnId : null,
691
+ };
692
+ }
693
+
694
+ /**
695
+ * `modelContext` for imported history: a header line plus one `role: text`
696
+ * line per message, oldest first, trimmed from the oldest end to
697
+ * {@link IMPORTED_HISTORY_MAX_CHARS}. Undefined when nothing usable remains.
698
+ */
699
+ export function formatImportedHistory(
700
+ messages: ChatImportedMessage[],
701
+ maxChars: number = IMPORTED_HISTORY_MAX_CHARS,
702
+ ): string | undefined {
703
+ const lines = messages
704
+ .filter(
705
+ (message) =>
706
+ IMPORTED_HISTORY_ROLES.has(message.role) &&
707
+ typeof message.text === "string" &&
708
+ message.text.trim().length > 0,
709
+ )
710
+ .map((message) => `${message.role}: ${message.text}`);
711
+ if (lines.length === 0) return undefined;
712
+ let budget = Math.min(maxChars, IMPORTED_HISTORY_MAX_CHARS) - IMPORTED_HISTORY_HEADER.length;
713
+ const kept: string[] = [];
714
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
715
+ const line = lines[index]!;
716
+ const cost = line.length + 1;
717
+ if (cost <= budget) {
718
+ kept.unshift(line);
719
+ budget -= cost;
720
+ continue;
721
+ }
722
+ // The newest line alone overflows: keep its tail so the most recent text survives.
723
+ if (kept.length === 0 && budget > 1) kept.unshift(line.slice(line.length - (budget - 1)));
724
+ break;
725
+ }
726
+ if (kept.length === 0) return undefined;
727
+ return `${IMPORTED_HISTORY_HEADER}\n${kept.join("\n")}`;
728
+ }
729
+
730
+ function abortError(): Error {
731
+ const error = new Error("The chat request was aborted.");
732
+ error.name = "AbortError";
733
+ return error;
734
+ }
735
+
736
+ /** Current brand spelling; the established SDK export remains compatible. */
737
+ export { OpenGeni as Opengeni };