@oh-my-pi/pi-coding-agent 17.1.0 → 17.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 (196) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/cli.js +6325 -4420
  3. package/dist/types/cli/bench-cli.d.ts +1 -0
  4. package/dist/types/config/model-resolver.d.ts +6 -3
  5. package/dist/types/config/settings-schema.d.ts +62 -0
  6. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  7. package/dist/types/launch/broker-list-order.test.d.ts +1 -0
  8. package/dist/types/live/attestation.d.ts +2 -0
  9. package/dist/types/live/controller.d.ts +10 -2
  10. package/dist/types/live/protocol.d.ts +1 -1
  11. package/dist/types/live/transport.d.ts +6 -19
  12. package/dist/types/live/visualizer.d.ts +8 -11
  13. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  14. package/dist/types/modes/components/custom-message.d.ts +1 -1
  15. package/dist/types/modes/components/message-frame.d.ts +8 -4
  16. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  17. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  18. package/dist/types/modes/interactive-mode.d.ts +3 -6
  19. package/dist/types/modes/types.d.ts +9 -5
  20. package/dist/types/modes/utils/ui-helpers.d.ts +1 -6
  21. package/dist/types/session/agent-session-types.d.ts +8 -1
  22. package/dist/types/session/agent-session.d.ts +20 -1
  23. package/dist/types/session/auth-storage.d.ts +1 -1
  24. package/dist/types/session/eval-runner.d.ts +2 -0
  25. package/dist/types/session/messages.d.ts +2 -0
  26. package/dist/types/session/session-tools.d.ts +15 -0
  27. package/dist/types/session/streaming-output.d.ts +8 -0
  28. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  29. package/dist/types/stt/index.d.ts +0 -2
  30. package/dist/types/stt/stt-controller.d.ts +7 -0
  31. package/dist/types/task/executor.d.ts +3 -1
  32. package/dist/types/task/structured-subagent.d.ts +3 -0
  33. package/dist/types/task/types.d.ts +11 -11
  34. package/dist/types/thinking.d.ts +13 -0
  35. package/dist/types/tiny/title-client.d.ts +10 -0
  36. package/dist/types/tools/builtin-names.d.ts +1 -1
  37. package/dist/types/tools/computer/protocol.d.ts +43 -0
  38. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  39. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  40. package/dist/types/tools/computer/worker.d.ts +15 -0
  41. package/dist/types/tools/computer-renderer.d.ts +22 -0
  42. package/dist/types/tools/computer.d.ts +71 -0
  43. package/dist/types/tools/context.d.ts +2 -0
  44. package/dist/types/tools/default-renderer.d.ts +21 -0
  45. package/dist/types/tools/eval-format/index.d.ts +7 -0
  46. package/dist/types/tools/eval-format/javascript.d.ts +2 -0
  47. package/dist/types/tools/eval-format/julia.d.ts +2 -0
  48. package/dist/types/tools/eval-format/python.d.ts +2 -0
  49. package/dist/types/tools/eval-format/ruby.d.ts +2 -0
  50. package/dist/types/tools/index.d.ts +2 -0
  51. package/dist/types/tools/resolve.d.ts +7 -0
  52. package/dist/types/tools/todo.d.ts +4 -1
  53. package/dist/types/tts/streaming-player.d.ts +10 -43
  54. package/dist/types/utils/tools-manager.d.ts +1 -2
  55. package/dist/types/web/search/providers/base.d.ts +16 -0
  56. package/dist/types/web/search/providers/brave.d.ts +2 -0
  57. package/dist/types/web/search/providers/firecrawl.d.ts +2 -0
  58. package/dist/types/web/search/providers/gemini.d.ts +3 -0
  59. package/dist/types/web/search/providers/jina.d.ts +2 -0
  60. package/dist/types/web/search/providers/kagi.d.ts +2 -0
  61. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  62. package/dist/types/web/search/providers/parallel.d.ts +2 -0
  63. package/dist/types/web/search/providers/perplexity.d.ts +3 -0
  64. package/dist/types/web/search/providers/searxng.d.ts +10 -0
  65. package/dist/types/web/search/providers/tavily.d.ts +8 -0
  66. package/dist/types/web/search/query.d.ts +190 -0
  67. package/package.json +12 -12
  68. package/src/cli/args.ts +1 -0
  69. package/src/cli/bench-cli.ts +12 -1
  70. package/src/cli/setup-cli.ts +2 -14
  71. package/src/cli/web-search-cli.ts +7 -0
  72. package/src/cli.ts +8 -0
  73. package/src/config/model-registry.ts +6 -0
  74. package/src/config/model-resolver.ts +17 -6
  75. package/src/config/settings-schema.ts +66 -0
  76. package/src/debug/raw-sse-buffer.ts +157 -30
  77. package/src/eval/executor-base.ts +1 -0
  78. package/src/eval/js/executor.ts +2 -0
  79. package/src/exec/bash-executor.ts +1 -0
  80. package/src/export/share.ts +4 -3
  81. package/src/extensibility/extensions/wrapper.ts +68 -12
  82. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  83. package/src/launch/broker-list-order.test.ts +89 -0
  84. package/src/launch/broker.ts +49 -8
  85. package/src/live/attestation.ts +91 -0
  86. package/src/live/controller.ts +76 -23
  87. package/src/live/protocol.test.ts +3 -3
  88. package/src/live/protocol.ts +1 -1
  89. package/src/live/transport.ts +72 -140
  90. package/src/live/visualizer.ts +114 -134
  91. package/src/modes/components/assistant-message.ts +7 -2
  92. package/src/modes/components/custom-message.ts +4 -1
  93. package/src/modes/components/message-frame.ts +14 -8
  94. package/src/modes/components/session-account-selector.ts +62 -0
  95. package/src/modes/components/tool-execution.ts +17 -110
  96. package/src/modes/controllers/input-controller.ts +55 -47
  97. package/src/modes/controllers/live-command-controller.ts +82 -5
  98. package/src/modes/controllers/selector-controller.ts +62 -0
  99. package/src/modes/interactive-mode.ts +79 -5
  100. package/src/modes/types.ts +10 -4
  101. package/src/modes/utils/ui-helpers.ts +7 -8
  102. package/src/prompts/system/computer-safety.md +14 -0
  103. package/src/prompts/system/resolve-device-reminder.md +1 -1
  104. package/src/prompts/system/workflow-notice.md +1 -1
  105. package/src/prompts/tools/ast-edit.md +1 -0
  106. package/src/prompts/tools/bash.md +1 -0
  107. package/src/prompts/tools/computer.md +26 -0
  108. package/src/prompts/tools/eval.md +2 -2
  109. package/src/prompts/tools/task.md +5 -2
  110. package/src/prompts/tools/web-search.md +2 -0
  111. package/src/sdk.ts +13 -4
  112. package/src/session/agent-session-types.ts +9 -0
  113. package/src/session/agent-session.ts +70 -0
  114. package/src/session/auth-storage.ts +1 -0
  115. package/src/session/eval-runner.ts +5 -0
  116. package/src/session/messages.ts +3 -0
  117. package/src/session/queued-messages.ts +7 -1
  118. package/src/session/session-tools.ts +37 -0
  119. package/src/session/streaming-output.ts +52 -5
  120. package/src/slash-commands/builtin-registry.ts +165 -9
  121. package/src/slash-commands/helpers/session-pin.ts +44 -0
  122. package/src/stt/downloader.ts +0 -2
  123. package/src/stt/index.ts +0 -2
  124. package/src/stt/stt-controller.ts +57 -146
  125. package/src/system-prompt.ts +4 -0
  126. package/src/task/executor.ts +17 -9
  127. package/src/task/index.ts +14 -34
  128. package/src/task/structured-subagent.ts +4 -0
  129. package/src/task/types.ts +15 -13
  130. package/src/thinking.ts +27 -0
  131. package/src/tiny/title-client.ts +22 -0
  132. package/src/tools/ast-edit.ts +4 -1
  133. package/src/tools/bash-interactive.ts +90 -86
  134. package/src/tools/bash.ts +13 -0
  135. package/src/tools/browser/render.ts +2 -1
  136. package/src/tools/builtin-names.ts +1 -0
  137. package/src/tools/computer/protocol.ts +28 -0
  138. package/src/tools/computer/supervisor.ts +258 -0
  139. package/src/tools/computer/worker-entry.ts +25 -0
  140. package/src/tools/computer/worker.ts +135 -0
  141. package/src/tools/computer-renderer.ts +108 -0
  142. package/src/tools/computer.ts +433 -0
  143. package/src/tools/context.ts +2 -0
  144. package/src/tools/default-renderer.ts +139 -0
  145. package/src/tools/essential-tools.ts +1 -0
  146. package/src/tools/eval-format/index.ts +24 -0
  147. package/src/tools/eval-format/javascript.ts +952 -0
  148. package/src/tools/eval-format/julia.ts +446 -0
  149. package/src/tools/eval-format/python.ts +544 -0
  150. package/src/tools/eval-format/ruby.ts +380 -0
  151. package/src/tools/eval-render.ts +12 -6
  152. package/src/tools/index.ts +5 -0
  153. package/src/tools/renderers.ts +2 -0
  154. package/src/tools/resolve.ts +10 -1
  155. package/src/tools/todo.ts +58 -6
  156. package/src/tools/xdev.ts +54 -26
  157. package/src/tts/streaming-player.ts +81 -340
  158. package/src/utils/clipboard.ts +1 -30
  159. package/src/utils/mac-file-urls.applescript +37 -0
  160. package/src/utils/tool-choice.ts +14 -0
  161. package/src/utils/tools-manager.ts +1 -19
  162. package/src/web/search/index.ts +28 -5
  163. package/src/web/search/providers/anthropic.ts +62 -4
  164. package/src/web/search/providers/base.ts +16 -0
  165. package/src/web/search/providers/brave.ts +30 -3
  166. package/src/web/search/providers/codex.ts +14 -1
  167. package/src/web/search/providers/duckduckgo.ts +23 -1
  168. package/src/web/search/providers/ecosia.ts +5 -1
  169. package/src/web/search/providers/exa.ts +28 -1
  170. package/src/web/search/providers/firecrawl.ts +37 -3
  171. package/src/web/search/providers/gemini.ts +12 -2
  172. package/src/web/search/providers/google.ts +2 -1
  173. package/src/web/search/providers/jina.ts +31 -8
  174. package/src/web/search/providers/kagi.ts +10 -1
  175. package/src/web/search/providers/kimi.ts +16 -1
  176. package/src/web/search/providers/mojeek.ts +14 -1
  177. package/src/web/search/providers/parallel.ts +48 -2
  178. package/src/web/search/providers/perplexity.ts +94 -6
  179. package/src/web/search/providers/searxng.ts +145 -9
  180. package/src/web/search/providers/startpage.ts +8 -2
  181. package/src/web/search/providers/synthetic.ts +7 -1
  182. package/src/web/search/providers/tavily.ts +52 -3
  183. package/src/web/search/providers/tinyfish.ts +6 -1
  184. package/src/web/search/providers/xai.ts +49 -2
  185. package/src/web/search/providers/zai.ts +19 -1
  186. package/src/web/search/query.ts +850 -0
  187. package/dist/types/stt/recorder.d.ts +0 -30
  188. package/dist/types/stt/transcriber.d.ts +0 -14
  189. package/dist/types/stt/wav.d.ts +0 -29
  190. package/dist/types/tts/player.d.ts +0 -32
  191. package/src/live/audio-worklet.txt +0 -59
  192. package/src/live/browser-runtime.txt +0 -221
  193. package/src/stt/recorder.ts +0 -551
  194. package/src/stt/transcriber.ts +0 -60
  195. package/src/stt/wav.ts +0 -173
  196. package/src/tts/player.ts +0 -137
