@ottocode/web-sdk 0.1.314 → 0.1.316

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.
@@ -3124,6 +3124,78 @@ var useSecureInputStore = create11((set) => ({
3124
3124
  clearPendingInputs: () => set({ pendingInputs: [] })
3125
3125
  }));
3126
3126
 
3127
+ // src/hooks/tool-preview-helpers.ts
3128
+ function bestEffortUnescapeJsonString(value) {
3129
+ try {
3130
+ return JSON.parse(`"${value.replace(/\\$/, "")}"`);
3131
+ } catch {
3132
+ return value.replace(/\\n/g, `
3133
+ `).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
3134
+ }
3135
+ }
3136
+ function extractJsonStringFieldAt(text, field, startIndex, requireClosed = false) {
3137
+ const marker = `"${field}"`;
3138
+ const markerIndex = text.indexOf(marker, Math.max(0, startIndex));
3139
+ if (markerIndex === -1)
3140
+ return;
3141
+ const colonIndex = text.indexOf(":", markerIndex + marker.length);
3142
+ if (colonIndex === -1)
3143
+ return;
3144
+ const quoteIndex = text.indexOf('"', colonIndex + 1);
3145
+ if (quoteIndex === -1)
3146
+ return;
3147
+ let escaped = "";
3148
+ let escaping = false;
3149
+ let closed = false;
3150
+ let endIndex = text.length;
3151
+ for (let i = quoteIndex + 1;i < text.length; i += 1) {
3152
+ const char = text[i];
3153
+ if (escaping) {
3154
+ escaped += `\\${char}`;
3155
+ escaping = false;
3156
+ continue;
3157
+ }
3158
+ if (char === "\\") {
3159
+ escaping = true;
3160
+ continue;
3161
+ }
3162
+ if (char === '"') {
3163
+ closed = true;
3164
+ endIndex = i + 1;
3165
+ break;
3166
+ }
3167
+ escaped += char;
3168
+ }
3169
+ if (requireClosed && !closed)
3170
+ return;
3171
+ return {
3172
+ value: bestEffortUnescapeJsonString(escaped),
3173
+ endIndex,
3174
+ closed
3175
+ };
3176
+ }
3177
+ function extractStreamingMultiEditPreviewEdits(buffer) {
3178
+ const editsStart = buffer.indexOf('"edits"');
3179
+ if (editsStart === -1)
3180
+ return [];
3181
+ const edits = [];
3182
+ let cursor = editsStart;
3183
+ const maxPreviewEdits = 50;
3184
+ while (cursor < buffer.length && edits.length < maxPreviewEdits) {
3185
+ const oldString = extractJsonStringFieldAt(buffer, "oldString", cursor, true);
3186
+ if (!oldString)
3187
+ break;
3188
+ const newString = extractJsonStringFieldAt(buffer, "newString", oldString.endIndex, false);
3189
+ if (!newString)
3190
+ break;
3191
+ edits.push({ oldString: oldString.value, newString: newString.value });
3192
+ cursor = Math.max(newString.endIndex, oldString.endIndex + 1);
3193
+ if (!newString.closed)
3194
+ break;
3195
+ }
3196
+ return edits;
3197
+ }
3198
+
3127
3199
  // src/hooks/useSessionStream.ts
3128
3200
  var TOOL_PREVIEW_THROTTLE_MS = 500;
3129
3201
  var TOOL_PREVIEW_THROTTLE_MIN_CHARS = 8000;
@@ -3277,7 +3349,7 @@ ${value.slice(-STREAMING_TOOL_INPUT_TAIL_CHARS)}`;
3277
3349
  toolInputBuffersRef.current.set(key, next);
3278
3350
  return parseArgsRecord(next);
3279
3351
  };
3280
- const bestEffortUnescapeJsonString = (value) => {
3352
+ const bestEffortUnescapeJsonString2 = (value) => {
3281
3353
  try {
3282
3354
  return JSON.parse(`"${value.replace(/\\$/, "")}"`);
3283
3355
  } catch {
@@ -3318,7 +3390,7 @@ ${value.slice(-STREAMING_TOOL_INPUT_TAIL_CHARS)}`;
3318
3390
  }
