@lamplitisles/codex-for-love 0.3.0 → 0.4.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.
@@ -22663,6 +22663,21 @@ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
22663
22663
  /** The app-server protocol contract tested by this application. */
22664
22664
  const SUPPORTED_CODEX_VERSION = "0.154.0";
22665
22665
  const DEFAULT_CODEX_MODEL = "gpt-5.6-luna";
22666
+ const ttsSchema = object({
22667
+ provider: _enum([
22668
+ "minimax",
22669
+ "alibaba",
22670
+ "bytedance"
22671
+ ]),
22672
+ voice: string().min(1),
22673
+ speed: number().finite().min(.5).max(2).optional()
22674
+ }).strict().superRefine((tts, context) => {
22675
+ if (tts.provider === "alibaba" && tts.speed !== void 0) context.addIssue({
22676
+ code: "custom",
22677
+ path: ["speed"],
22678
+ message: "Alibaba TTS speed is unavailable on the configured non-realtime API"
22679
+ });
22680
+ });
22666
22681
  const schema = object({
22667
22682
  name: string().min(1),
22668
22683
  persona: string().min(1),
@@ -22688,14 +22703,11 @@ const schema = object({
22688
22703
  }),
22689
22704
  speech: object({
22690
22705
  endpoint: url().default("https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"),
22691
- tts: object({
22692
- provider: _enum([
22693
- "minimax",
22694
- "alibaba",
22695
- "bytedance"
22696
- ]),
22697
- voice: string().min(1)
22698
- }).strict().optional()
22706
+ tts: ttsSchema.optional()
22707
+ }).strict().optional(),
22708
+ keet: object({
22709
+ endpoint: string().min(1).optional(),
22710
+ media_root: string().min(1).optional()
22699
22711
  }).strict().optional()
22700
22712
  }).strict();
22701
22713
  async function loadConfig(path) {
@@ -22715,13 +22727,32 @@ async function loadConfig(path) {
22715
22727
  ...config.codex,
22716
22728
  home: config.codex.home ? resolve(base, config.codex.home) : void 0,
22717
22729
  provenance: config.codex.provenance ? resolve(base, config.codex.provenance) : void 0
22718
- }
22730
+ },
22731
+ keet: config.keet ? {
22732
+ ...config.keet.endpoint ? { endpoint: keetEndpoint(config.keet.endpoint) } : {},
22733
+ ...config.keet.media_root ? { media_root: keetMediaRoot(config.keet.media_root) } : {}
22734
+ } : void 0
22719
22735
  };
22720
22736
  }
22737
+ function keetEndpoint(value) {
22738
+ let url;
22739
+ try {
22740
+ url = new URL(value);
22741
+ } catch {
22742
+ throw new Error("Keet endpoint must be a loopback http URL");
22743
+ }
22744
+ if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || !url.port || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new Error("Keet endpoint must be a bare http://127.0.0.1:PORT URL");
22745
+ return url.origin;
22746
+ }
22747
+ function keetMediaRoot(value) {
22748
+ if (!value.startsWith("/")) throw new Error("Keet media_root must be an absolute path");
22749
+ return resolve(value);
22750
+ }
22721
22751
  /** Alibaba STT/TTS reuses speech; ByteDance TTS keeps its separate secret. */
22722
22752
  const credentialSchema = object({
22723
22753
  speech: string().min(1).optional(),
22724
- tts: string().min(1).optional()
22754
+ tts: string().min(1).optional(),
22755
+ keet: string().min(1).optional()
22725
22756
  }).strict();
22726
22757
  async function loadCredentials(state) {
22727
22758
  try {
@@ -22792,7 +22823,8 @@ async function provider(options) {
22792
22823
  speaker: options.voice,
22793
22824
  audio_params: {
22794
22825
  format: "mp3",
22795
- sample_rate: 24e3
22826
+ sample_rate: 24e3,
22827
+ ...options.speed === void 0 ? {} : { speech_rate: Math.round((options.speed - 1) * 100) }
22796
22828
  }
22797
22829
  }
22798
22830
  })
@@ -22838,6 +22870,8 @@ async function minimax(options) {
22838
22870
  options.text,
22839
22871
  "--voice",
22840
22872
  options.voice,
22873
+ "--speed",
22874
+ String(options.speed),
22841
22875
  "--format",
22842
22876
  "mp3",
22843
22877
  "--out",
@@ -22852,7 +22886,8 @@ async function minimax(options) {
22852
22886
  }
22853
22887
  async function synthesizeSpeech(options) {
22854
22888
  const text = options.text.replace(/[\s\u00a0]+/gu, " ").trim();
22855
- if (!text || Array.from(text).length > 240) throw new Error("Invalid speech passage");
22889
+ const speed = options.speed ?? 1;
22890
+ if (!text || Array.from(text).length > 240 || !Number.isFinite(speed) || speed < .5 || speed > 2) throw new Error("Invalid speech passage");
22856
22891
  const profile = options.provider === "minimax" ? "speech-2.8-hd" : options.model;
22857
22892
  if (options.provider !== "minimax" && (!options.endpoint || !options.model || !options.credential)) throw new Error("Speech synthesis is unavailable");
22858
22893
  const key = createHash("sha256").update(JSON.stringify([
@@ -22860,6 +22895,7 @@ async function synthesizeSpeech(options) {
22860
22895
  options.provider,
22861
22896
  profile,
22862
22897
  options.voice,
22898
+ speed,
22863
22899
  text
22864
22900
  ])).digest("hex");
22865
22901
  const path = join(options.audioDir, `${key}.mp3`);
@@ -22875,6 +22911,7 @@ async function synthesizeSpeech(options) {
22875
22911
  if (options.provider === "minimax") await minimax({
22876
22912
  text,
22877
22913
  voice: options.voice,
22914
+ speed,
22878
22915
  output: temp,
22879
22916
  signal,
22880
22917
  execFileImpl: options.execFileImpl ?? execFileAsync
@@ -22886,6 +22923,7 @@ async function synthesizeSpeech(options) {
22886
22923
  model: options.model,
22887
22924
  credential: options.credential,
22888
22925
  voice: options.voice,
22926
+ speed: options.speed,
22889
22927
  text,
22890
22928
  signal,
22891
22929
  fetchImpl: options.fetchImpl ?? fetch