@@ -1,30 +0,0 @@
1
- export interface RecordingHandle {
2
- stop(): Promise<void>;
3
- }
4
- /**
5
- * Returns available recording tools in priority order.
6
- */
7
- export declare function detectRecordingTools(): string[];
8
- export interface ResolvedRecorder {
9
- tool: "sox" | "ffmpeg" | "arecord" | "powershell";
10
- bin: string;
11
- }
12
- export declare function detectRecorder(): ResolvedRecorder | null;
13
- /**
14
- * Ensure a recorder is available, downloading the static ffmpeg binary when
15
- * nothing is already present. Returns the resolved recorder.
16
- */
17
- export declare function ensureRecorder(onProgress?: (p: {
18
- stage: string;
19
- percent?: number;
20
- }) => void, signal?: AbortSignal): Promise<ResolvedRecorder>;
21
- export declare function startRecording(outputPath: string): Promise<RecordingHandle>;
22
- /**
23
- * Verify a recorded audio file is usable.
24
- * Returns the file size in bytes, or throws.
25
- */
26
- export declare function verifyRecordingFile(filePath: string): Promise<number>;
27
- export interface StreamingRecordingHandle {
28
- stop(): Promise<void>;
29
- }
30
- export declare function startStreamingRecording(onAudio: (samples: Float32Array) => void): Promise<StreamingRecordingHandle | null>;
@@ -1,14 +0,0 @@
1
- export interface TranscribeOptions {
2
- modelName?: string;
3
- language?: string;
4
- signal?: AbortSignal;
5
- }
6
- /**
7
- * Transcribe a WAV file using the local ONNX Whisper worker.
8
- *
9
- * Decodes the WAV to a 16 kHz mono Float32Array in-process (no Python, no
10
- * ffmpeg) and routes it to the warm speech worker, which keeps the model loaded
11
- * across calls. Honors `options.signal` (abort) and applies an internal timeout
12
- * with the same semantics as the previous Python path.
13
- */
14
- export declare function transcribe(audioPath: string, options?: TranscribeOptions): Promise<string>;
@@ -1,29 +0,0 @@
1
- /**
2
- * Minimal WAV (RIFF/PCM) decoder producing the Float32Array @ 16 kHz mono that
3
- * transformers.js `automatic-speech-recognition` expects. Ports the decode/
4
- * mono-mix/resample logic from the retired Python `transcribe.py` (which read
5
- * via the stdlib `wave` module) so STT no longer shells out to Python.
6
- *
7
- * Supported sample formats: PCM uint8 (8-bit), int16 (16-bit), int32 (32-bit),
8
- * and IEEE float32 (format tag 3). Any number of channels is mixed down to mono.
9
- */
10
- /** transformers.js Whisper feature extractor operates at 16 kHz. */
11
- export declare const TARGET_SAMPLE_RATE = 16000;
12
- /**
13
- * Resample via linear interpolation, mirroring the Python `np.interp` over
14
- * `linspace(0, n-1, targetLen)` against `arange(n)`.
15
- */
16
- export declare function resampleLinear(input: Float32Array, fromRate: number, toRate: number): Float32Array;
17
- /**
18
- * Decode a WAV byte buffer into a 16 kHz mono Float32Array suitable for the
19
- * transformers.js Whisper pipeline.
20
- */
21
- export declare function decodeWavToMono16k(buffer: ArrayBuffer): Float32Array;
22
- /**
23
- * Decode interleaved little-endian signed 16-bit PCM bytes into normalized
24
- * [-1, 1] mono float samples. The live recorder streams raw s16le frames from
25
- * sox/ffmpeg/arecord stdout (no RIFF container), so this is the hot-path
26
- * counterpart to {@link decodeWavToMono16k}. `bytes` MUST be 2-byte aligned;
27
- * callers buffer any trailing odd byte across chunk boundaries.
28
- */
29
- export declare function decodePcmS16LE(bytes: Uint8Array): Float32Array;
@@ -1,32 +0,0 @@
1
- export interface PlayerCommand {
2
- cmd: string;
3
- args: string[];
4
- }
5
- /** Injection seam for {@link playerCommandsFor} — defaults to real PATH/tools lookups. */
6
- export interface PlayerLookup {
7
- which?: (bin: string) => string | null;
8
- ffmpeg?: () => string | null;
9
- }
10
- /**
11
- * Build the ordered list of playback commands to try for `filePath` on the
12
- * given platform. Pure + injectable so the selection logic is testable without
13
- * spawning anything.
14
- *
15
- * - darwin: `afplay` (always present on macOS).
16
- * - win32: PowerShell `Media.SoundPlayer.PlaySync()` (no extra deps).
17
- * - linux/other POSIX: `paplay` (PulseAudio) → `aplay` (ALSA) → the bundled
18
- * static `ffmpeg` (`-f pulse` then `-f alsa`). Empty result means nothing is
19
- * available and the caller should surface an install hint.
20
- */
21
- export declare function playerCommandsFor(platform: NodeJS.Platform, filePath: string, lookup?: PlayerLookup): PlayerCommand[];
22
- export interface PlayAudioOptions {
23
- signal?: AbortSignal;
24
- }
25
- /**
26
- * Play `filePath` through the speakers, trying each candidate command in order
27
- * and returning on the first clean exit. Throws an actionable Error if no
28
- * player exists or every candidate fails (with the collected stderr).
29
- */
30
- export declare function playAudioFile(filePath: string, options?: PlayAudioOptions): Promise<void>;
31
- /** Best-effort temp-file cleanup used by callers after playback. */
32
- export declare function removeTempFile(filePath: string): Promise<void>;
@@ -1,59 +0,0 @@
1
- class OmpPcmSourceProcessor extends AudioWorkletProcessor {
2
- constructor() {
3
- super();
4
- this.frames = [];
5
- this.frameOffset = 0;
6
- this.queuedSamples = 0;
7
- this.muted = false;
8
- this.maxQueuedSamples = Math.floor(sampleRate * 0.75);
9
- this.port.onmessage = event => {
10
- const message = event.data;
11
- if (message.type === "mute") {
12
- this.muted = message.muted;
13
- this.clear();
14
- return;
15
- }
16
- if (message.type !== "audio" || this.muted || !(message.samples instanceof Float32Array)) return;
17
- let samples = message.samples;
18
- if (samples.length > this.maxQueuedSamples) samples = samples.slice(samples.length - this.maxQueuedSamples);
19
- this.frames.push(samples);
20
- this.queuedSamples += samples.length;
21
- while (this.queuedSamples > this.maxQueuedSamples && this.frames.length > 1) {
22
- const stale = this.frames.shift();
23
- this.queuedSamples -= stale.length - this.frameOffset;
24
- this.frameOffset = 0;
25
- }
26
- };
27
- }
28
-
29
- clear() {
30
- this.frames.length = 0;
31
- this.frameOffset = 0;
32
- this.queuedSamples = 0;
33
- }
34
-
35
- process(_inputs, outputs) {
36
- const output = outputs[0];
37
- const channel = output && output[0];
38
- if (!channel) return true;
39
- channel.fill(0);
40
- if (this.muted) return true;
41
- let outputOffset = 0;
42
- while (outputOffset < channel.length && this.frames.length > 0) {
43
- const frame = this.frames[0];
44
- const available = frame.length - this.frameOffset;
45
- const count = Math.min(available, channel.length - outputOffset);
46
- channel.set(frame.subarray(this.frameOffset, this.frameOffset + count), outputOffset);
47
- outputOffset += count;
48
- this.frameOffset += count;
49
- this.queuedSamples -= count;
50
- if (this.frameOffset === frame.length) {
51
- this.frames.shift();
52
- this.frameOffset = 0;
53
- }
54
- }
55
- return true;
56
- }
57
- }
58
-
59
- registerProcessor("omp-pcm-source", OmpPcmSourceProcessor);
@@ -1,221 +0,0 @@
1
- (() => {
2
- const INPUT_SAMPLE_RATE = 16_000;
3
- const LEVEL_INTERVAL_MS = 50;
4
- const OPEN_TIMEOUT_MS = 20_000;
5
- let context;
6
- let peer;
7
- let channel;
8
- let sourceNode;
9
- let sourceTrack;
10
- let remoteSource;
11
- let analyser;
12
- let levelWaveform;
13
- let levelTimer;
14
- let disconnectTimer;
15
- let workletUrl;
16
- let closing = false;
17
- let muted = false;
18
- let openSettled = false;
19
- let openResolve;
20
- let openReject;
21
- let previousInputSample;
22
- let inputPhase = 0;
23
- const openPromise = new Promise((resolve, reject) => {
24
- openResolve = resolve;
25
- openReject = reject;
26
- });
27
-
28
- function reportFailure(message) {
29
- if (closing) return;
30
- if (!openSettled) {
31
- openSettled = true;
32
- openReject(new Error(message));
33
- }
34
- Promise.resolve(window.__ompLiveFailure(message)).catch(() => {});
35
- }
36
-
37
- function waitForIceGathering() {
38
- if (peer.iceGatheringState === "complete") return Promise.resolve();
39
- return new Promise((resolve, reject) => {
40
- const timeout = setTimeout(() => {
41
- peer.removeEventListener("icegatheringstatechange", onChange);
42
- reject(new Error("Timed out gathering WebRTC ICE candidates"));
43
- }, 10_000);
44
- function onChange() {
45
- if (peer.iceGatheringState !== "complete") return;
46
- clearTimeout(timeout);
47
- peer.removeEventListener("icegatheringstatechange", onChange);
48
- resolve();
49
- }
50
- peer.addEventListener("icegatheringstatechange", onChange);
51
- });
52
- }
53
-
54
- function decodePcm(base64) {
55
- const encoded = atob(base64);
56
- const bytes = new Uint8Array(encoded.length);
57
- for (let index = 0; index < encoded.length; index++) bytes[index] = encoded.charCodeAt(index);
58
- const view = new DataView(bytes.buffer);
59
- const samples = new Float32Array(bytes.length / 4);
60
- for (let index = 0; index < samples.length; index++) samples[index] = view.getFloat32(index * 4, true);
61
- return samples;
62
- }
63
-
64
- function resample(samples) {
65
- if (samples.length === 0) return new Float32Array();
66
- const step = INPUT_SAMPLE_RATE / context.sampleRate;
67
- const prefix = previousInputSample === undefined ? 0 : 1;
68
- const input = new Float32Array(samples.length + prefix);
69
- if (prefix === 1) input[0] = previousInputSample;
70
- input.set(samples, prefix);
71
- if (prefix === 0) inputPhase = 0;
72
- const available = input.length - 1;
73
- if (available <= 0) {
74
- previousInputSample = input[0];
75
- return new Float32Array();
76
- }
77
- const outputLength = Math.max(0, Math.ceil((available - inputPhase) / step));
78
- const output = new Float32Array(outputLength);
79
- let written = 0;
80
- while (inputPhase < available) {
81
- const index = Math.floor(inputPhase);
82
- const fraction = inputPhase - index;
83
- output[written++] = input[index] + (input[index + 1] - input[index]) * fraction;
84
- inputPhase += step;
85
- }
86
- inputPhase -= available;
87
- previousInputSample = input[input.length - 1];
88
- return written === output.length ? output : output.slice(0, written);
89
- }
90
-
91
- function reportLevel() {
92
- if (!analyser || !levelWaveform) return;
93
- analyser.getFloatTimeDomainData(levelWaveform);
94
- let sum = 0;
95
- for (const sample of levelWaveform) sum += sample * sample;
96
- const level = Math.min(1, Math.max(0, Math.sqrt(sum / levelWaveform.length)));
97
- Promise.resolve(window.__ompLiveOutputLevel(level)).catch(() => {});
98
- }
99
-
100
- function attachRemoteTrack(event) {
101
- if (closing || event.track.kind !== "audio") return;
102
- if (remoteSource) remoteSource.disconnect();
103
- const stream = new MediaStream([event.track]);
104
- remoteSource = context.createMediaStreamSource(stream);
105
- analyser = context.createAnalyser();
106
- analyser.fftSize = 1024;
107
- levelWaveform = new Float32Array(analyser.fftSize);
108
- remoteSource.connect(analyser);
109
- analyser.connect(context.destination);
110
- clearInterval(levelTimer);
111
- levelTimer = setInterval(reportLevel, LEVEL_INTERVAL_MS);
112
- }
113
-
114
- async function start(workletSource) {
115
- if (peer) throw new Error("Live browser runtime already started");
116
- context = new AudioContext({ latencyHint: "interactive" });
117
- await context.resume();
118
- workletUrl = URL.createObjectURL(new Blob([workletSource], { type: "text/javascript" }));
119
- await context.audioWorklet.addModule(workletUrl);
120
- sourceNode = new AudioWorkletNode(context, "omp-pcm-source", {
121
- numberOfInputs: 0,
122
- numberOfOutputs: 1,
123
- outputChannelCount: [1],
124
- });
125
- const destination = context.createMediaStreamDestination();
126
- sourceNode.connect(destination);
127
- sourceTrack = destination.stream.getAudioTracks()[0];
128
- sourceTrack.enabled = !muted;
129
-
130
- peer = new RTCPeerConnection();
131
- peer.addTrack(sourceTrack, destination.stream);
132
- peer.ontrack = attachRemoteTrack;
133
- peer.onconnectionstatechange = () => {
134
- clearTimeout(disconnectTimer);
135
- if (peer.connectionState === "failed" || peer.connectionState === "closed") {
136
- reportFailure(`WebRTC peer connection ${peer.connectionState}`);
137
- } else if (peer.connectionState === "disconnected") {
138
- disconnectTimer = setTimeout(() => {
139
- if (peer && peer.connectionState === "disconnected") reportFailure("WebRTC peer connection disconnected");
140
- }, 2_000);
141
- }
142
- };
143
- channel = peer.createDataChannel("oai-events");
144
- channel.onopen = () => {
145
- if (openSettled) return;
146
- openSettled = true;
147
- openResolve();
148
- };
149
- channel.onerror = () => reportFailure("Live data channel failed");
150
- channel.onclose = () => reportFailure("Live data channel closed unexpectedly");
151
- channel.onmessage = event => {
152
- if (typeof event.data !== "string") return;
153
- Promise.resolve(window.__ompLiveServerEvent(event.data)).catch(() => {});
154
- };
155
-
156
- const offer = await peer.createOffer();
157
- await peer.setLocalDescription(offer);
158
- await waitForIceGathering();
159
- if (!peer.localDescription || !peer.localDescription.sdp) throw new Error("Browser produced an empty WebRTC offer");
160
- return peer.localDescription.sdp;
161
- }
162
-
163
- async function acceptAnswer(sdp) {
164
- if (!peer) throw new Error("Live browser runtime has not started");
165
- await peer.setRemoteDescription({ type: "answer", sdp });
166
- }
167
-
168
- async function waitForOpen() {
169
- let timeout;
170
- try {
171
- await Promise.race([
172
- openPromise,
173
- new Promise((_, reject) => {
174
- timeout = setTimeout(() => reject(new Error("Timed out waiting for the live data channel to open")), OPEN_TIMEOUT_MS);
175
- }),
176
- ]);
177
- } finally {
178
- clearTimeout(timeout);
179
- }
180
- }
181
-
182
- function send(payload) {
183
- if (!channel || channel.readyState !== "open") throw new Error("Live data channel is not open");
184
- channel.send(payload);
185
- }
186
-
187
- function pushAudio(base64) {
188
- if (!sourceNode || muted) return;
189
- const output = resample(decodePcm(base64));
190
- if (output.length === 0) return;
191
- sourceNode.port.postMessage({ type: "audio", samples: output }, [output.buffer]);
192
- }
193
-
194
- function setMuted(nextMuted) {
195
- muted = nextMuted;
196
- previousInputSample = undefined;
197
- inputPhase = 0;
198
- if (sourceTrack) sourceTrack.enabled = !muted;
199
- if (sourceNode) sourceNode.port.postMessage({ type: "mute", muted });
200
- }
201
-
202
- async function close() {
203
- if (closing) return;
204
- closing = true;
205
- clearInterval(levelTimer);
206
- clearTimeout(disconnectTimer);
207
- if (channel) channel.close();
208
- if (peer) peer.close();
209
- if (remoteSource) remoteSource.disconnect();
210
- if (analyser) analyser.disconnect();
211
- if (sourceNode) {
212
- sourceNode.port.postMessage({ type: "mute", muted: true });
213
- sourceNode.disconnect();
214
- }
215
- if (sourceTrack) sourceTrack.stop();
216
- if (workletUrl) URL.revokeObjectURL(workletUrl);
217
- if (context && context.state !== "closed") await context.close();
218
- }
219
-
220
- window.ompCodexLive = { start, acceptAnswer, waitForOpen, send, pushAudio, setMuted, close };
221
- })();