@nebutra/tts 0.2.2 → 0.2.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @nebutra/tts
2
2
 
3
+ ## 0.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies []:
8
+ - @nebutra/capability-kit@2.0.0
9
+ - @nebutra/provider-factory@2.0.0
10
+
3
11
  ## 0.2.2
4
12
 
5
13
  ### Patch Changes
package/package.json CHANGED
@@ -1,23 +1,29 @@
1
1
  {
2
2
  "name": "@nebutra/tts",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Provider-agnostic text-to-speech / narration synthesis. Zero-config deterministic mock default; ElevenLabs/OpenAI/Volces adapters wired via the shared provider-factory pattern.",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
8
  "nebutra": {
9
+ "status": "wip",
10
+ "graph": "labs",
9
11
  "featureId": "tts",
10
12
  "category": "integrations",
11
13
  "summary": "Multi-tenant text-to-speech provider abstraction"
12
14
  },
13
- "main": "./src/index.ts",
14
- "types": "./src/index.ts",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
15
17
  "exports": {
16
- ".": "./src/index.ts"
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ }
17
23
  },
18
24
  "dependencies": {
19
- "@nebutra/capability-kit": "0.2.2",
20
- "@nebutra/provider-factory": "0.2.2"
25
+ "@nebutra/capability-kit": "2.0.0",
26
+ "@nebutra/provider-factory": "2.0.0"
21
27
  },
