@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
@@ -47,7 +47,7 @@ export type { SessionRealtimeLifecycleProjection } from "./codex-realtime-lifecy
47
47
  const HEARTBEAT_INTERVAL_MS = 10_000;
48
48
  const OUTBOUND_SYNC_INTERVAL_MS = 1_000;
49
49
  export const CODEX_REALTIME_NEGOTIATION_TIMEOUT_MS = 20_000;
50
- // OpenGeni policy: rotate conservatively without asserting an upstream lifetime.
50
+ // Opengeni policy: rotate conservatively without asserting an upstream lifetime.
51
51
  const DEFAULT_CONNECTION_ROTATION_INTERVAL_MS = 15 * 60_000;
52
52
  const DEFAULT_RECONNECT_BACKOFF_MS = [250, 1_000, 2_000, 5_000] as const;
53
53
  const MAX_BROWSER_TIMEOUT_MS = 2_147_483_647;
@@ -120,8 +120,39 @@ export type CodexRealtimeControllerSnapshot = {
120
120
  reconnectAttempt: number;
121
121
  diagnostic: CodexRealtimeDiagnostic | null;
122
122
  error: string | null;
123
+ /**
124
+ * Set when Opengeni refused or ended deployment-funded voice for credits or
125
+ * availability. Codes match voice input: `insufficient_credits`,
126
+ * `allowance_exhausted`, `monthly_model_cost_limit`, plus
127
+ * `realtime_voice_unavailable`. Cleared on the next start.
128
+ */
129
+ refusal?: CodexRealtimeRefusal | null;
123
130
  };
124
131
 
