@opengeni/sdk 1.3.0 → 1.4.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 (59) hide show
  1. package/README.md +25 -3
  2. package/dist/adapters/next.js +2 -2
  3. package/dist/artifacts.js +4 -4
  4. package/dist/azure-live-transport.d.ts +32 -0
  5. package/dist/browser.js +3 -3
  6. package/dist/chat/index.js +5 -5
  7. package/dist/{chunk-IB6SUE3R.js → chunk-DTIMLEPB.js} +1 -1
  8. package/dist/chunk-DTIMLEPB.js.map +1 -0
  9. package/dist/{chunk-VYCTL62C.js → chunk-GZQSWAKD.js} +1 -1
  10. package/dist/chunk-GZQSWAKD.js.map +1 -0
  11. package/dist/{chunk-2UXZUCEZ.js → chunk-JOI7XCG6.js} +110 -9
  12. package/dist/chunk-JOI7XCG6.js.map +1 -0
  13. package/dist/{chunk-CREM7YRK.js → chunk-TRUFUTDI.js} +2 -2
  14. package/dist/{chunk-CNSPJYF6.js → chunk-TX6ED3AP.js} +210 -6
  15. package/dist/chunk-TX6ED3AP.js.map +1 -0
  16. package/dist/{chunk-ZBICMU4Z.js → chunk-XIQIV4VL.js} +2 -2
  17. package/dist/{chunk-G5NZGPTB.js → chunk-XPWH3HKG.js} +2 -2
  18. package/dist/{chunk-YVH2PH4X.js → chunk-YWLJGEUB.js} +2 -2
  19. package/dist/{chunk-RSKO3CMZ.js → chunk-ZOSRUR2Q.js} +2 -2
  20. package/dist/codex-realtime-controller.d.ts +13 -0
  21. package/dist/codex-realtime-controller.js +3 -1
  22. package/dist/codex-realtime-v3-wire.d.ts +3 -2
  23. package/dist/codex-realtime.d.ts +4 -0
  24. package/dist/core.js +4 -4
  25. package/dist/document-authority.js +3 -3
  26. package/dist/editable-artifacts.js +1 -1
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +55 -18
  29. package/dist/index.js.map +1 -1
  30. package/dist/realtime.d.ts +1 -1
  31. package/dist/realtime.js +248 -2
  32. package/dist/realtime.js.map +1 -1
  33. package/dist/session-proxy-client.d.ts +7 -3
  34. package/dist/session-proxy.d.ts +38 -7
  35. package/dist/session-proxy.js +2 -2
  36. package/dist/site.js +3 -3
  37. package/dist/transcription.d.ts +1 -1
  38. package/dist/types.d.ts +47 -8
  39. package/package.json +3 -3
  40. package/src/azure-live-transport.ts +286 -0
  41. package/src/codex-realtime-controller.ts +148 -4
  42. package/src/codex-realtime-v3-wire.ts +4 -3
  43. package/src/codex-realtime-v3.ts +11 -2
  44. package/src/codex-realtime.ts +9 -1
  45. package/src/index.ts +3 -0
  46. package/src/realtime.ts +15 -6
  47. package/src/session-proxy-client.ts +7 -3
  48. package/src/session-proxy.ts +321 -11
  49. package/src/transcription.ts +3 -0
  50. package/src/types.ts +49 -5
  51. package/dist/chunk-2UXZUCEZ.js.map +0 -1
  52. package/dist/chunk-CNSPJYF6.js.map +0 -1
  53. package/dist/chunk-IB6SUE3R.js.map +0 -1
  54. package/dist/chunk-VYCTL62C.js.map +0 -1
  55. /package/dist/{chunk-CREM7YRK.js.map → chunk-TRUFUTDI.js.map} +0 -0
  56. /package/dist/{chunk-ZBICMU4Z.js.map → chunk-XIQIV4VL.js.map} +0 -0
  57. /package/dist/{chunk-G5NZGPTB.js.map → chunk-XPWH3HKG.js.map} +0 -0
  58. /package/dist/{chunk-YVH2PH4X.js.map → chunk-YWLJGEUB.js.map} +0 -0
  59. /package/dist/{chunk-RSKO3CMZ.js.map → chunk-ZOSRUR2Q.js.map} +0 -0
@@ -227,4 +227,4 @@ export {
227
227
  authorizeTranscriptionAdapter,
228
228
  createTranscriptionSessionRequest
229
229
  };