22
28
  "devDependencies": {
23
29
  "@types/node": "^25.9.1",
@@ -37,6 +43,12 @@
37
43
  "publishConfig": {
38
44
  "access": "public"
39
45
  },
46
+ "files": [
47
+ "dist",
48
+ "README.md",
49
+ "LICENSE",
50
+ "CHANGELOG.md"
51
+ ],
40
52
  "scripts": {
41
53
  "build": "tsup",
42
54
  "test": "vitest run",
@@ -1,17 +0,0 @@
1
-
2
- > @nebutra/tts@0.2.2 build /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/integrations/tts
3
- > tsup
4
-
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/integrations/tts/tsup.config.ts
9
- CLI Target: es2022
10
- CLI Cleaning output folder
11
- ESM Build start
12
- ESM dist/index.js 2.26 KB
13
- ESM dist/index.js.map 5.64 KB
14
- ESM ⚡️ Build success in 17ms
15
- DTS Build start
16
- DTS ⚡️ Build success in 2711ms
17
- DTS dist/index.d.ts 2.06 KB
@@ -1,14 +0,0 @@
1
-
2
- > @nebutra/tts@0.2.1 test /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/integrations/tts
3
- > vitest run
4
-
5
-
6
- RUN v4.1.4 /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/integrations/tts
7
-
8
- ✓ src/__tests__/tts.test.ts (9 tests) 5ms
9
-
10
- Test Files 1 passed (1)
11
- Tests 9 passed (9)
12
- Start at 09:38:37
13
- Duration 463ms (transform 86ms, setup 0ms, import 110ms, tests 5ms, environment 0ms)
14
-
@@ -1,4 +0,0 @@
1
-
2
- > @nebutra/tts@0.2.2 typecheck /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/integrations/tts
3
- > tsc --noEmit
4
-
@@ -1,68 +0,0 @@
1
- import { afterEach, describe, expect, it } from "vitest";
2
- import { getTts, MockTtsProvider, resolveTtsProvider, TtsError } from "../index";
3
-
4
- const SAVED = { ...process.env };
5
- afterEach(() => {
6
- process.env = { ...SAVED };
7
- });
8
-
9
- describe("MockTtsProvider", () => {
10
- it("is deterministic: same text → same uri + duration", async () => {
11
- const p = new MockTtsProvider();
12
- const a = await p.synthesize({ text: "hello world", tenantId: "t1" });
13
- const b = await p.synthesize({ text: "hello world", tenantId: "t1" });
14
- expect(a).toEqual(b);
15
- expect(a.uri.startsWith("data:audio/")).toBe(true);
16
- expect(a.durationSec).toBeGreaterThan(0);
17
- });
18
-
19
- it("longer text → longer estimated duration", async () => {
20
- const p = new MockTtsProvider();
21
- const short = await p.synthesize({ text: "hi", tenantId: "t1" });
22
- const long = await p.synthesize({ text: "hi ".repeat(200), tenantId: "t1" });
23
- expect(long.durationSec).toBeGreaterThan(short.durationSec);
24
- });
25
-
26
- it("rejects empty text with a TtsError carrying a suggestion", async () => {
27
- const p = new MockTtsProvider();
28
- await expect(p.synthesize({ text: " ", tenantId: "t1" })).rejects.toBeInstanceOf(TtsError);
29
- try {
30
- await p.synthesize({ text: "", tenantId: "t1" });
31
- } catch (e) {
32
- expect((e as TtsError).suggestion.length).toBeGreaterThan(0);
33
- expect((e as TtsError).code).toBe("TTS_EMPTY_TEXT");
34
- }
35
- });
36
-
37
- it("health() reports ok", async () => {
38
- expect((await new MockTtsProvider().health()).ok).toBe(true);
39
- });
40
- });
41
-
42
- describe("resolveTtsProvider", () => {
43
- it("falls back to mock with no env", () => {
44
- expect(resolveTtsProvider()).toBe("mock");
45
- });
46
- it("detects elevenlabs then openai by env", () => {
47
- process.env.OPENAI_API_KEY = "x";
48
- expect(resolveTtsProvider()).toBe("openai");
49
- process.env.ELEVENLABS_API_KEY = "y";
50
- expect(resolveTtsProvider()).toBe("elevenlabs");
51
- });
52
- it("explicit TTS_PROVIDER wins", () => {
53
- process.env.ELEVENLABS_API_KEY = "y";
54
- process.env.TTS_PROVIDER = "mock";
55
- expect(resolveTtsProvider()).toBe("mock");
56
- });
57
- });
58
-
59
- describe("getTts", () => {
60
- it("zero-config returns a working mock provider", async () => {
61
- const tts = await getTts();
62
- const out = await tts.synthesize({ text: "narration line", tenantId: "org_1" });
63
- expect(out.uri.startsWith("data:audio/")).toBe(true);
64
- });
65
- it("throws TtsError for an unimplemented real provider (stub, fail-loud)", async () => {
66
- await expect(getTts({ provider: "elevenlabs" })).rejects.toBeInstanceOf(TtsError);
67
- });
68
- });
package/src/index.ts DELETED
@@ -1,117 +0,0 @@
1
- /**
2
- * @nebutra/tts — provider-agnostic narration synthesis.
3
- *
4
- * The video-pipeline gap (the absorbed source had none). Mirrors Sailor's
5
- * established provider pattern: a deterministic zero-config mock is the
6
- * active default; real adapters (ElevenLabs/OpenAI/Volces) are selected via
7
- * the shared `@nebutra/provider-factory` and fail loud until landed (stub
8
- * lifecycle — activate when a real caller arrives). Multi-tenant by request
9
- * (`tenantId` is mandatory); persistence is the caller's (`@nebutra/uploads`).
10
- */
11
-
12
- import { CapabilityError } from "@nebutra/capability-kit";
13
- import { envPresent, resolveProviderType } from "@nebutra/provider-factory";
14
-
15
- export class TtsError extends CapabilityError {
16
- constructor(message: string, init: { code: string; suggestion: string; cause?: unknown }) {
17
- super(message, init, {
18
- name: "TtsError",
19
- emptySuggestionFallback:
20
- "No suggestion was provided. This is a bug in @nebutra/tts — report it.",
21
- });
22
- }
23
- }
24
-
25
- export type TtsProviderType = "mock" | "elevenlabs" | "openai" | "volces";
26
-
27
- export interface SynthesizeRequest {
28
- readonly text: string;
29
- /** Owning tenant — mandatory; never inferred. */
30
- readonly tenantId: string;
31
- readonly voice?: string;
32
- }
33
-
34
- export interface SynthesizedAudio {
35
- readonly uri: string;
36
- readonly durationSec: number;
37
- readonly provider: TtsProviderType;
38
- }
39
-
40
- export interface TtsProvider {
41
- readonly name: TtsProviderType;
42
- synthesize(req: SynthesizeRequest): Promise<SynthesizedAudio>;
43
- health(): Promise<{ ok: boolean; detail: string }>;
44
- }
45
-
46
- function assertText(text: string): void {
47
- if (!text || text.trim().length === 0) {
48
- throw new TtsError("Cannot synthesize empty text.", {
49
- code: "TTS_EMPTY_TEXT",
50
- suggestion: "Pass a non-empty narration string.",
51
- });
52
- }
53
- }
54
-
55
- /** ~150 spoken words/min ≈ 2.5 words/s; deterministic estimate. */
56
- function estimateDurationSec(text: string): number {
57
- const words = text.trim().split(/\s+/).length;
58
- return Math.max(1, Math.round((words / 2.5) * 10) / 10);
59
- }
60
-
61
- /** Stable non-crypto hash for deterministic mock output. */
62
- function hash(s: string): string {
63
- let h = 2166136261;
64
- for (let i = 0; i < s.length; i++) {
65
- h ^= s.charCodeAt(i);
66
- h = Math.imul(h, 16777619);
67
- }
68
- return (h >>> 0).toString(36);
69
- }
70
-
71
- /** Zero-config deterministic default — real audio-free, but non-mock-typed. */
72
- export class MockTtsProvider implements TtsProvider {
73
- readonly name = "mock" as const;
74
-
75
- async synthesize(req: SynthesizeRequest): Promise<SynthesizedAudio> {
76
- assertText(req.text);
77
- const tag = hash(`${req.tenantId}:${req.voice ?? "default"}:${req.text}`);
78
- return {
79
- uri: `data:audio/x-nebutra-mock;base64,${Buffer.from(tag).toString("base64")}`,
80
- durationSec: estimateDurationSec(req.text),
81
- provider: "mock",
82
- };
83
- }
84
-
85
- async health(): Promise<{ ok: boolean; detail: string }> {
86
- return { ok: true, detail: "mock tts: deterministic, zero-config" };
87
- }
88
- }
89
-
90
- export function resolveTtsProvider(explicit?: TtsProviderType): TtsProviderType {
91
- return resolveProviderType<TtsProviderType>({
92
- explicit,
93
- envVarName: "TTS_PROVIDER",
94
- detectors: [
95
- { provider: "elevenlabs", when: envPresent("ELEVENLABS_API_KEY") },
96
- { provider: "openai", when: envPresent("OPENAI_API_KEY") },
97
- { provider: "volces", when: envPresent("VOLCES_TTS_TOKEN") },
98
- ],
99
- fallback: "mock",
100
- });
101
- }
102
-
103
- export interface TtsConfig {
104
- readonly provider?: TtsProviderType;
105
- }
106
-
107
- /** Resolve a TTS provider. Zero-config → mock; real adapters fail loud. */
108
- export async function getTts(config?: TtsConfig): Promise<TtsProvider> {
109
- const type = resolveTtsProvider(config?.provider);
110
- if (type === "mock") return new MockTtsProvider();
111
- throw new TtsError(`The "${type}" TTS provider is not implemented yet.`, {
112
- code: "TTS_PROVIDER_UNIMPLEMENTED",
113
- suggestion:
114
- `Implement the ${type} adapter (synthesize + health) and register it ` +
115
- `here, or unset its env var to use the zero-config mock provider.`,
116
- });
117
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src",
6
- "incremental": false
7
- },
8
- "include": ["src/**/*.ts"],
9
- "exclude": ["node_modules", "dist"]
10
- }
package/tsup.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from "tsup";
2
-
3
- export default defineConfig({
4
- entry: ["src/index.ts"],
5
- format: ["esm"],
6
- dts: true,
7
- clean: true,
8
- sourcemap: true,
9
- target: "es2022",
10
- });