@speech-sdk/core 0.31.0 → 0.33.0

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 (85) hide show
  1. package/README.md +39 -2
  2. package/dist/apply-speed.d.ts +1 -0
  3. package/dist/apply-speed.d.ts.map +1 -1
  4. package/dist/apply-speed.js +7 -5
  5. package/dist/apply-speed.js.map +1 -1
  6. package/dist/audio-decode.js +3 -2
  7. package/dist/audio-decode.js.map +1 -1
  8. package/dist/audio-output.d.ts +1 -0
  9. package/dist/audio-output.d.ts.map +1 -1
  10. package/dist/audio-output.js +1 -1
  11. package/dist/audio-output.js.map +1 -1
  12. package/dist/conversation/attribute-timestamps.d.ts +1 -21
  13. package/dist/conversation/attribute-timestamps.d.ts.map +1 -1
  14. package/dist/conversation/attribute-timestamps.js +26 -264
  15. package/dist/conversation/attribute-timestamps.js.map +1 -1
  16. package/dist/conversation/dispatch.d.ts +4 -2
  17. package/dist/conversation/dispatch.d.ts.map +1 -1
  18. package/dist/conversation/dispatch.js +47 -18
  19. package/dist/conversation/dispatch.js.map +1 -1
  20. package/dist/conversation/errors.d.ts +9 -0
  21. package/dist/conversation/errors.d.ts.map +1 -1
  22. package/dist/conversation/errors.js +18 -0
  23. package/dist/conversation/errors.js.map +1 -1
  24. package/dist/conversation/pcm-concat.d.ts +13 -0
  25. package/dist/conversation/pcm-concat.d.ts.map +1 -1
  26. package/dist/conversation/pcm-concat.js +22 -4
  27. package/dist/conversation/pcm-concat.js.map +1 -1
  28. package/dist/conversation/split-turns.d.ts +31 -0
  29. package/dist/conversation/split-turns.d.ts.map +1 -0
  30. package/dist/conversation/split-turns.js +158 -0
  31. package/dist/conversation/split-turns.js.map +1 -0
  32. package/dist/conversation/stitch.d.ts +1 -3
  33. package/dist/conversation/stitch.d.ts.map +1 -1
  34. package/dist/conversation/stitch.js +10 -15
  35. package/dist/conversation/stitch.js.map +1 -1
  36. package/dist/conversation/types.d.ts +5 -0
  37. package/dist/conversation/types.d.ts.map +1 -1
  38. package/dist/conversation/validate.d.ts.map +1 -1
  39. package/dist/conversation/validate.js +3 -0
  40. package/dist/conversation/validate.js.map +1 -1
  41. package/dist/generate-conversation.d.ts +7 -3
  42. package/dist/generate-conversation.d.ts.map +1 -1
  43. package/dist/generate-conversation.js +154 -105
  44. package/dist/generate-conversation.js.map +1 -1
  45. package/dist/generate-speech.d.ts.map +1 -1
  46. package/dist/generate-speech.js +55 -14
  47. package/dist/generate-speech.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/metadata.d.ts +9 -0
  53. package/dist/metadata.d.ts.map +1 -1
  54. package/dist/providers/google/index.d.ts +3 -0
  55. package/dist/providers/google/index.d.ts.map +1 -1
  56. package/dist/providers/google/index.js +203 -64
  57. package/dist/providers/google/index.js.map +1 -1
  58. package/dist/sentence-boundaries.d.ts.map +1 -1
  59. package/dist/sentence-boundaries.js +2 -2
  60. package/dist/sentence-boundaries.js.map +1 -1
  61. package/dist/speech-provider.d.ts +1 -0
  62. package/dist/speech-provider.d.ts.map +1 -1
  63. package/dist/speech-provider.js.map +1 -1
  64. package/dist/speech-result.d.ts +20 -0
  65. package/dist/speech-result.d.ts.map +1 -1
  66. package/dist/speech-result.js.map +1 -1
  67. package/dist/text-chunker.d.ts +2 -0
  68. package/dist/text-chunker.d.ts.map +1 -1
  69. package/dist/text-chunker.js +68 -1
  70. package/dist/text-chunker.js.map +1 -1
  71. package/dist/timestamp-finalization.d.ts +1 -0
  72. package/dist/timestamp-finalization.d.ts.map +1 -1
  73. package/dist/timestamp-finalization.js +5 -5
  74. package/dist/timestamp-finalization.js.map +1 -1
  75. package/dist/types.d.ts +3 -1
  76. package/dist/types.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/dist/conversation/proportional-fill.d.ts +0 -10
  79. package/dist/conversation/proportional-fill.d.ts.map +0 -1
  80. package/dist/conversation/proportional-fill.js +0 -64
  81. package/dist/conversation/proportional-fill.js.map +0 -1
  82. package/dist/conversation/silence-detection.d.ts +0 -14
  83. package/dist/conversation/silence-detection.d.ts.map +0 -1
  84. package/dist/conversation/silence-detection.js +0 -52
  85. package/dist/conversation/silence-detection.js.map +0 -1