230
- //# sourceMappingURL=chunk-VYCTL62C.js.map
230
+ //# sourceMappingURL=chunk-GZQSWAKD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transcription.ts"],"sourcesContent":["/**\n * Framework- and transport-agnostic speech-to-text capability contract.\n *\n * Audio transport, microphone access, credentials, and provider SDKs belong to\n * host-supplied adapters. This module deliberately contains no browser globals\n * and no provider implementation.\n */\n\nimport type { WorkspaceVoiceInputSettings } from \"./types\";\n\nexport type TranscriptionCredentialMode = \"managed\" | \"byok\";\n\nexport type WorkspaceTranscriptionTarget = {\n provider: string;\n model: string | null;\n credentialMode: TranscriptionCredentialMode;\n /** Workspace-scoped connection reference. This is never a secret. */\n credentialConnectionId: string | null;\n region: string | null;\n};\n\nexport type WorkspaceTranscriptionPolicy = {\n enabled: boolean;\n /** Exact admin-accepted policy identity; required whenever enabled. */\n acceptanceId: string | null;\n primary: WorkspaceTranscriptionTarget | null;\n /** Explicit language preference. Required when automatic detection is not accepted. */\n language: string | null;\n /** Whether the accepted adapter may automatically detect the spoken language. */\n autoDetectLanguage: boolean;\n /** Whether the accepted adapter may identify distinct speakers. */\n diarization: {\n enabled: boolean;\n maxSpeakers: number | null;\n };\n retention: {\n mode: \"none\" | \"provider-policy\";\n maxDays: number | null;\n };\n privacy: {\n allowProviderLogging: boolean;\n allowProviderTraining: boolean;\n };\n fallback: {\n mode: \"disabled\" | \"explicit\";\n targets: WorkspaceTranscriptionTarget[];\n };\n cost: {\n currency: \"USD\";\n maxPerHour: number | null;\n maxPerMonth: number | null;\n };\n};\n\nexport const DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY: WorkspaceTranscriptionPolicy = {\n enabled: false,\n acceptanceId: null,\n primary: null,\n language: null,\n autoDetectLanguage: false,\n diarization: { enabled: false, maxSpeakers: null },\n retention: { mode: \"none\", maxDays: null },\n privacy: { allowProviderLogging: false, allowProviderTraining: false },\n fallback: { mode: \"disabled\", targets: [] },\n cost: { currency: \"USD\", maxPerHour: null, maxPerMonth: null },\n};\n\nexport type TranscriptionAdapterDescriptor = {\n provider: string;\n model: string | null;\n credentialMode: TranscriptionCredentialMode;\n region: string | null;\n};\n\nexport type TranscriptionTargetSelection =\n | { kind: \"primary\" }\n | { kind: \"fallback\"; index: number };\n\nexport type TranscriptionPolicyBlockReason =\n | \"disabled\"\n | \"unaccepted\"\n | \"target_missing\"\n | \"fallback_disabled\"\n | \"fallback_unaccepted\"\n | \"provider_mismatch\"\n | \"model_mismatch\"\n | \"credential_mode_mismatch\"\n | \"region_mismatch\";\n\nexport type TranscriptionAuthorization =\n | {\n authorized: true;\n acceptanceId: string;\n target: WorkspaceTranscriptionTarget;\n selection: TranscriptionTargetSelection;\n }\n | { authorized: false; reason: TranscriptionPolicyBlockReason };\n\nexport type TranscriptionLifecycleStatus =\n | \"idle\"\n | \"requesting-permission\"\n | \"listening\"\n | \"reconnecting\"\n | \"cancelling\"\n | \"closed\"\n | \"error\";\n\nexport type TranscriptionErrorCode =\n | \"insufficient_credits\"\n | \"allowance_exhausted\"\n | \"monthly_model_cost_limit\"\n | \"permission_denied\"\n | \"not_supported\"\n | \"network\"\n | \"provider\"\n | \"policy_blocked\"\n | \"timeout\"\n | \"cancelled\"\n | \"unavailable\"\n | \"too_large\"\n | \"invalid_audio\"\n | \"unknown\";\n\n/** Native browser voice input defaults to the configured deployment capability. */\nexport function resolveWorkspaceVoiceInputEnabled(settings: unknown): boolean | null {\n if (!isRecord(settings)) return null;\n const voiceInput = settings.voiceInput;\n if (isRecord(voiceInput) && typeof voiceInput.enabled === \"boolean\") {\n return (voiceInput as WorkspaceVoiceInputSettings).enabled;\n }\n const legacy = settings.transcription;\n return isRecord(legacy) && typeof legacy.enabled === \"boolean\" ? legacy.enabled : null;\n}\n\nexport type TranscriptionTimeSpan = {\n startMilliseconds: number;\n endMilliseconds: number;\n};\n\nexport type TranscriptionSpeaker = {\n /** Provider-neutral identity stable within the local transcription session. */\n id: string;\n label?: string | undefined;\n};\n\nexport type TranscriptionWord = {\n text: string;\n span: TranscriptionTimeSpan;\n confidence?: number | undefined;\n speaker?: TranscriptionSpeaker | undefined;\n};\n\n/** Optional result detail; adapters omit fields their provider cannot supply. */\nexport type TranscriptionResultMetadata = {\n detectedLanguage?: string | undefined;\n span?: TranscriptionTimeSpan | undefined;\n confidence?: number | undefined;\n speaker?: TranscriptionSpeaker | undefined;\n words?: TranscriptionWord[] | undefined;\n};\n\nexport type TranscriptionDiagnostic = {\n operation: \"start\" | \"session\" | \"cancel\" | \"close\";\n code: TranscriptionErrorCode;\n /** Exact diagnostic detail forwarded by React without content rewriting. */\n detail: string;\n};\n\ntype TranscriptionEventBase = {\n /** Stable across reconnects and explicitly accepted fallback attempts. */\n localSessionId: string;\n /** Adapter-monotonic across the entire local session, including replay. */\n sequence: number;\n occurredAt: string;\n};\n\nexport type TranscriptionEvent =\n | (TranscriptionEventBase & { type: \"permission.requested\" })\n | (TranscriptionEventBase & {\n type: \"session.opened\";\n providerSessionId: string;\n })\n | (TranscriptionEventBase & {\n type: \"transcript.partial\";\n segmentId: string;\n text: string;\n metadata?: TranscriptionResultMetadata | undefined;\n })\n | (TranscriptionEventBase & {\n type: \"transcript.final\";\n segmentId: string;\n text: string;\n /** Stable provider/coordinator acceptance identity used for dedupe. */\n providerAcceptanceId: string;\n metadata?: TranscriptionResultMetadata | undefined;\n })\n | (TranscriptionEventBase & {\n type: \"usage\";\n audioMilliseconds: number;\n costUsd: number | null;\n })\n | (TranscriptionEventBase & {\n type: \"session.reconnecting\";\n attempt: number;\n reason: string;\n })\n | (TranscriptionEventBase & {\n type: \"session.error\";\n code: TranscriptionErrorCode;\n recoverable: boolean;\n })\n | (TranscriptionEventBase & {\n type: \"session.closed\";\n reason: \"completed\" | \"cancelled\" | \"error\" | \"replaced\";\n });\n\nexport type TranscriptionSessionRequest = {\n localSessionId: string;\n policyAcceptanceId: string;\n selection: TranscriptionTargetSelection;\n target: WorkspaceTranscriptionTarget;\n language: string | null;\n autoDetectLanguage: boolean;\n diarization: WorkspaceTranscriptionPolicy[\"diarization\"];\n retention: WorkspaceTranscriptionPolicy[\"retention\"];\n privacy: WorkspaceTranscriptionPolicy[\"privacy\"];\n cost: WorkspaceTranscriptionPolicy[\"cost\"];\n /** A replacement/reconnect adapter must emit events above this floor. */\n sequenceFloor: number;\n};\n\nexport type TranscriptionEventListener = (event: TranscriptionEvent) => void;\n\nexport type TranscriptionAdapterStartContext = {\n /** Aborted on local cancellation, policy replacement, timeout, or unmount. */\n signal: AbortSignal;\n /** Non-UI observability seam; callers receive the exact provider detail. */\n reportDiagnostic: (diagnostic: TranscriptionDiagnostic) => void;\n};\n\nexport type TranscriptionSession = {\n readonly localSessionId: string;\n cancel(reason?: string): Promise<void>;\n close(): Promise<void>;\n};\n\nexport type TranscriptionAdapter = {\n readonly descriptor: TranscriptionAdapterDescriptor;\n start(\n request: TranscriptionSessionRequest,\n listener: TranscriptionEventListener,\n context: TranscriptionAdapterStartContext,\n ): Promise<TranscriptionSession>;\n};\n\n/** Invalid or absent settings always resolve to the fail-closed default. */\nexport function resolveWorkspaceTranscriptionPolicy(\n settings: unknown,\n): WorkspaceTranscriptionPolicy {\n if (!isRecord(settings)) return cloneDefaultPolicy();\n const candidate = settings.transcription;\n if (!isWorkspaceTranscriptionPolicy(candidate)) return cloneDefaultPolicy();\n return {\n ...candidate,\n primary: candidate.primary ? normalizeTarget(candidate.primary) : null,\n language: candidate.language?.trim() ?? null,\n diarization: { ...candidate.diarization },\n retention: { ...candidate.retention },\n privacy: { ...candidate.privacy },\n fallback: {\n mode: candidate.fallback.mode,\n targets: candidate.fallback.targets.map(normalizeTarget),\n },\n cost: { ...candidate.cost },\n };\n}\n\n/**\n * Speech authorization is intentionally independent from turn model policy.\n * Every selected adapter must match one exact admin-accepted target.\n */\nexport function authorizeTranscriptionAdapter(\n policy: WorkspaceTranscriptionPolicy,\n descriptor: TranscriptionAdapterDescriptor,\n selection: TranscriptionTargetSelection = { kind: \"primary\" },\n): TranscriptionAuthorization {\n if (!isWorkspaceTranscriptionPolicy(policy)) {\n return { authorized: false, reason: \"unaccepted\" };\n }\n if (!policy.enabled) return { authorized: false, reason: \"disabled\" };\n if (!policy.acceptanceId) return { authorized: false, reason: \"unaccepted\" };\n let target: WorkspaceTranscriptionTarget | null | undefined;\n if (selection.kind === \"primary\") {\n target = policy.primary;\n } else {\n if (policy.fallback.mode !== \"explicit\") {\n return { authorized: false, reason: \"fallback_disabled\" };\n }\n target = policy.fallback.targets[selection.index];\n if (!target) return { authorized: false, reason: \"fallback_unaccepted\" };\n }\n if (!target) return { authorized: false, reason: \"target_missing\" };\n const acceptedTarget = normalizeTarget(target);\n if (acceptedTarget.provider !== descriptor.provider) {\n return { authorized: false, reason: \"provider_mismatch\" };\n }\n if (acceptedTarget.model !== descriptor.model) {\n return { authorized: false, reason: \"model_mismatch\" };\n }\n if (acceptedTarget.credentialMode !== descriptor.credentialMode) {\n return { authorized: false, reason: \"credential_mode_mismatch\" };\n }\n if (acceptedTarget.region !== descriptor.region) {\n return { authorized: false, reason: \"region_mismatch\" };\n }\n return {\n authorized: true,\n acceptanceId: policy.acceptanceId,\n target: acceptedTarget,\n selection,\n };\n}\n\nexport function createTranscriptionSessionRequest(input: {\n policy: WorkspaceTranscriptionPolicy;\n adapter: TranscriptionAdapter;\n localSessionId: string;\n selection?: TranscriptionTargetSelection | undefined;\n sequenceFloor?: number | undefined;\n}): TranscriptionSessionRequest | null {\n const sequenceFloor = input.sequenceFloor ?? 0;\n if (!Number.isSafeInteger(sequenceFloor) || sequenceFloor < 0) return null;\n const authorization = authorizeTranscriptionAdapter(\n input.policy,\n input.adapter.descriptor,\n input.selection,\n );\n if (!authorization.authorized) return null;\n return {\n localSessionId: input.localSessionId,\n policyAcceptanceId: authorization.acceptanceId,\n selection: authorization.selection,\n target: { ...authorization.target },\n language: input.policy.language?.trim() ?? null,\n autoDetectLanguage: input.policy.autoDetectLanguage,\n diarization: { ...input.policy.diarization },\n retention: { ...input.policy.retention },\n privacy: { ...input.policy.privacy },\n cost: { ...input.policy.cost },\n sequenceFloor,\n };\n}\n\nfunction cloneDefaultPolicy(): WorkspaceTranscriptionPolicy {\n return {\n ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,\n diarization: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.diarization },\n retention: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.retention },\n privacy: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.privacy },\n fallback: { mode: \"disabled\", targets: [] },\n cost: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.cost },\n };\n}\n\nfunction isWorkspaceTranscriptionPolicy(value: unknown): value is WorkspaceTranscriptionPolicy {\n if (!isRecord(value) || typeof value.enabled !== \"boolean\") return false;\n if (\n !hasOnlyKeys(value, [\n \"enabled\",\n \"acceptanceId\",\n \"primary\",\n \"language\",\n \"autoDetectLanguage\",\n \"diarization\",\n \"retention\",\n \"privacy\",\n \"fallback\",\n \"cost\",\n ])\n ) {\n return false;\n }\n if (!(value.acceptanceId === null || isUuid(value.acceptanceId))) return false;\n if (!(value.primary === null || isTarget(value.primary))) return false;\n if (!(value.language === null || isBoundedString(value.language, 64))) return false;\n if (typeof value.autoDetectLanguage !== \"boolean\") return false;\n if (\n !isRecord(value.diarization) ||\n !hasOnlyKeys(value.diarization, [\"enabled\", \"maxSpeakers\"]) ||\n typeof value.diarization.enabled !== \"boolean\" ||\n !(\n value.diarization.maxSpeakers === null ||\n (isBoundedInteger(value.diarization.maxSpeakers, 100) && value.diarization.maxSpeakers >= 2)\n )\n ) {\n return false;\n }\n if (!value.diarization.enabled && value.diarization.maxSpeakers !== null) return false;\n if (!isRecord(value.retention) || !hasOnlyKeys(value.retention, [\"mode\", \"maxDays\"])) {\n return false;\n }\n if (value.retention.mode !== \"none\" && value.retention.mode !== \"provider-policy\") return false;\n if (!(value.retention.maxDays === null || isBoundedInteger(value.retention.maxDays, 3650))) {\n return false;\n }\n if (\n !isRecord(value.privacy) ||\n !hasOnlyKeys(value.privacy, [\"allowProviderLogging\", \"allowProviderTraining\"]) ||\n typeof value.privacy.allowProviderLogging !== \"boolean\" ||\n typeof value.privacy.allowProviderTraining !== \"boolean\"\n ) {\n return false;\n }\n if (!isRecord(value.fallback) || !hasOnlyKeys(value.fallback, [\"mode\", \"targets\"])) {\n return false;\n }\n if (value.fallback.mode !== \"disabled\" && value.fallback.mode !== \"explicit\") return false;\n if (\n !Array.isArray(value.fallback.targets) ||\n value.fallback.targets.length > 8 ||\n !value.fallback.targets.every(isTarget)\n ) {\n return false;\n }\n if (value.fallback.mode === \"disabled\" && value.fallback.targets.length !== 0) return false;\n if (value.fallback.mode === \"explicit\" && value.fallback.targets.length === 0) return false;\n if (\n !isRecord(value.cost) ||\n !hasOnlyKeys(value.cost, [\"currency\", \"maxPerHour\", \"maxPerMonth\"]) ||\n value.cost.currency !== \"USD\"\n ) {\n return false;\n }\n if (!isNullableBoundedNumber(value.cost.maxPerHour, 10_000)) return false;\n if (!isNullableBoundedNumber(value.cost.maxPerMonth, 1_000_000)) return false;\n if (value.enabled && (!value.acceptanceId || !value.primary)) return false;\n if (value.enabled && !value.autoDetectLanguage && value.language === null) return false;\n if (value.autoDetectLanguage && value.language !== null) return false;\n const targets = [value.primary, ...value.fallback.targets].filter(\n (target): target is WorkspaceTranscriptionTarget => target !== null,\n );\n if (new Set(targets.map(targetKey)).size !== targets.length) return false;\n return true;\n}\n\nfunction targetKey(target: WorkspaceTranscriptionTarget): string {\n return [\n target.provider.trim(),\n target.model?.trim() ?? \"\",\n target.credentialMode,\n target.credentialConnectionId ?? \"\",\n target.region?.trim() ?? \"\",\n ].join(\"\\u0000\");\n}\n\nfunction isTarget(value: unknown): value is WorkspaceTranscriptionTarget {\n if (!isRecord(value)) return false;\n if (\n !hasOnlyKeys(value, [\"provider\", \"model\", \"credentialMode\", \"credentialConnectionId\", \"region\"])\n ) {\n return false;\n }\n if (!isBoundedString(value.provider, 128)) return false;\n if (!(value.model === null || isBoundedString(value.model, 256))) return false;\n if (value.credentialMode !== \"managed\" && value.credentialMode !== \"byok\") return false;\n if (value.provider.trim() === \"azure-speech\" && value.credentialMode !== \"byok\") return false;\n if (!(value.credentialConnectionId === null || isUuid(value.credentialConnectionId))) {\n return false;\n }\n if (!(value.region === null || isBoundedString(value.region, 128))) return false;\n if (value.credentialMode === \"byok\" && value.credentialConnectionId === null) return false;\n if (value.credentialMode === \"managed\" && value.credentialConnectionId !== null) return false;\n return true;\n}\n\nfunction normalizeTarget(target: WorkspaceTranscriptionTarget): WorkspaceTranscriptionTarget {\n return {\n provider: target.provider.trim(),\n model: target.model?.trim() ?? null,\n credentialMode: target.credentialMode,\n credentialConnectionId: target.credentialConnectionId,\n region: target.region?.trim() ?? null,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {\n const accepted = new Set(keys);\n return Object.keys(value).every((key) => accepted.has(key));\n}\n\nfunction isBoundedString(value: unknown, maximum: number): value is string {\n return typeof value === \"string\" && value.trim().length > 0 && value.length <= maximum;\n}\n\nfunction isUuid(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)\n );\n}\n\nfunction isBoundedInteger(value: unknown, maximum: number): value is number {\n return Number.isInteger(value) && (value as number) >= 0 && (value as number) <= maximum;\n}\n\nfunction isNullableBoundedNumber(value: unknown, maximum: number): boolean {\n return (\n value === null ||\n (typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= maximum)\n );\n}\n"],"mappings":"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","names":[]}
@@ -91,7 +91,7 @@ function parseCodexRealtimeV3Event(payload) {
91
91
  }