132
+ export type CodexRealtimeRefusal = { code: string; message: string };
133
+
134
+ const REALTIME_REFUSAL_CODES = new Set([
135
+ "insufficient_credits",
136
+ "allowance_exhausted",
137
+ "monthly_model_cost_limit",
138
+ "realtime_voice_unavailable",
139
+ ]);
140
+
141
+ /** A definitive credit/availability refusal from Opengeni, with its plain message. */
142
+ export function codexRealtimeRefusal(error: unknown): CodexRealtimeRefusal | null {
143
+ if (!(error instanceof OpenGeniApiError) || !error.code) return null;
144
+ if (!REALTIME_REFUSAL_CODES.has(error.code)) return null;
145
+ const message = apiErrorMessage(error);
146
+ return {
147
+ code: error.code,
148
+ message:
149
+ message ??
150
+ (error.code === "insufficient_credits"
151
+ ? "Live voice needs Opengeni credits. Add credits to continue."
152
+ : "Live voice is unavailable right now."),
153
+ };
154
+ }
155
+
125
156
  export type CodexRealtimeControllerClient = {
126
157
  beginSessionRealtime(
127
158
  workspaceId: string,
@@ -178,6 +209,7 @@ export type CodexRealtimeOwnerStorage = Pick<Storage, "getItem" | "setItem" | "r
178
209
 
179
210
  /** Canonical browser-owner storage namespace for a public realtime model. */
180
211
  export function sessionRealtimeOwnerStorageNamespace(model: SessionRealtimeModel): string {
212
+ if (model === "opengeni-azure/gpt-live-1") return "azure-live-owner";
181
213
  if (model === "gpt-live-1-boulder-alpha") return "codex-realtime-owner";
182
214
  if (model === "supergrok/grok-voice-think-fast-2.0") return "xai-realtime-owner";
183
215
  return "gateway-realtime-owner";
@@ -355,6 +387,7 @@ export function createCodexRealtimeController(
355
387
  reconnectAttempt: 0,
356
388
  diagnostic: null,
357
389
  error: null,
390
+ refusal: null,
358
391
  };
359
392
  let active: ConnectionRuntime | null = null;
360
393
  let pendingAbort: AbortController | null = null;
@@ -371,6 +404,9 @@ export function createCodexRealtimeController(
371
404
  let generation = 0;
372
405
  let reconnectAttempt = 0;
373
406
  let recoveryTerminal = false;
407
+ // Whether this mode ever reached a live provider connection. A definitive
408
+ // failure before that ends the mode instead of leaving an empty call open.
409
+ let connectedInMode = false;
374
410
  let mutationTail = Promise.resolve();
375
411
  let connectionTask: Promise<void> | null = null;
376
412
  const acceptedDelegationItemIds = new Set(
@@ -699,6 +735,12 @@ export function createCodexRealtimeController(
699
735
  if (closed || stopping || isAbortError(error)) return;
700
736
  const message = safeError(error);
701
737
  if (error instanceof CodexRealtimeMicrophoneError) {
738
+ if (error.code !== "track_ended" && !connectedInMode) {
739
+ // No conversation exists yet: end the call and say how to fix the
740
+ // microphone instead of holding an empty "reconnecting" call open.
741
+ await endAfterFailure(microphoneFailureMessage(error), null);
742
+ return;
743
+ }
702
744
  if (error.code === "track_ended" && state.mode?.state === "active") {
703
745
  reconnectAttempt += 1;
704
746
  publish({
@@ -720,6 +762,11 @@ export function createCodexRealtimeController(
720
762
  transitionEnded("Realtime owner no longer exists");
721
763
  return;
722
764
  }
765
+ const refusal = codexRealtimeRefusal(error);
766
+ if (refusal) {
767
+ await endForRefusal(refusal);
768
+ return;
769
+ }
723
770
  if (error instanceof OpenGeniApiError && error.status === 409 && error.retryable && owner) {
724
771
  try {
725
772
  const reconciled = await begin(owner, true, false);
@@ -737,6 +784,9 @@ export function createCodexRealtimeController(
737
784
  return;
738
785
  }
739
786
  }
787
+ } else if (error instanceof OpenGeniApiError && !error.retryable && !connectedInMode) {
788
+ await endAfterFailure(message, null);
789
+ return;
740
790
  } else if (error instanceof OpenGeniApiError && !error.retryable) {
741
791
  stopTimers();
742
792
  if (!active) releaseMicrophone();
@@ -883,6 +933,15 @@ export function createCodexRealtimeController(
883
933
  });
884
934
  let connected: CodexRealtimeWebrtcSession;
885
935
  try {
936
+ // Persist a fragment provider's tail before the replacement broker reads
937
+ // its startup history, while the old connection still owns the ledger.
938
+ if (active?.transport.drain) {
939
+ await active.transport.drain();
940
+ await active.bridge.flush();
941
+ }
942
+ if (closed || stopping || abort.signal.aborted || pendingGeneration !== targetGeneration) {
943
+ throw abort.signal.reason ?? new DOMException("Aborted", "AbortError");
944
+ }
886
945
  const media = await ensureMicrophone(replaceMicrophone, abort.signal);
887
946
  const operationId = randomUUID();
888
947
  const commonTransportInput = {
@@ -1016,6 +1075,7 @@ export function createCodexRealtimeController(
1016
1075
  onFatal: (fatal) => onBridgeFatal(targetGeneration, bridge, fatal),
1017
1076
  });
1018
1077
  active = { generation: targetGeneration, transport: connected, bridge };
1078
+ connectedInMode = true;
1019
1079
  connected.setOutputMuted(state.outputMuted);
1020
1080
  recoveryTerminal = false;
1021
1081
  pendingAbort = null;
@@ -1098,6 +1158,7 @@ export function createCodexRealtimeController(
1098
1158
  };
1099
1159
 
1100
1160
  const heartbeat = async (): Promise<void> => {
1161
+ let stopInstruction: CodexRealtimeRefusal | null = null;
1101
1162
  await exclusive(async () => {
1102
1163
  const current = state.mode;
1103
1164
  if (!owner || !current || current.state !== "active") {
@@ -1121,6 +1182,43 @@ export function createCodexRealtimeController(
1121
1182
  return;
1122
1183
  }
1123
1184
  publish({ mode: result.mode, realtimeId: result.mode.id, error: null });
1185
+ stopInstruction = result.stop ?? null;
1186
+ });
1187
+ // The server stopped extending the lease (for example, out of credits):
1188
+ // drain and end gracefully inside the remaining lease.
1189
+ const instruction = stopInstruction as CodexRealtimeRefusal | null;
1190
+ stopInstruction = null;
1191
+ if (instruction && !stopping && !closed) await endForRefusal(instruction);
1192
+ };
1193
+
1194
+ /**
1195
+ * End the call because Opengeni refused it, keep final speech, and leave a
1196
+ * terminal, non-retrying state that explains why.
1197
+ */
1198
+ const endForRefusal = async (refusal: CodexRealtimeRefusal): Promise<void> =>
1199
+ await endAfterFailure(refusal.message, refusal);
1200
+
1201
+ const endAfterFailure = async (
1202
+ message: string,
1203
+ refusal: CodexRealtimeRefusal | null,
1204
+ ): Promise<void> => {
1205
+ try {
1206
+ await controller.stop();
1207
+ } catch {
1208
+ // The lease is no longer extended; a failed graceful end lapses on its own.
1209
+ stopping = false;
1210
+ closeBrowserResources();
1211
+ clearOwner();
1212
+ }
1213
+ recoveryTerminal = false;
1214
+ publish({
1215
+ status: "error",
1216
+ realtimeId: null,
1217
+ mode: null,
1218
+ bridge: null,
1219
+ diagnostic: diagnostic("terminal_stop", message, false),
1220
+ error: message,
1221
+ refusal,
1124
1222
  });
1125
1223
  };
1126
1224
 
@@ -1176,6 +1274,7 @@ export function createCodexRealtimeController(
1176
1274
  closed = false;
1177
1275
  stopping = false;
1178
1276
  recoveryTerminal = false;
1277
+ connectedInMode = false;
1179
1278
  const record: OwnerRecord = {
1180
1279
  version: OWNER_RECORD_VERSION,
1181
1280
  workspaceId: options.workspaceId,
@@ -1186,11 +1285,12 @@ export function createCodexRealtimeController(
1186
1285
  };
1187
1286
  owner = record;
1188
1287
  storage?.setItem(storageKey, JSON.stringify(record));
1189
- publish({ status: "starting", error: null, diagnostic: null });
1288
+ publish({ status: "starting", error: null, diagnostic: null, refusal: null });
1190
1289
  try {
1191
1290
  await begin(record, false);
1192
1291
  } catch (error) {
1193
1292
  closeBrowserResources(false);
1293
+ const refusal = codexRealtimeRefusal(error);
1194
1294
  const failedMode = state.mode as SessionRealtimeMode | null;
1195
1295
  if (failedMode?.state === "active") {
1196
1296
  await handleConnectionFailure(error, "reconnect", false);
@@ -1200,9 +1300,13 @@ export function createCodexRealtimeController(
1200
1300
  status: "error",
1201
1301
  realtimeId: null,
1202
1302
  mode: null,
1203
- error: safeError(error),
1303
+ error: refusal?.message ?? safeError(error),
1304
+ ...(refusal
1305
+ ? { refusal, diagnostic: diagnostic("terminal_stop", refusal.message, false) }
1306
+ : {}),
1204
1307
  });
1205
1308
  }
1309
+ if (refusal) return;
1206
1310
  throw error;
1207
1311
  }
1208
1312
  },
@@ -1211,6 +1315,8 @@ export function createCodexRealtimeController(
1211
1315
  if (recoveryTerminal && state.mode?.state === "active") return;
1212
1316
  const record = readOwnerRecord(storage, storageKey, options);
1213
1317
  if (!record) {
1318
+ // Keep a terminal failure readable after the server records the end.
1319
+ if (state.status === "error" && !state.mode) return;
1214
1320
  transitionEnded();
1215
1321
  return;
1216
1322
  }
@@ -1234,7 +1340,10 @@ export function createCodexRealtimeController(
1234
1340
  if (lifecycle.state === "ended") {
1235
1341
  const record = readOwnerRecord(storage, storageKey, options);
1236
1342
  if (record && record.operationId !== lifecycle.operationId) {
1237
- if (connectionTask || state.status === "active") return;
1343
+ // An earlier call's end is not news while this browser is starting
1344
+ // its own call (for example, waiting on the microphone prompt):
1345
+ // re-beginning would advance the lease and fail the pending start.
1346
+ if (connectionTask || state.status === "active" || state.status === "starting") return;
1238
1347
  closed = false;
1239
1348
  stopping = false;
1240
1349
  owner = record;
@@ -1250,6 +1359,9 @@ export function createCodexRealtimeController(
1250
1359
  await connectionTask;
1251
1360
  return;
1252
1361
  }
1362
+ // The server's end of a call that failed to start must not wipe the
1363
+ // failure the user still needs to read.
1364
+ if (state.status === "error" && !state.mode) return;
1253
1365
  if (state.realtimeId === null || lifecycle.realtimeId === state.realtimeId) {
1254
1366
  transitionEnded(`Realtime ended: ${lifecycle.reason}`);
1255
1367
  }
@@ -1259,11 +1371,22 @@ export function createCodexRealtimeController(
1259
1371
  if (state.status === "active" && state.realtimeId === lifecycle.realtimeId) return;
1260
1372
  const record = readOwnerRecord(storage, storageKey, options);
1261
1373
  if (!record || record.operationId !== lifecycle.operationId) {
1262
- closeBrowserResources();
1263
- owner = null;
1264
1374
  const leaseExpiresAt = Date.parse(lifecycle.leaseExpiresAt);
1265
1375
  const remainingLeaseMs = leaseExpiresAt - now().getTime();
1266
- if (Number.isFinite(remainingLeaseMs) && remainingLeaseMs <= 0) {
1376
+ const leaseExpired = Number.isFinite(remainingLeaseMs) && remainingLeaseMs <= 0;
1377
+ // An earlier call whose lease already ran out (its end not yet in the
1378
+ // events) is not another owner: it must not fail the call this
1379
+ // browser is starting.
1380
+ if (
1381
+ leaseExpired &&
1382
+ lifecycle.realtimeId !== state.realtimeId &&
1383
+ (connectionTask || state.status === "starting")
1384
+ ) {
1385
+ return;
1386
+ }
1387
+ closeBrowserResources();
1388
+ owner = null;
1389
+ if (leaseExpired) {
1267
1390
  transitionEnded("Realtime lease expired");
1268
1391
  return;
1269
1392
  }
@@ -1337,6 +1460,7 @@ export function createCodexRealtimeController(
1337
1460
  connectionTask = null;
1338
1461
  void retiredTask?.catch(() => undefined);
1339
1462
  try {
1463
+ await active?.transport.drain?.();
1340
1464
  await active?.bridge.sealAndFlush();
1341
1465
  closeBrowserResources();
1342
1466
  let current = state.mode;
@@ -1708,6 +1832,37 @@ function isAbortError(error: unknown): boolean {
1708
1832
  return error instanceof Error && error.name === "AbortError";
1709
1833
  }
1710
1834
 
1835
+ function microphoneFailureMessage(error: CodexRealtimeMicrophoneError): string {
1836
+ switch (error.code) {
1837
+ case "permission_denied":
1838
+ return "Microphone access is blocked. Allow it in site settings, then try again.";
1839
+ case "device_not_found":
1840
+ return "No microphone was found. Connect one, then try again.";
1841
+ case "device_unavailable":
1842
+ return "Your microphone is busy or unavailable. Close other apps using it, then try again.";
1843
+ default:
1844
+ return /timed out/i.test(error.message)
1845
+ ? "Microphone access wasn't granted in time. Allow it when your browser asks, then try again."
1846
+ : "The microphone could not start. Try again.";
1847
+ }
1848
+ }
1849
+
1850
+ /** The server's own message, without the transport prefix or reference id. */
1851
+ function apiErrorMessage(error: OpenGeniApiError): string | null {
1852
+ try {
1853
+ const body = JSON.parse(error.body) as Record<string, unknown>;
1854
+ const nested =
1855
+ body.error && typeof body.error === "object" ? (body.error as Record<string, unknown>) : body;
1856
+ return typeof nested.message === "string" && nested.message ? nested.message : null;
1857
+ } catch {
1858
+ return null;
1859
+ }
1860
+ }
1861
+
1711
1862
  function safeError(error: unknown): string {
1712
- return error instanceof Error ? error.message : "Codex realtime browser controller failed";
1863
+ if (error instanceof OpenGeniApiError) {
1864
+ const message = apiErrorMessage(error);
1865
+ if (message) return /[.!?]$/.test(message) ? message : `${message}.`;
1866
+ }
1867
+ return error instanceof Error ? error.message : "Live voice failed in this browser.";
1713
1868
  }
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * These shapes are pinned to openai/codex@fa1d4c40. V3 delegates work with
5
5
  * `delegation.created`; it does not expose the ordinary Realtime V2
6
- * `function_call` protocol.
6
+ * `function_call` protocol. `transcript.segment` is an application adapter
7
+ * extension for providers that emit fragments without semantic turn boundaries.
7
8
  */
8
9
 
9
10
  export const CODEX_REALTIME_INITIAL_ITEMS_MAX_COUNT = 128;
@@ -40,7 +41,7 @@ export type CodexRealtimeV3Event =
40
41
  endMs: number | null;
41
42
  })
42
43
  | (ProviderEventIdentity & {
43
- type: "turn.done";
44
+ type: "turn.done" | "transcript.segment";
44
45
  turnId: string;
45
46
  role: "user" | "assistant";
46
47
  transcript: string;
@@ -173,7 +174,7 @@ export function parseCodexRealtimeV3Event(payload: string): CodexRealtimeV3Parse
173
174
  };
174
175
  }
175
176
 
176
- if (type === "turn.done") {
177
+ if (type === "turn.done" || type === "transcript.segment") {
177
178
  const turn = record(event.turn);
178
179
  const turnIdField = boundedStringField(turn, "id", CODEX_REALTIME_V3_MAX_IDENTIFIER_BYTES);
179
180
  const transcriptField = boundedStringField(
@@ -316,7 +316,7 @@ export function createCodexRealtimeV3Bridge(
316
316
  if (closed || fatal) break;
317
317
 
318
318
  for (const entry of result.outbound) {
319
- // This is OpenGeni's durable browser-delivery acknowledgment. The
319
+ // This is Opengeni's durable browser-delivery acknowledgment. The
320
320
  // provider send below remains at-least-once because pinned V3 exposes
321
321
  // no provider receipt and providerAckSequences is never populated.
322
322
  if (entry.clientAckedAt === null && !clientReceivedSequences.has(entry.sequence)) {
@@ -324,6 +324,12 @@ export function createCodexRealtimeV3Bridge(
324
324
  clientAckThroughSequence = Math.max(clientAckThroughSequence ?? 0, entry.sequence);
325
325
  }
326
326
  }
327
+ // Persist final inbound fragments even after the provider starts closing.
328
+ // Pending outbound context remains replayable on the next connection.
329
+ if (options.events.readyState !== "open") {
330
+ publish();
331
+ continue;
332
+ }
327
333
  for (let index = 0; index < result.outbound.length;) {
328
334
  const entry = result.outbound[index]!;
329
335
  if (sentSequences.has(entry.sequence)) {
@@ -497,7 +503,7 @@ export function createCodexRealtimeV3Bridge(
497
503
  }
498
504
  } else if (event.type === "output_audio.delta") {
499
505
  speaking = true;
500
- } else if (event.type === "turn.done") {
506
+ } else if (event.type === "turn.done" || event.type === "transcript.segment") {
501
507
  speaking = false;
502
508
  if (event.transcript.length > 0 && !finalizedTurnIds.has(event.turnId)) {
503
509
  const coveredByDelegationItemId =
@@ -570,7 +576,7 @@ export function createCodexRealtimeV3Bridge(
570
576
 
571
577
  function finalTranscript(
572
578
  randomUUID: () => string,
573
- event: Extract<CodexRealtimeV3Event, { type: "turn.done" }>,
579
+ event: Extract<CodexRealtimeV3Event, { type: "turn.done" | "transcript.segment" }>,
574
580
  coveredByDelegationItemId: string | null,
575
581
  modelContext: string | undefined,
576
582
  ): SessionRealtimeInboundEntry {
@@ -581,6 +587,9 @@ function finalTranscript(
581
587
  text: event.transcript,
582
588
  payload: {
583
589
  turnId: event.turnId,
590
+ ...(event.type === "transcript.segment"
591
+ ? { transcriptSource: "provider_fragments", grouping: "application_segment" }
592
+ : {}),
584
593
  ...(coveredByDelegationItemId ? { coveredByDelegationItemId } : {}),
585
594
  },
586
595
  ...(modelContext ? { modelContext } : {}),
@@ -37,6 +37,8 @@ export type AcquireCodexRealtimeMicrophoneOptions = {
37
37
 
38
38
  export type StartCodexRealtimeWebrtcOptions = {
39
39
  negotiate: CodexRealtimeNegotiator;
40
+ /** Exact configured provider model expected from the server broker. */
41
+ expectedModel?: CodexRealtimeWebrtcResponse["model"];
40
42
  realtimeId: string;
41
43
  operationId: string;
42
44
  browserInstanceId: string;
@@ -77,6 +79,8 @@ export type CodexRealtimeWebrtcSession = {
77
79
  setOutputMuted(muted: boolean): void;
78
80
  activateRemoteAudio(): void;
79
81
  retryAudibleOutput(): Promise<boolean>;
82
+ /** Gracefully finish provider output before sealing the durable ledger. */
83
+ drain?(): Promise<void>;
80
84
  /** Idempotently close media, data channel, and peer transport. */
81
85
  stop(): void;
82
86
  };
@@ -85,7 +89,7 @@ export type CodexRealtimeWebrtcSession = {
85
89
  * Complete the browser half of native connected-Codex GPT-Live V3 negotiation.
86
90
  * Provider credentials never enter this boundary: `negotiate` sends SDP,
87
91
  * public session configuration, and the active browser-owner proof only to the
88
- * OpenGeni API.
92
+ * Opengeni API.
89
93
  */
90
94
  export async function startCodexRealtimeWebrtc(
91
95
  options: StartCodexRealtimeWebrtcOptions,
@@ -260,8 +264,12 @@ export async function startCodexRealtimeWebrtc(
260
264
  { signal: options.signal },
261
265
  );
262
266
  throwIfAborted(options.signal);
263
- if (answer.version !== "v3" || answer.model !== "gpt-live-1-boulder-alpha" || !answer.sdp) {
264
- throw new Error("OpenGeni returned an incompatible Codex realtime answer");
267
+ if (
268
+ answer.version !== "v3" ||
269
+ answer.model !== (options.expectedModel ?? "gpt-live-1-boulder-alpha") ||
270
+ !answer.sdp
271
+ ) {
272
+ throw new Error("Opengeni returned an incompatible Codex realtime answer");
265
273
  }
266
274
  await peerConnection.setRemoteDescription({
267
275
  type: "answer",
@@ -2,23 +2,11 @@ import type {
2
2
  CompanyBrainContextReceiptPage as ContractCompanyBrainContextReceiptPage,
3
3
  CompanyBrainKnowledgeProposalPage as ContractCompanyBrainKnowledgeProposalPage,
4
4
  CompanyBrainOkfPackage as ContractCompanyBrainOkfPackage,
5
- KnowledgeBrowseRequest as ContractKnowledgeBrowseRequest,
6
- KnowledgeBrowseResponse as ContractKnowledgeBrowseResponse,
7
- KnowledgeGetResponse as ContractKnowledgeGetResponse,
8
- KnowledgeRecord as ContractKnowledgeRecord,
9
- KnowledgeSearchRequest as ContractKnowledgeSearchRequest,
10
- KnowledgeSearchResponse as ContractKnowledgeSearchResponse,
11
5
  } from "@opengeni/contracts";
12
6
 
13
7
  export type CompanyBrainOkfPackage = ContractCompanyBrainOkfPackage;
14
8
  export type CompanyBrainContextReceiptPage = ContractCompanyBrainContextReceiptPage;
15
9
  export type CompanyBrainKnowledgeProposalPage = ContractCompanyBrainKnowledgeProposalPage;
16
- export type CompanyBrainKnowledgeSearchRequest = ContractKnowledgeSearchRequest;
17
- export type CompanyBrainKnowledgeSearchResponse = ContractKnowledgeSearchResponse;
18
- export type CompanyBrainKnowledgeBrowseRequest = ContractKnowledgeBrowseRequest;
19
- export type CompanyBrainKnowledgeBrowseResponse = ContractKnowledgeBrowseResponse;
20
- export type CompanyBrainKnowledgeGetResponse = ContractKnowledgeGetResponse;
21
- export type CompanyBrainKnowledgeRecord = ContractKnowledgeRecord;
22
10
 
23
11
  export type CompanyBrainContextReceiptListOptions = {
24
12
  attemptId?: string;
@@ -38,34 +26,6 @@ function inspectorPath(workspaceId: string, suffix: string): string {
38
26
  return `/v1/workspaces/${workspaceId}/company-brain/${suffix}`;
39
27
  }
40
28
 
41
- /** Human-authorized, permission-first Knowledge search for Company Brain inspectors. */
42
- export function searchCompanyBrainKnowledge(
43
- client: CompanyBrainInspectorTransport,
44
- workspaceId: string,
45
- request: CompanyBrainKnowledgeSearchRequest,
46
- ): Promise<CompanyBrainKnowledgeSearchResponse> {
47
- return client.requestJson("POST", inspectorPath(workspaceId, "knowledge/search"), request);
48
- }
49
-
50
- export function getCompanyBrainKnowledge(
51
- client: CompanyBrainInspectorTransport,
52
- workspaceId: string,
53
- recordId: string,
54
- ): Promise<CompanyBrainKnowledgeGetResponse> {
55
- return client.requestJson(
56
- "GET",
57
- `${inspectorPath(workspaceId, "knowledge/record")}?id=${encodeURIComponent(recordId)}`,
58
- );
59
- }
60
-
61
- export function browseCompanyBrainKnowledge(
62
- client: CompanyBrainInspectorTransport,
63
- workspaceId: string,
64
- request: CompanyBrainKnowledgeBrowseRequest = {},
65
- ): Promise<CompanyBrainKnowledgeBrowseResponse> {
66
- return client.requestJson("POST", inspectorPath(workspaceId, "knowledge/browse"), request);
67
- }
68
-
69
29
  export function listCompanyBrainContextReceipts(
70
30
  client: CompanyBrainInspectorTransport,
71
31
  workspaceId: string,
@@ -82,6 +82,19 @@ export type CompanyProfileMutationResponse = {
82
82
  head: CompanyProfileHead | null;
83
83
  event: CompanyProfileActivationEvent | null;
84
84
  };
85
+ export type CompanyProfileAgentPolicyMode = "off" | "suggest" | "automatic";
86
+ export type CompanyProfileAgentPolicy = {
87
+ organizationId: string;
88
+ mode: CompanyProfileAgentPolicyMode;
89
+ version: number;
90
+ updatedAt: string;
91
+ changed?: boolean;
92
+ };
93
+ export type UpdateCompanyProfileAgentPolicyRequest = {
94
+ mode: CompanyProfileAgentPolicyMode;
95
+ expectedVersion: number;
96
+ operationId: string;
97
+ };
85
98
  export type CompanyProfileDiffRequest = { fromRevisionId: string; toRevisionId: string };
86
99
  export type CompanyProfileDiffResponse = {
87
100
  from: CompanyProfileRevision;
package/src/core.ts CHANGED
@@ -1,7 +1,21 @@
1
1
  /** Backward-compatible non-artifact client surface. */
2
2
  export { OpenGeniDocumentAuthorityClient as OpenGeniCoreClient } from "./document-authority-client";
3
+ export {
4
+ OpenGeniToolCallError,
5
+ OpenGeniToolReapprovalRequiredError,
6
+ OpenGeniToolsClient,
7
+ } from "./tools";
8
+ export type {
9
+ OpenGeniGeneratedTools,
10
+ OpenGeniDynamicToolNode,
11
+ OpenGeniToolCallOptions,
12
+ OpenGeniToolFunction,
13
+ OpenGeniToolsFacade,
14
+ OpenGeniWorkspaceTools,
15
+ } from "./tools";
3
16
  export type {
4
17
  FetchLike,
18
+ FetchResponse,
5
19
  GetSessionOptions,
6
20
  OpenGeniClientOptions,
7
21
  OpenGeniRequestOptions,
@@ -13,9 +27,15 @@ export type {
13
27
  export {
14
28
  OpenGeniApiContractMismatchError,
15
29
  OpenGeniApiError,
30
+ OpenGeniSetupError,
31
+ OpenGeniSecureContextRequiredError,
16
32
  OpenGeniSessionListCursorError,
17
33
  OpenGeniStreamError,
34
+ formatErrorMessage,
18
35
  isRetryableStreamError,
19
36
  } from "./errors";
37
+ export type { OpenGeniSecureContextRequiredReason } from "./errors";
38
+ export { parseDeprecationNotice } from "./deprecation";
39
+ export type { OpenGeniDeprecationHandler, OpenGeniDeprecationNotice } from "./deprecation";
20
40
  export { resolveWorkspaceVoiceInputEnabled } from "./transcription";
21
41
  export { OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION } from "./types";