@@ -27,6 +27,11 @@ export interface GenerateConversationOptions<V extends Voice = Voice, M extends
27
27
  readonly pronunciations?: PronunciationsInput;
28
28
  readonly providerOptions?: Record<string, unknown>;
29
29
  readonly speed?: number;
30
+ /**
31
+ * Also return `turns`: one audio clip per input turn, cut at the silence after each turn's last word. Requires
32
+ * `timestamps: true`. Throws `TurnSplitError` when turn boundaries can't be trusted.
33
+ */
34
+ readonly splitTurns?: boolean;
30
35
  readonly timestampProvider?: TimestampProvider;
31
36
  readonly timestamps?: boolean;
32
37
  readonly turns: readonly ConversationTurn<V>[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IACvD,mDAAmD;IACnD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B,CAC1C,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS;IAEb,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IACvD,mDAAmD;IACnD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B,CAC1C,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS;IAEb,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/conversation/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAEhF,wBAAgB,aAAa,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,MAAM,CAE5E;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,2BAA2B,GACnC,IAAI,CA2BN"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/conversation/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAEhF,wBAAgB,aAAa,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,MAAM,CAE5E;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,2BAA2B,GACnC,IAAI,CAiCN"}
@@ -6,6 +6,9 @@ export function validateConversationInput(options) {
6
6
  if (options.turns.length === 0) {
7
7
  throw new ConversationInputError("generateConversation requires at least one turn.");
8
8
  }
9
+ if (options.splitTurns && options.timestamps !== true) {
10
+ throw new ConversationInputError("splitTurns requires timestamps: true — turn boundaries come from word timestamps.");
11
+ }
9
12
  // Model placement must be all-or-nothing — partial mix hides which model actually ran where.
10
13
  const hasTopLevel = options.model != null;
11
14
  for (let i = 0; i < options.turns.length; i++) {
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/conversation/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAGrD,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAoC;IAEpC,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,sBAAsB,CAC9B,kDAAkD,CACnD,CAAC;IACJ,CAAC;IAED,6FAA6F;IAC7F,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;IAE1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,sBAAsB,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC;QACxC,IAAI,WAAW,IAAI,YAAY,EAAE,CAAC;YAChC,MAAM,IAAI,sBAAsB,CAC9B,SAAS,CAAC,kIAAkI,CAC7I,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,CAAC,WAAW,IAAI,YAAY,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,sBAAsB,CAC9B,SAAS,CAAC,2HAA2H,CACtI,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/conversation/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAGrD,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAoC;IAEpC,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,sBAAsB,CAC9B,kDAAkD,CACnD,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QACtD,MAAM,IAAI,sBAAsB,CAC9B,mFAAmF,CACpF,CAAC;IACJ,CAAC;IAED,6FAA6F;IAC7F,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;IAE1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,sBAAsB,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC;QACxC,IAAI,WAAW,IAAI,YAAY,EAAE,CAAC;YAChC,MAAM,IAAI,sBAAsB,CAC9B,SAAS,CAAC,kIAAkI,CAC7I,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,CAAC,WAAW,IAAI,YAAY,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,sBAAsB,CAC9B,SAAS,CAAC,2HAA2H,CACtI,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -1,9 +1,13 @@
1
1
  import type { GenerateConversationOptions } from "./conversation/types.js";
2
2
  import { type ResolvedModel, type Voice } from "./speech-provider.js";
3
- import type { ConversationResult, ConversationResultWithTimestamps } from "./speech-result.js";
4
- export { ConversationInputError, DialogueConstraintError, StitchUnsupportedError, } from "./conversation/errors.js";
3
+ import type { ConversationResult, ConversationResultWithTimestamps, ConversationResultWithTurns } from "./speech-result.js";
4
+ export { ConversationInputError, DialogueConstraintError, StitchUnsupportedError, TurnSplitError, type TurnSplitFailureReason, } from "./conversation/errors.js";
5
5
  export type { ConversationTurn, GenerateConversationOptions, } from "./conversation/types.js";
6
- export type { ConversationMetadata, ConversationResult, ConversationResultWithTimestamps, } from "./speech-result.js";
6
+ export type { ConversationMetadata, ConversationPathKind, ConversationResult, ConversationResultWithTimestamps, ConversationResultWithTurns, ConversationStitchReason, ConversationTurnAudio, } from "./speech-result.js";
7
+ export declare function generateConversation<V extends Voice = Voice, M extends string | ResolvedModel<V> | undefined = string | ResolvedModel<V> | undefined>(options: GenerateConversationOptions<V, M> & {
8
+ timestamps: true;
9
+ splitTurns: true;
10
+ }): Promise<ConversationResultWithTurns>;
7
11
  export declare function generateConversation<V extends Voice = Voice, M extends string | ResolvedModel<V> | undefined = string | ResolvedModel<V> | undefined>(options: GenerateConversationOptions<V, M> & {
8
12
  timestamps: true;
9
13
  }): Promise<ConversationResultWithTimestamps>;
@@ -1 +1 @@
1
- {"version":3,"file":"generate-conversation.d.ts","sourceRoot":"","sources":["../src/generate-conversation.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,2BAA2B,EAC5B,MAAM,yBAAyB,CAAC;AAyBjC,OAAO,EAGL,KAAK,aAAa,EAElB,KAAK,KAAK,EACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAEV,kBAAkB,EAClB,gCAAgC,EACjC,MAAM,oBAAoB,CAAC;AAU5B,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,gCAAgC,GACjC,MAAM,oBAAoB,CAAC;AAkC5B,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS,EAEb,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,GAChE,OAAO,CAAC,gCAAgC,CAAC,CAAC;AAC7C,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS,EACb,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC"}
1
+ {"version":3,"file":"generate-conversation.d.ts","sourceRoot":"","sources":["../src/generate-conversation.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,2BAA2B,EAC5B,MAAM,yBAAyB,CAAC;AAwBjC,OAAO,EAGL,KAAK,aAAa,EAElB,KAAK,KAAK,EACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAEV,kBAAkB,EAClB,gCAAgC,EAChC,2BAA2B,EAC5B,MAAM,oBAAoB,CAAC;AAU5B,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACd,KAAK,sBAAsB,GAC5B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,gCAAgC,EAChC,2BAA2B,EAC3B,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAkC5B,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS,EAEb,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAC3C,UAAU,EAAE,IAAI,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB,GACA,OAAO,CAAC,2BAA2B,CAAC,CAAC;AACxC,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS,EAEb,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,GAChE,OAAO,CAAC,gCAAgC,CAAC,CAAC;AAC7C,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS,EACb,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC"}
@@ -1,9 +1,10 @@
1
1
  import pRetry from "p-retry";
2
- import { applySpeedToAudio, isSpeedActive, scaleTimestamps, validateSpeed, } from "./apply-speed.js";
2
+ import { applySpeedToAudio, isSpeedActive, outputAfterSpeed, scaleTimestamps, validateSpeed, } from "./apply-speed.js";
3
3
  import { computeAudioDuration } from "./audio-duration.js";
4
4
  import { applyOptionalOutputConversion, sampleRateHintFrom, validateOutput, } from "./audio-output.js";
5
5
  import { mapWithConcurrency, resolveMaxConcurrency } from "./concurrency.js";
6
6
  import { chooseConversationPath } from "./conversation/dispatch.js";
7
+ import { TurnSplitError } from "./conversation/errors.js";
7
8
  import { validateConversationInput } from "./conversation/validate.js";
8
9
  import { deriveTimestampsViaSTT } from "./derive-timestamps.js";
9
10
  import { NoSpeechGeneratedError, OutputConversionUnsupportedError, TimestampProviderRequiredError, TimestampValidationError, } from "./errors.js";
@@ -21,7 +22,7 @@ import { preprocessSpeechText } from "./text-preprocessing.js";
21
22
  import { deriveTimestampsViaProvider } from "./timestamp-alignment.js";
22
23
  import { finalizeTimestamps } from "./timestamp-finalization.js";
23
24
  // biome-ignore lint/performance/noBarrelFile: public entry point — re-export error classes so callers get fn + types + errors from one import
24
- export { ConversationInputError, DialogueConstraintError, StitchUnsupportedError, } from "./conversation/errors.js";
25
+ export { ConversationInputError, DialogueConstraintError, StitchUnsupportedError, TurnSplitError, } from "./conversation/errors.js";
25
26
  const DEFAULT_GAP_MS = 300;
26
27
  const DEFAULT_MAX_RETRIES = 2;
27
28
  function requireValidTimestamps(args) {
@@ -84,12 +85,18 @@ export async function generateConversation(options) {
84
85
  for (const [index, resolved] of resolvedPerTurn.entries()) {
85
86
  validateInstructionSupport(resolved, combineInstructions(options.instructions, options.turns[index].instructions));
86
87
  }
87
- const forceStitch = hasPerTurnSpeed ||
88
- needsConversationStitchForMaxInputChars({
89
- resolvedPerTurn,
90
- turns: options.turns,
91
- userMaxInputChars: options.maxInputChars,
92
- });
88
+ const needsMaxInputCharsStitch = needsConversationStitchForMaxInputChars({
89
+ resolvedPerTurn,
90
+ turns: options.turns,
91
+ userMaxInputChars: options.maxInputChars,
92
+ });
93
+ let forceStitch;
94
+ if (hasPerTurnSpeed) {
95
+ forceStitch = "per-turn-speed";
96
+ }
97
+ else if (needsMaxInputCharsStitch) {
98
+ forceStitch = "max-input-chars";
99
+ }
93
100
  const path = chooseConversationPath({
94
101
  forceStitch,
95
102
  resolvedPerTurn,
@@ -97,14 +104,13 @@ export async function generateConversation(options) {
97
104
  output: options.output,
98
105
  });
99
106
  if (path.kind === "native") {
100
- return await applySpeedToConversationResult({
101
- result: await runNativeDispatch({
107
+ return await finishConversation({
108
+ run: await runNativeDispatch({
102
109
  options,
103
110
  resolved: path.resolved,
104
111
  blocks: path.blocks,
105
112
  }),
106
- speed: options.speed,
107
- output: options.output,
113
+ options,
108
114
  });
109
115
  }
110
116
  // Lazy-load so native-only callers don't bundle pcm-concat / mediabunny.
@@ -119,7 +125,6 @@ export async function generateConversation(options) {
119
125
  maxConcurrency: resolveMaxConcurrency(options.maxConcurrency),
120
126
  maxInputChars: options.maxInputChars,
121
127
  maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
122
- output: options.output,
123
128
  volumeDbfs: options.volumeDbfs,
124
129
  abortSignal: options.abortSignal,
125
130
  headers: options.headers,
@@ -127,8 +132,6 @@ export async function generateConversation(options) {
127
132
  timestamps: options.timestamps ?? false,
128
133
  timestampProvider: options.timestampProvider,
129
134
  pronunciations: options.pronunciations,
130
- // Defer output conversion to applySpeedToConversationResult to avoid encoding twice.
131
- deferOutputConversion: isSpeedActive(options.speed),
132
135
  });
133
136
  if (stitched.audio.length === 0) {
134
137
  throw new NoSpeechGeneratedError(`${describeConversationModels(resolvedPerTurn)}: stitched conversation audio is empty.`, { reason: "provider_empty_response" });
@@ -140,11 +143,16 @@ export async function generateConversation(options) {
140
143
  audioDurationMs: stitched.metadata.audioDurationMs,
141
144
  }),
142
145
  perTurn: stitched.metadataPerTurn,
146
+ path: "stitch",
147
+ stitchReason: path.stitchReason,
143
148
  };
144
149
  let fallbackWarning;
145
150
  if (path.reason === "fallback-from-native") {
146
151
  fallbackWarning = `native dialogue unavailable because per-turn providerOptions are set; rendered via stitch (${options.turns.length} API calls instead of 1)`;
147
152
  }
153
+ else if (path.reason === "fallback-from-native-custom-voice") {
154
+ fallbackWarning = `native dialogue requires prebuilt voices; custom voices were rendered via stitch (${options.turns.length} generateSpeech calls) instead`;
155
+ }
148
156
  else if (path.reason === "fallback-from-native-oversized") {
149
157
  fallbackWarning = `native dialogue exceeds the provider's per-call limit and couldn't be split into voice-valid blocks; rendered via stitch (${options.turns.length} API calls instead of 1)`;
150
158
  }
@@ -157,20 +165,67 @@ export async function generateConversation(options) {
157
165
  const combinedWarnings = fallbackWarning
158
166
  ? [fallbackWarning, ...stitched.warnings]
159
167
  : stitched.warnings;
160
- return await applySpeedToConversationResult({
161
- result: {
162
- audio: new DefaultGeneratedAudioFile({
163
- data: stitched.audio,
164
- mediaType: stitched.mediaType,
165
- }),
166
- metadata,
167
- providerMetadata: { turns: stitched.providerMetadataPerTurn },
168
- warnings: combinedWarnings.length > 0 ? [...combinedWarnings] : undefined,
169
- timestamps: stitched.timestamps,
168
+ return await finishConversation({
169
+ run: {
170
+ result: {
171
+ audio: new DefaultGeneratedAudioFile({
172
+ data: stitched.audio,
173
+ mediaType: stitched.mediaType,
174
+ }),
175
+ metadata,
176
+ providerMetadata: { turns: stitched.providerMetadataPerTurn },
177
+ warnings: combinedWarnings.length > 0 ? [...combinedWarnings] : undefined,
178
+ timestamps: stitched.timestamps,
179
+ },
180
+ knownCutsSec: stitched.turnCutsSec,
170
181
  },
182
+ options,
183
+ });
184
+ }
185
+ // Runners return decodable audio; the requested output format is encoded exactly once, here.
186
+ async function finishConversation(args) {
187
+ const { options, run } = args;
188
+ const speed = isSpeedActive(options.speed) ? options.speed : 1;
189
+ const output = speed === 1 ? options.output : outputAfterSpeed(options.output);
190
+ const result = await applySpeedToConversationResult({
191
+ result: run.result,
171
192
  speed: options.speed,
172
- output: options.output,
173
193
  });
194
+ if (!options.splitTurns) {
195
+ return await encodeConversationAudio(result, output);
196
+ }
197
+ const { splitConversationTurns } = await import("./conversation/split-turns.js");
198
+ const split = await splitConversationTurns({
199
+ audio: result.audio.uint8Array,
200
+ knownCutsSec: run.knownCutsSec?.map((sec) => sec == null ? sec : sec / speed),
201
+ mediaType: result.audio.mediaType,
202
+ output,
203
+ timestamps: result.timestamps ?? [],
204
+ turnCount: options.turns.length,
205
+ });
206
+ const encoded = await encodeConversationAudio(result, output, {
207
+ audio: new Uint8Array(split.pcm.buffer, split.pcm.byteOffset, split.pcm.byteLength),
208
+ mediaType: `audio/pcm;rate=${split.sampleRate}`,
209
+ });
210
+ return { ...encoded, turns: split.turns };
211
+ }
212
+ async function encodeConversationAudio(result, output,
213
+ // Same audio already decoded, when the caller has it, to skip a second decode.
214
+ source = {
215
+ audio: result.audio.uint8Array,
216
+ mediaType: result.audio.mediaType,
217
+ }) {
218
+ if (!output) {
219
+ return result;
220
+ }
221
+ const converted = await applyOptionalOutputConversion({ ...source, output });
222
+ return {
223
+ ...result,
224
+ audio: new DefaultGeneratedAudioFile({
225
+ data: converted.audio,
226
+ mediaType: converted.mediaType,
227
+ }),
228
+ };
174
229
  }
175
230
  function needsConversationStitchForMaxInputChars(args) {
176
231
  let forceStitch = false;
@@ -220,6 +275,27 @@ function finalizeConversationTurnTimestamps(args) {
220
275
  });
221
276
  return projected;
222
277
  }
278
+ // Force decodable PCM/WAV via getStitchOptions for normalization; if unavailable, emit the provider's mixed audio and warn.
279
+ function resolveNativeStitchOptions(options, resolved) {
280
+ const stitchOpts = resolved.provider.getStitchOptions?.(resolved.modelId, {
281
+ sampleRate: sampleRateHintFrom(options.output),
282
+ });
283
+ if (stitchOpts) {
284
+ return { stitchOpts, warnings: [] };
285
+ }
286
+ if (options.splitTurns) {
287
+ throw new TurnSplitError({ reason: "undecodable_audio" });
288
+ }
289
+ if (options.output) {
290
+ throw new OutputConversionUnsupportedError(`${resolved.provider.id}/${resolved.modelId}`);
291
+ }
292
+ return {
293
+ stitchOpts,
294
+ warnings: [
295
+ `${resolved.provider.id}/${resolved.modelId}: native dialogue path returns the provider's mixed audio without volume normalization (no decodable PCM/WAV mode).`,
296
+ ],
297
+ };
298
+ }
223
299
  async function runNative(args) {
224
300
  const { options, resolved, maxRetries } = args;
225
301
  const start = performance.now();
@@ -227,17 +303,7 @@ async function runNative(args) {
227
303
  throw new Error(`generateConversation: ${resolved.provider.id}/${resolved.modelId} dispatched to native but generateDialogue missing`);
228
304
  }
229
305
  const generateDialogue = resolved.provider.generateDialogue.bind(resolved.provider);
230
- // Force decodable PCM/WAV via getStitchOptions for normalization; if unavailable, emit the provider's mixed audio and warn.
231
- const stitchOpts = resolved.provider.getStitchOptions?.(resolved.modelId, {
232
- sampleRate: sampleRateHintFrom(options.output),
233
- });
234
- const warnings = [];
235
- if (!stitchOpts) {
236
- warnings.push(`${resolved.provider.id}/${resolved.modelId}: native dialogue path returns the provider's mixed audio without volume normalization (no decodable PCM/WAV mode).`);
237
- }
238
- if (options.output && !stitchOpts) {
239
- throw new OutputConversionUnsupportedError(`${resolved.provider.id}/${resolved.modelId}`);
240
- }
306
+ const { stitchOpts, warnings } = resolveNativeStitchOptions(options, resolved);
241
307
  // Stitch options must win — caller-supplied response_format would break the decoder.
242
308
  const dialogueProviderOptions = stitchOpts
243
309
  ? { ...options.providerOptions, ...stitchOpts.providerOptions }
@@ -289,7 +355,7 @@ async function runNative(args) {
289
355
  });
290
356
  const computedDuration = await computeAudioDuration(audio.uint8Array, outputMediaType);
291
357
  const audioDurationMs = computedDuration ?? result.audioDurationMs;
292
- const { timestamps: rawTimestamps, warnings: attributionWarnings } = await resolveNativeDialogueTimestamps({
358
+ const rawTimestamps = await resolveNativeDialogueTimestamps({
293
359
  requestTimestamps,
294
360
  nativeTimestamps: result.timestamps,
295
361
  hasNativeTimestamps: hasNativeDialogueTimestamps,
@@ -311,42 +377,30 @@ async function runNative(args) {
311
377
  timestamps: projection.timestamps,
312
378
  turnTexts: substitutedTurns.map((turn) => turn.originalText),
313
379
  });
314
- // Defer output conversion to applySpeedToConversationResult when top-level speed
315
- // is active — otherwise we'd encode here and re-encode in the stretch step.
316
- const deferOutput = isSpeedActive(options.speed);
317
- const converted = await applyOptionalOutputConversion({
318
- audio: audio.uint8Array,
319
- mediaType: outputMediaType,
320
- output: deferOutput ? undefined : options.output,
321
- });
322
- const finalAudio = options.output && !deferOutput
323
- ? new DefaultGeneratedAudioFile({
324
- data: converted.audio,
325
- mediaType: converted.mediaType,
326
- })
327
- : audio;
328
380
  const inputChars = options.turns.reduce((n, t) => n + t.text.length, 0);
329
381
  const metadata = {
330
382
  latencyMs,
331
383
  inputChars,
332
384
  ...(audioDurationMs != null && { audioDurationMs }),
385
+ path: "native",
333
386
  };
334
387
  const preprocessingWarnings = substitutedTurns.flatMap((turn) => turn.warnings);
335
388
  const mergedWarningList = [
336
389
  ...warnings,
337
390
  ...preprocessingWarnings,
338
- ...attributionWarnings,
339
391
  ...(projection.estimatedBoundaries
340
392
  ? [PRONUNCIATION_TIMESTAMP_ESTIMATE_WARNING]
341
393
  : []),
342
394
  ];
343
395
  const mergedWarnings = mergedWarningList.length > 0 ? mergedWarningList : undefined;
344
396
  return {
345
- audio: finalAudio,
346
- metadata,
347
- providerMetadata: result.providerMetadata,
348
- warnings: mergedWarnings,
349
- timestamps,
397
+ result: {
398
+ audio,
399
+ metadata,
400
+ providerMetadata: result.providerMetadata,
401
+ warnings: mergedWarnings,
402
+ timestamps,
403
+ },
350
404
  };
351
405
  }
352
406
  async function runNativeDispatch(args) {
@@ -429,7 +483,7 @@ async function runNativeSplit(args) {
429
483
  mediaType: stitchOpts.mediaType,
430
484
  }).uint8Array;
431
485
  const segment = await decodeAudioToPcm16(blockAudio, stitchOpts.mediaType);
432
- const { timestamps, warnings } = await resolveNativeDialogueTimestamps({
486
+ const timestamps = await resolveNativeDialogueTimestamps({
433
487
  requestTimestamps,
434
488
  nativeTimestamps: result.timestamps,
435
489
  hasNativeTimestamps,
@@ -441,20 +495,29 @@ async function runNativeSplit(args) {
441
495
  audioDurationMs: (segment.pcm.length / segment.sampleRate) * 1000,
442
496
  substitutedTurnTexts: blockTurns.map((t) => t.canonicalText),
443
497
  timestampProvider: options.timestampProvider,
444
- pcmSegment: segment,
445
498
  });
446
499
  return {
447
500
  segment,
448
501
  timestamps,
449
- warnings,
450
502
  providerMetadata: result.providerMetadata,
451
503
  };
452
504
  }, { signal: options.abortSignal });
453
- const { concatPcmToWav, dbfsToInt16Rms, normalizeRms, stitchTargetRate } = await import("./conversation/pcm-concat.js");
505
+ const { concatPcmToWavWithRanges, dbfsToInt16Rms, gapMidpointsSec, normalizeRms, stitchTargetRate, } = await import("./conversation/pcm-concat.js");
454
506
  const segments = perBlock.map((p) => p.segment);
455
507
  const leveled = normalizeRms(segments, options.volumeDbfs == null ? undefined : dbfsToInt16Rms(options.volumeDbfs));
456
508
  const targetSampleRate = stitchTargetRate(leveled);
457
- const wav = await concatPcmToWav(leveled, { gapMs, targetSampleRate });
509
+ const { wav, ranges } = await concatPcmToWavWithRanges(leveled, {
510
+ gapMs,
511
+ targetSampleRate,
512
+ });
513
+ // Block joins are the only boundaries the SDK cut itself; boundaries inside a block stay undefined.
514
+ const knownCutsSec = [];
515
+ for (const [b, cutSec] of gapMidpointsSec(ranges, targetSampleRate).entries()) {
516
+ const lastTurn = blocks[b].at(-1);
517
+ if (lastTurn != null) {
518
+ knownCutsSec[lastTurn] = cutSec;
519
+ }
520
+ }
458
521
  const timestampProjection = requestTimestamps
459
522
  ? composeBlockTimestamps({
460
523
  perBlock,
@@ -465,15 +528,9 @@ async function runNativeSplit(args) {
465
528
  })
466
529
  : { estimatedBoundaries: false, timestamps: undefined };
467
530
  const timestamps = timestampProjection.timestamps;
468
- const deferOutput = isSpeedActive(options.speed);
469
- const converted = await applyOptionalOutputConversion({
470
- audio: wav,
471
- mediaType: "audio/wav",
472
- output: deferOutput ? undefined : options.output,
473
- });
474
531
  const finalAudio = new DefaultGeneratedAudioFile({
475
- data: converted.audio,
476
- mediaType: converted.mediaType,
532
+ data: wav,
533
+ mediaType: "audio/wav",
477
534
  });
478
535
  // Derive duration from the PCM sample counts (resampled to the stitch rate plus gaps)
479
536
  // instead of re-decoding the merged audio — matches the stitch path.
@@ -486,20 +543,23 @@ async function runNativeSplit(args) {
486
543
  latencyMs: Math.round(performance.now() - start),
487
544
  inputChars,
488
545
  ...(audioDurationMs != null && { audioDurationMs }),
546
+ path: "native-split",
489
547
  };
490
548
  const warnings = [
491
549
  ...substitutedTurns.flatMap((turn) => turn.warnings),
492
- ...perBlock.flatMap((p) => p.warnings),
493
550
  ...(timestampProjection.estimatedBoundaries
494
551
  ? [PRONUNCIATION_TIMESTAMP_ESTIMATE_WARNING]
495
552
  : []),
496
553
  ];
497
554
  return {
498
- audio: finalAudio,
499
- metadata,
500
- providerMetadata: { blocks: perBlock.map((p) => p.providerMetadata) },
501
- warnings: warnings.length > 0 ? [...warnings] : undefined,
502
- timestamps,
555
+ result: {
556
+ audio: finalAudio,
557
+ metadata,
558
+ providerMetadata: { blocks: perBlock.map((p) => p.providerMetadata) },
559
+ warnings: warnings.length > 0 ? [...warnings] : undefined,
560
+ timestamps,
561
+ },
562
+ knownCutsSec,
503
563
  };
504
564
  }
505
565
  function composeBlockTimestamps(args) {
@@ -538,7 +598,7 @@ function composeBlockTimestamps(args) {
538
598
  }
539
599
  async function resolveNativeDialogueTimestamps(args) {
540
600
  if (!args.requestTimestamps) {
541
- return { timestamps: undefined, warnings: [] };
601
+ return;
542
602
  }
543
603
  // Either use native flat timestamps, or derive via STT fallback.
544
604
  let flatTimestamps;
@@ -582,36 +642,24 @@ async function resolveNativeDialogueTimestamps(args) {
582
642
  text: args.substitutedTurnTexts.join(" "),
583
643
  timestamps: flatTimestamps,
584
644
  });
585
- const { detectSilenceGaps } = await import("./conversation/silence-detection.js");
586
645
  const { attributeTimestamps } = await import("./conversation/attribute-timestamps.js");
587
- let silenceGaps = [];
588
- try {
589
- const segment = args.pcmSegment ??
590
- (await (await import("./audio-decode.js")).decodeAudioToPcm16(args.audio, args.mediaType));
591
- const gaps = detectSilenceGaps(segment.pcm, {
592
- sampleRate: segment.sampleRate,
593
- minDurationMs: 150,
594
- });
595
- silenceGaps = gaps;
596
- }
597
- catch {
598
- // Decoder couldn't read the audio (e.g., compressed format we can't decode locally).
599
- // Tier 1 will be skipped; dispatcher falls through to Tier 2/3.
600
- }
601
- const result = attributeTimestamps({
646
+ // Validated words cover the joined turn text exactly, so an exact partition always exists.
647
+ const attributed = attributeTimestamps({
602
648
  timestamps: flatTimestamps,
603
649
  turnTexts: args.substitutedTurnTexts,
604
- silenceGaps,
605
650
  });
606
- return {
607
- timestamps: finalizeConversationTurnTimestamps({
608
- audioDurationMs: args.audioDurationMs,
651
+ if (!attributed) {
652
+ throw new TimestampValidationError({
653
+ reason: "transcript_mismatch",
609
654
  source: args.ttsModel,
610
- timestamps: result.timestamps,
611
- turnTexts: args.substitutedTurnTexts,
612
- }),
613
- warnings: result.warnings,
614
- };
655
+ });
656
+ }
657
+ return finalizeConversationTurnTimestamps({
658
+ audioDurationMs: args.audioDurationMs,
659
+ source: args.ttsModel,
660
+ timestamps: attributed,
661
+ turnTexts: args.substitutedTurnTexts,
662
+ });
615
663
  }
616
664
  async function buildNativeAudio(args) {
617
665
  let audioBytes = args.result.audio;
@@ -663,7 +711,7 @@ function buildSubstitutedTurns(turns, resolved, ruleMap) {
663
711
  });
664
712
  }
665
713
  async function applySpeedToConversationResult(args) {
666
- const { result, speed, output } = args;
714
+ const { result, speed } = args;
667
715
  if (!isSpeedActive(speed)) {
668
716
  return result;
669
717
  }
@@ -671,7 +719,8 @@ async function applySpeedToConversationResult(args) {
671
719
  audio: result.audio.uint8Array,
672
720
  mediaType: result.audio.mediaType,
673
721
  speed,
674
- output,
722
+ // finishConversation encodes the requested format once, after any split.
723
+ output: { format: "wav" },
675
724
  });
676
725
  const newAudio = new DefaultGeneratedAudioFile({
677
726
  data: stretched.audio,