3319
3391
  if (requireClosed && !closed)
3320
3392
  return;
3321
- return bestEffortUnescapeJsonString(escaped);
3393
+ return bestEffortUnescapeJsonString2(escaped);
3322
3394
  };
3323
3395
  const getBufferedToolInput = (payload) => {
3324
3396
  const key = getToolBufferKey(payload);
@@ -3355,7 +3427,7 @@ ${argContent.slice(-STREAMING_WRITE_CONTENT_PREVIEW_CHARS)}`;
3355
3427
  }
3356
3428
  const rawTail = buffer.slice(Math.max(valueStart, buffer.length - STREAMING_WRITE_CONTENT_PREVIEW_CHARS));
3357
3429
  return `… showing latest streamed content only …
3358
- ${bestEffortUnescapeJsonString(rawTail)}`;
3430
+ ${bestEffortUnescapeJsonString2(rawTail)}`;
3359
3431
  };
3360
3432
  const getStreamingPatchPreviewContent = (args, buffer) => {
3361
3433
  const argPatch = args?.patch;
@@ -3384,9 +3456,9 @@ ${argPatch.slice(-STREAMING_PATCH_PREVIEW_TAIL_CHARS)}`;
3384
3456
  }
3385
3457
  const rawHead = buffer.slice(valueStart, valueStart + STREAMING_PATCH_PREVIEW_HEAD_CHARS);
3386
3458
  const rawTail = buffer.slice(-STREAMING_PATCH_PREVIEW_TAIL_CHARS);
3387
- return `${bestEffortUnescapeJsonString(rawHead)}
3459
+ return `${bestEffortUnescapeJsonString2(rawHead)}
3388
3460
  … patch preview truncated while streaming …
3389
- ${bestEffortUnescapeJsonString(rawTail)}`;
3461
+ ${bestEffortUnescapeJsonString2(rawTail)}`;
3390
3462
  };
3391
3463
  const getResultRecord = (payload) => payload?.result && typeof payload.result === "object" && !Array.isArray(payload.result) ? payload.result : null;
3392
3464
  const getArtifactRecord = (payload) => {
@@ -3562,14 +3634,15 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3562
3634
  return lines.join(`
3563
3635
  `);
3564
3636
  };
3565
- const getMultiEditPreviewEdits = (args) => {
3637
+ const getMultiEditPreviewEdits = (args, buffer) => {
3566
3638
  const edits = Array.isArray(args?.edits) ? args.edits : [];
3567
- return edits.flatMap((edit) => {
3639
+ const parsedEdits = edits.flatMap((edit) => {
3568
3640
  if (!edit || typeof edit !== "object" || Array.isArray(edit))
3569
3641
  return [];
3570
3642
  const record = edit;
3571
3643
  return typeof record.oldString === "string" && typeof record.newString === "string" ? [{ oldString: record.oldString, newString: record.newString }] : [];
3572
3644
  });
3645
+ return parsedEdits.length > 0 ? parsedEdits : extractStreamingMultiEditPreviewEdits(buffer);
3573
3646
  };
3574
3647
  const getEditPreviewPatch = (toolName, path, args, buffer, artifact) => {
3575
3648
  if (typeof artifact?.patch === "string")
@@ -3579,7 +3652,7 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3579
3652
  const newString = getStringArg(args, buffer, "newString");
3580
3653
  return oldString !== undefined && newString !== undefined ? buildStringEditPatchPreview(path, [{ oldString, newString }]) : undefined;
3581
3654
  }
3582
- const edits = getMultiEditPreviewEdits(args);
3655
+ const edits = getMultiEditPreviewEdits(args, buffer);
3583
3656
  return buildStringEditPatchPreview(path, edits);
3584
3657
  };
3585
3658
  const handleReadToolActivity = (eventType, payload, delta) => {
@@ -8069,6 +8142,7 @@ function useEdgeHover({
8069
8142
  import { useCallback as useCallback14, useEffect as useEffect22, useRef as useRef11, useState as useState10 } from "react";
8070
8143
  var TARGET_SAMPLE_RATE = 16000;
8071
8144
  var PCM_FRAME_BYTES = 3200;
8145
+ var PROCESSOR_BUFFER_SIZE = 4096;
8072
8146
  function getAudioContextConstructor() {
8073
8147
  if (typeof window === "undefined")
8074
8148
  return null;
@@ -8213,8 +8287,7 @@ function useVoiceInput({
8213
8287
  }, []);
8214
8288
  const handleAudioProcess = useCallback14((event) => {
8215
8289
  const audioContext = audioContextRef.current;
8216
- const socket = socketRef.current;
8217
- if (!audioContext || !socket || socket.readyState !== WebSocket.OPEN || stoppingRef.current) {
8290
+ if (!audioContext || stoppingRef.current) {
8218
8291
  return;
8219
8292
  }
8220
8293
  const input = event.inputBuffer.getChannelData(0);
@@ -8246,9 +8319,53 @@ function useVoiceInput({
8246
8319
  setIsTranscribing(false);
8247
8320
  stoppingRef.current = false;
8248
8321
  try {
8249
- const status = await apiClient.getDictationStatus();
8322
+ const streamPromise = navigator.mediaDevices.getUserMedia({
8323
+ audio: {
8324
+ echoCancellation: true,
8325
+ noiseSuppression: true,
8326
+ autoGainControl: true
8327
+ }
8328
+ });
8329
+ const statusPromise = apiClient.getDictationStatus().then((status2) => ({ status: status2 }), (error2) => ({ error: error2 }));
8330
+ const stream = await streamPromise;
8331
+ if (stoppingRef.current) {
8332
+ for (const track of stream.getTracks())
8333
+ track.stop();
8334
+ return;
8335
+ }
8336
+ streamRef.current = stream;
8337
+ const AudioContextCtor = getAudioContextConstructor();
8338
+ if (!AudioContextCtor)
8339
+ throw new Error("AudioContext is unavailable");
8340
+ const audioContext = new AudioContextCtor;
8341
+ audioContextRef.current = audioContext;
8342
+ const source = audioContext.createMediaStreamSource(stream);
8343
+ const analyserNode = audioContext.createAnalyser();
8344
+ analyserNode.fftSize = 256;
8345
+ analyserNode.smoothingTimeConstant = 0.55;
8346
+ const processor = audioContext.createScriptProcessor(PROCESSOR_BUFFER_SIZE, 1, 1);
8347
+ processor.onaudioprocess = handleAudioProcess;
8348
+ source.connect(analyserNode);
8349
+ source.connect(processor);
8350
+ processor.connect(audioContext.destination);
8351
+ sourceRef.current = source;
8352
+ processorRef.current = processor;
8353
+ if (audioContext.state === "suspended") {
8354
+ await audioContext.resume();
8355
+ }
8356
+ if (stoppingRef.current)
8357
+ return;
8358
+ setAnalyser(analyserNode);
8359
+ setIsListening(true);
8360
+ const statusResult = await statusPromise;
8361
+ if ("error" in statusResult)
8362
+ throw statusResult.error;
8363
+ const { status } = statusResult;
8364
+ if (stoppingRef.current)
8365
+ return;
8250
8366
  const model = status.models.find((item) => item.id === status.defaultModel);
8251
8367
  if (!model?.installed) {
8368
+ cleanup();
8252
8369
  handleMissingModel();
8253
8370
  return;
8254
8371
  }
@@ -8256,7 +8373,10 @@ function useVoiceInput({
8256
8373
  model: status.defaultModel,
8257
8374
  language: toLanguageCode(lang)
8258
8375
  });
8376
+ if (stoppingRef.current)
8377
+ return;
8259
8378
  if (!session.modelInstalled) {
8379
+ cleanup();
8260
8380
  handleMissingModel();
8261
8381
  return;
8262
8382
  }
@@ -8269,7 +8389,6 @@ function useVoiceInput({
8269
8389
  reject(new Error("Timed out connecting to local dictation"));
8270
8390
  }, 5000);
8271
8391
  socket.onopen = () => {
8272
- window.clearTimeout(timeout);
8273
8392
  socket.send(JSON.stringify({
8274
8393
  type: "start",
8275
8394
  model: session.model,
@@ -8281,7 +8400,23 @@ function useVoiceInput({
8281
8400
  },
8282
8401
  partialResults: false
8283
8402
  }));
8284
- resolve();
8403
+ };
8404
+ socket.onmessage = (event) => {
8405
+ if (typeof event.data !== "string")
8406
+ return;
8407
+ const payload = parseServerEvent(event.data);
8408
+ if (!payload)
8409
+ return;
8410
+ if (payload.type === "ready") {
8411
+ window.clearTimeout(timeout);
8412
+ flushFrameBuffer(false);
8413
+ resolve();
8414
+ return;
8415
+ }
8416
+ if (payload.type === "error") {
8417
+ window.clearTimeout(timeout);
8418
+ reject(new Error(payload.message));
8419
+ }
8285
8420
  };
8286
8421
  socket.onerror = () => {
8287
8422
  window.clearTimeout(timeout);
@@ -8309,32 +8444,6 @@ function useVoiceInput({
8309
8444
  setIsListening(false);
8310
8445
  setIsTranscribing(false);
8311
8446
  };
8312
- const stream = await navigator.mediaDevices.getUserMedia({
8313
- audio: {
8314
- echoCancellation: true,
8315
- noiseSuppression: true,
8316
- autoGainControl: true
8317
- }
8318
- });
8319
- streamRef.current = stream;
8320
- const AudioContextCtor = getAudioContextConstructor();
8321
- if (!AudioContextCtor)
8322
- throw new Error("AudioContext is unavailable");
8323
- const audioContext = new AudioContextCtor;
8324
- const source = audioContext.createMediaStreamSource(stream);
8325
- const analyserNode = audioContext.createAnalyser();
8326
- analyserNode.fftSize = 256;
8327
- analyserNode.smoothingTimeConstant = 0.55;
8328
- const processor = audioContext.createScriptProcessor(4096, 1, 1);
8329
- processor.onaudioprocess = handleAudioProcess;
8330
- source.connect(analyserNode);
8331
- source.connect(processor);
8332
- processor.connect(audioContext.destination);
8333
- audioContextRef.current = audioContext;
8334
- sourceRef.current = source;
8335
- processorRef.current = processor;
8336
- setAnalyser(analyserNode);
8337
- setIsListening(true);
8338
8447
  } catch (err) {
8339
8448
  const name = err instanceof Error ? err.name : "";
8340
8449
  const msg = name === "NotAllowedError" ? "Microphone permission denied" : err instanceof Error ? err.message : "Could not start voice input";
@@ -8346,6 +8455,7 @@ function useVoiceInput({
8346
8455
  emitError,
8347
8456
  handleAudioProcess,
8348
8457
  handleMissingModel,
8458
+ flushFrameBuffer,
8349
8459
  isSupported,
8350
8460
  lang
8351
8461
  ]);
@@ -8591,4 +8701,4 @@ export {
8591
8701
  getAgentToolCount
8592
8702
  };
8593
8703
 
8594
- //# debugId=FCCD29FAF084571164756E2164756E21
8704
+ //# debugId=43D698DB1EF766A364756E2164756E21