@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.
- package/README.md +25 -3
- package/dist/adapters/next.js +2 -2
- package/dist/artifacts.js +4 -4
- package/dist/azure-live-transport.d.ts +32 -0
- package/dist/browser.js +3 -3
- package/dist/chat/index.js +5 -5
- package/dist/{chunk-IB6SUE3R.js → chunk-DTIMLEPB.js} +1 -1
- package/dist/chunk-DTIMLEPB.js.map +1 -0
- package/dist/{chunk-VYCTL62C.js → chunk-GZQSWAKD.js} +1 -1
- package/dist/chunk-GZQSWAKD.js.map +1 -0
- package/dist/{chunk-2UXZUCEZ.js → chunk-JOI7XCG6.js} +110 -9
- package/dist/chunk-JOI7XCG6.js.map +1 -0
- package/dist/{chunk-CREM7YRK.js → chunk-TRUFUTDI.js} +2 -2
- package/dist/{chunk-CNSPJYF6.js → chunk-TX6ED3AP.js} +210 -6
- package/dist/chunk-TX6ED3AP.js.map +1 -0
- package/dist/{chunk-ZBICMU4Z.js → chunk-XIQIV4VL.js} +2 -2
- package/dist/{chunk-G5NZGPTB.js → chunk-XPWH3HKG.js} +2 -2
- package/dist/{chunk-YVH2PH4X.js → chunk-YWLJGEUB.js} +2 -2
- package/dist/{chunk-RSKO3CMZ.js → chunk-ZOSRUR2Q.js} +2 -2
- package/dist/codex-realtime-controller.d.ts +13 -0
- package/dist/codex-realtime-controller.js +3 -1
- package/dist/codex-realtime-v3-wire.d.ts +3 -2
- package/dist/codex-realtime.d.ts +4 -0
- package/dist/core.js +4 -4
- package/dist/document-authority.js +3 -3
- package/dist/editable-artifacts.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +55 -18
- package/dist/index.js.map +1 -1
- package/dist/realtime.d.ts +1 -1
- package/dist/realtime.js +248 -2
- package/dist/realtime.js.map +1 -1
- package/dist/session-proxy-client.d.ts +7 -3
- package/dist/session-proxy.d.ts +38 -7
- package/dist/session-proxy.js +2 -2
- package/dist/site.js +3 -3
- package/dist/transcription.d.ts +1 -1
- package/dist/types.d.ts +47 -8
- package/package.json +3 -3
- package/src/azure-live-transport.ts +286 -0
- package/src/codex-realtime-controller.ts +148 -4
- package/src/codex-realtime-v3-wire.ts +4 -3
- package/src/codex-realtime-v3.ts +11 -2
- package/src/codex-realtime.ts +9 -1
- package/src/index.ts +3 -0
- package/src/realtime.ts +15 -6
- package/src/session-proxy-client.ts +7 -3
- package/src/session-proxy.ts +321 -11
- package/src/transcription.ts +3 -0
- package/src/types.ts +49 -5
- package/dist/chunk-2UXZUCEZ.js.map +0 -1
- package/dist/chunk-CNSPJYF6.js.map +0 -1
- package/dist/chunk-IB6SUE3R.js.map +0 -1
- package/dist/chunk-VYCTL62C.js.map +0 -1
- /package/dist/{chunk-CREM7YRK.js.map → chunk-TRUFUTDI.js.map} +0 -0
- /package/dist/{chunk-ZBICMU4Z.js.map → chunk-XIQIV4VL.js.map} +0 -0
- /package/dist/{chunk-G5NZGPTB.js.map → chunk-XPWH3HKG.js.map} +0 -0
- /package/dist/{chunk-YVH2PH4X.js.map → chunk-YWLJGEUB.js.map} +0 -0
- /package/dist/{chunk-RSKO3CMZ.js.map → chunk-ZOSRUR2Q.js.map} +0 -0
|
@@ -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
|
-
|
|
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-
|
|
2382
|
+
//# sourceMappingURL=chunk-JOI7XCG6.js.map
|