92
92
  };
93
93
  }
94
- if (type === "turn.done") {
94
+ if (type === "turn.done" || type === "transcript.segment") {
95
95
  const turn = record(event.turn);
96
96
  const turnIdField = boundedStringField(turn, "id", CODEX_REALTIME_V3_MAX_IDENTIFIER_BYTES);
97
97
  const transcriptField = boundedStringField(
@@ -395,6 +395,10 @@ function createCodexRealtimeV3Bridge(options) {
395
395
  clientAckThroughSequence = Math.max(clientAckThroughSequence ?? 0, entry.sequence);
396
396
  }
397
397
  }
398
+ if (options.events.readyState !== "open") {
399
+ publish();
400
+ continue;
401
+ }
398
402
  for (let index = 0; index < result.outbound.length; ) {
399
403
  const entry = result.outbound[index];
400
404
  if (sentSequences.has(entry.sequence)) {
@@ -539,7 +543,7 @@ function createCodexRealtimeV3Bridge(options) {
539
543
  }
540
544
  } else if (event.type === "output_audio.delta") {
541
545
  speaking = true;
542
- } else if (event.type === "turn.done") {
546
+ } else if (event.type === "turn.done" || event.type === "transcript.segment") {
543
547
  speaking = false;
544
548
  if (event.transcript.length > 0 && !finalizedTurnIds.has(event.turnId)) {
545
549
  const coveredByDelegationItemId = event.role === "user" && pendingDelegationUserTranscript !== null && normalizedTranscript(event.transcript) === normalizedTranscript(pendingDelegationUserTranscript.text) ? pendingDelegationUserTranscript.delegationItemId : null;
@@ -610,6 +614,7 @@ function finalTranscript(randomUUID, event, coveredByDelegationItemId, modelCont
610
614
  text: event.transcript,
611
615
  payload: {
612
616
  turnId: event.turnId,
617
+ ...event.type === "transcript.segment" ? { transcriptSource: "provider_fragments", grouping: "application_segment" } : {},
613
618
  ...coveredByDelegationItemId ? { coveredByDelegationItemId } : {}
614
619
  },
615
620
  ...modelContext ? { modelContext } : {}
@@ -872,7 +877,7 @@ async function startCodexRealtimeWebrtc(options) {
872
877
  { signal: options.signal }
873
878
  );
874
879
  throwIfAborted(options.signal);
875
- if (answer.version !== "v3" || answer.model !== "gpt-live-1-boulder-alpha" || !answer.sdp) {
880
+ if (answer.version !== "v3" || answer.model !== (options.expectedModel ?? "gpt-live-1-boulder-alpha") || !answer.sdp) {
876
881
  throw new Error("Opengeni returned an incompatible Codex realtime answer");
877
882
  }
878
883
  await peerConnection.setRemoteDescription({
@@ -1093,7 +1098,23 @@ var SESSION_REALTIME_INBOUND_KINDS = /* @__PURE__ */ new Set([
1093
1098
  "error"
1094
1099
  ]);
1095
1100
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
1101
+ var REALTIME_REFUSAL_CODES = /* @__PURE__ */ new Set([
1102
+ "insufficient_credits",
1103
+ "allowance_exhausted",
1104
+ "monthly_model_cost_limit",
1105
+ "realtime_voice_unavailable"
1106
+ ]);
1107
+ function codexRealtimeRefusal(error) {
1108
+ if (!(error instanceof OpenGeniApiError) || !error.code) return null;
1109
+ if (!REALTIME_REFUSAL_CODES.has(error.code)) return null;
1110
+ const message = apiErrorMessage(error);
1111
+ return {
1112
+ code: error.code,
1113
+ message: message ?? (error.code === "insufficient_credits" ? "Live voice needs Opengeni credits. Add credits to continue." : "Live voice is unavailable right now.")
1114
+ };
1115
+ }
1096
1116
  function sessionRealtimeOwnerStorageNamespace(model) {
1117
+ if (model === "opengeni-azure/gpt-live-1") return "azure-live-owner";
1097
1118
  if (model === "gpt-live-1-boulder-alpha") return "codex-realtime-owner";
1098
1119
  if (model === "supergrok/grok-voice-think-fast-2.0") return "xai-realtime-owner";
1099
1120
  return "gateway-realtime-owner";
@@ -1151,7 +1172,8 @@ function createCodexRealtimeController(options) {
1151
1172
  connectionGeneration: 0,
1152
1173
  reconnectAttempt: 0,
1153
1174
  diagnostic: null,
1154
- error: null
1175
+ error: null,
1176
+ refusal: null
1155
1177
  };
1156
1178
  let active = null;
1157
1179
  let pendingAbort = null;
@@ -1168,6 +1190,7 @@ function createCodexRealtimeController(options) {
1168
1190
  let generation = 0;
1169
1191
  let reconnectAttempt = 0;
1170
1192
  let recoveryTerminal = false;
1193
+ let connectedInMode = false;
1171
1194
  let mutationTail = Promise.resolve();
1172
1195
  let connectionTask = null;
1173
1196
  const acceptedDelegationItemIds = new Set(
@@ -1420,6 +1443,10 @@ function createCodexRealtimeController(options) {
1420
1443
  if (closed || stopping || isAbortError(error)) return;
1421
1444
  const message = safeError2(error);
1422
1445
  if (error instanceof CodexRealtimeMicrophoneError) {
1446
+ if (error.code !== "track_ended" && !connectedInMode) {
1447
+ await endAfterFailure(microphoneFailureMessage(error), null);
1448
+ return;
1449
+ }
1423
1450
  if (error.code === "track_ended" && state.mode?.state === "active") {
1424
1451
  reconnectAttempt += 1;
1425
1452
  publish({
@@ -1439,6 +1466,11 @@ function createCodexRealtimeController(options) {
1439
1466
  transitionEnded("Realtime owner no longer exists");
1440
1467
  return;
1441
1468
  }
1469
+ const refusal = codexRealtimeRefusal(error);
1470
+ if (refusal) {
1471
+ await endForRefusal(refusal);
1472
+ return;
1473
+ }
1442
1474
  if (error instanceof OpenGeniApiError && error.status === 409 && error.retryable && owner) {
1443
1475
  try {
1444
1476
  const reconciled = await begin(owner, true, false);
@@ -1456,6 +1488,9 @@ function createCodexRealtimeController(options) {
1456
1488
  return;
1457
1489
  }
1458
1490
  }
1491
+ } else if (error instanceof OpenGeniApiError && !error.retryable && !connectedInMode) {
1492
+ await endAfterFailure(message, null);
1493
+ return;
1459
1494
  } else if (error instanceof OpenGeniApiError && !error.retryable) {
1460
1495
  stopTimers();
1461
1496
  if (!active) releaseMicrophone();
@@ -1574,6 +1609,13 @@ function createCodexRealtimeController(options) {
1574
1609
  });
1575
1610
  let connected;
1576
1611
  try {
1612
+ if (active?.transport.drain) {
1613
+ await active.transport.drain();
1614
+ await active.bridge.flush();
1615
+ }
1616
+ if (closed || stopping || abort.signal.aborted || pendingGeneration !== targetGeneration) {
1617
+ throw abort.signal.reason ?? new DOMException("Aborted", "AbortError");
1618
+ }
1577
1619
  const media = await ensureMicrophone(replaceMicrophone, abort.signal);
1578
1620
  const operationId = randomUUID();
1579
1621
  const commonTransportInput = {
@@ -1689,6 +1731,7 @@ function createCodexRealtimeController(options) {
1689
1731
  onFatal: (fatal) => onBridgeFatal(targetGeneration, bridge, fatal)
1690
1732
  });
1691
1733
  active = { generation: targetGeneration, transport: connected, bridge };
1734
+ connectedInMode = true;
1692
1735
  connected.setOutputMuted(state.outputMuted);
1693
1736
  recoveryTerminal = false;
1694
1737
  pendingAbort = null;
@@ -1759,6 +1802,7 @@ function createCodexRealtimeController(options) {
1759
1802
  return response.mode;
1760
1803
  };
1761
1804
  const heartbeat = async () => {
1805
+ let stopInstruction = null;
1762
1806
  await exclusive(async () => {
1763
1807
  const current = state.mode;
1764
1808
  if (!owner || !current || current.state !== "active") {
@@ -1779,6 +1823,30 @@ function createCodexRealtimeController(options) {
1779
1823
  return;
1780
1824
  }
1781
1825
  publish({ mode: result.mode, realtimeId: result.mode.id, error: null });
1826
+ stopInstruction = result.stop ?? null;
1827
+ });
1828
+ const instruction = stopInstruction;
1829
+ stopInstruction = null;
1830
+ if (instruction && !stopping && !closed) await endForRefusal(instruction);
1831
+ };
1832
+ const endForRefusal = async (refusal) => await endAfterFailure(refusal.message, refusal);
1833
+ const endAfterFailure = async (message, refusal) => {
1834
+ try {
1835
+ await controller.stop();
1836
+ } catch {
1837
+ stopping = false;
1838
+ closeBrowserResources();
1839
+ clearOwner();
1840
+ }
1841
+ recoveryTerminal = false;
1842
+ publish({
1843
+ status: "error",
1844
+ realtimeId: null,
1845
+ mode: null,
1846
+ bridge: null,
1847
+ diagnostic: diagnostic("terminal_stop", message, false),
1848
+ error: message,
1849
+ refusal
1782
1850
  });
1783
1851
  };
1784
1852
  const flush = async () => {
@@ -1828,6 +1896,7 @@ function createCodexRealtimeController(options) {
1828
1896
  closed = false;
1829
1897
  stopping = false;
1830
1898
  recoveryTerminal = false;
1899
+ connectedInMode = false;
1831
1900
  const record2 = {
1832
1901
  version: OWNER_RECORD_VERSION,
1833
1902
  workspaceId: options.workspaceId,
@@ -1838,11 +1907,12 @@ function createCodexRealtimeController(options) {
1838
1907
  };
1839
1908
  owner = record2;
1840
1909
  storage?.setItem(storageKey, JSON.stringify(record2));
1841
- publish({ status: "starting", error: null, diagnostic: null });
1910
+ publish({ status: "starting", error: null, diagnostic: null, refusal: null });
1842
1911
  try {
1843
1912
  await begin(record2, false);
1844
1913
  } catch (error) {
1845
1914
  closeBrowserResources(false);
1915
+ const refusal = codexRealtimeRefusal(error);
1846
1916
  const failedMode = state.mode;
1847
1917
  if (failedMode?.state === "active") {
1848
1918
  await handleConnectionFailure(error, "reconnect", false);
@@ -1852,9 +1922,11 @@ function createCodexRealtimeController(options) {
1852
1922
  status: "error",
1853
1923
  realtimeId: null,
1854
1924
  mode: null,
1855
- error: safeError2(error)
1925
+ error: refusal?.message ?? safeError2(error),
1926
+ ...refusal ? { refusal, diagnostic: diagnostic("terminal_stop", refusal.message, false) } : {}
1856
1927
  });
1857
1928
  }
1929
+ if (refusal) return;
1858
1930
  throw error;
1859
1931
  }
1860
1932
  },
@@ -1863,6 +1935,7 @@ function createCodexRealtimeController(options) {
1863
1935
  if (recoveryTerminal && state.mode?.state === "active") return;
1864
1936
  const record3 = readOwnerRecord(storage, storageKey, options);
1865
1937
  if (!record3) {
1938
+ if (state.status === "error" && !state.mode) return;
1866
1939
  transitionEnded();
1867
1940
  return;
1868
1941
  }
@@ -1883,7 +1956,7 @@ function createCodexRealtimeController(options) {
1883
1956
  if (lifecycle.state === "ended") {
1884
1957
  const record3 = readOwnerRecord(storage, storageKey, options);
1885
1958
  if (record3 && record3.operationId !== lifecycle.operationId) {
1886
- if (connectionTask || state.status === "active") return;
1959
+ if (connectionTask || state.status === "active" || state.status === "starting") return;
1887
1960
  closed = false;
1888
1961
  stopping = false;
1889
1962
  owner = record3;
@@ -1896,6 +1969,7 @@ function createCodexRealtimeController(options) {
1896
1969
  await connectionTask;
1897
1970
  return;
1898
1971
  }
1972
+ if (state.status === "error" && !state.mode) return;
1899
1973
  if (state.realtimeId === null || lifecycle.realtimeId === state.realtimeId) {
1900
1974
  transitionEnded(`Realtime ended: ${lifecycle.reason}`);
1901
1975
  }
@@ -1979,6 +2053,7 @@ function createCodexRealtimeController(options) {
1979
2053
  connectionTask = null;
1980
2054
  void retiredTask?.catch(() => void 0);
1981
2055
  try {
2056
+ await active?.transport.drain?.();
1982
2057
  await active?.bridge.sealAndFlush();
1983
2058
  closeBrowserResources();
1984
2059
  let current = state.mode;
@@ -2248,8 +2323,33 @@ function stringValue2(value) {
2248
2323
  function isAbortError(error) {
2249
2324
  return error instanceof Error && error.name === "AbortError";
2250
2325
  }
2326
+ function microphoneFailureMessage(error) {
2327
+ switch (error.code) {
2328
+ case "permission_denied":
2329
+ return "Microphone access is blocked. Allow it in site settings, then try again.";
2330
+ case "device_not_found":
2331
+ return "No microphone was found. Connect one, then try again.";
2332
+ case "device_unavailable":
2333
+ return "Your microphone is busy or unavailable. Close other apps using it, then try again.";
2334
+ default:
2335
+ return /timed out/i.test(error.message) ? "Microphone access wasn't granted in time. Allow it when your browser asks, then try again." : "The microphone could not start. Try again.";
2336
+ }
2337
+ }
2338
+ function apiErrorMessage(error) {
2339
+ try {
2340
+ const body = JSON.parse(error.body);
2341
+ const nested = body.error && typeof body.error === "object" ? body.error : body;
2342
+ return typeof nested.message === "string" && nested.message ? nested.message : null;
2343
+ } catch {
2344
+ return null;
2345
+ }
2346
+ }
2251
2347
  function safeError2(error) {
2252
- return error instanceof Error ? error.message : "Codex realtime browser controller failed";
2348
+ if (error instanceof OpenGeniApiError) {
2349
+ const message = apiErrorMessage(error);
2350
+ if (message) return /[.!?]$/.test(message) ? message : `${message}.`;
2351
+ }
2352
+ return error instanceof Error ? error.message : "Live voice failed in this browser.";
2253
2353
  }
2254
2354
 
2255
2355
  export {
@@ -2273,9 +2373,10 @@ export {
2273
2373
  createCodexRealtimeV3Bridge,
2274
2374
  projectSessionRealtimeLifecycle,
2275
2375
  CODEX_REALTIME_NEGOTIATION_TIMEOUT_MS,
2376
+ codexRealtimeRefusal,
2276
2377
  sessionRealtimeOwnerStorageNamespace,
2277
2378
  sessionRealtimeOwnerStorageKey,
2278
2379
  hasStoredSessionRealtimeOwnerProof,
2279
2380
  createCodexRealtimeController
2280
2381
  };
2281
- //# sourceMappingURL=chunk-2UXZUCEZ.js.map
2382
+ //# sourceMappingURL=chunk-JOI7XCG6.js.map