@motebit/sdk 0.7.0 → 0.8.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 (66) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/appearance-config.test.d.ts +2 -0
  3. package/dist/__tests__/appearance-config.test.d.ts.map +1 -0
  4. package/dist/__tests__/appearance-config.test.js +104 -0
  5. package/dist/__tests__/appearance-config.test.js.map +1 -0
  6. package/dist/__tests__/branded-ids.test.d.ts +2 -0
  7. package/dist/__tests__/branded-ids.test.d.ts.map +1 -0
  8. package/dist/__tests__/branded-ids.test.js +70 -0
  9. package/dist/__tests__/branded-ids.test.js.map +1 -0
  10. package/dist/__tests__/index.test.d.ts +2 -0
  11. package/dist/__tests__/index.test.d.ts.map +1 -0
  12. package/dist/__tests__/index.test.js +194 -0
  13. package/dist/__tests__/index.test.js.map +1 -0
  14. package/dist/__tests__/provider-mode.test.d.ts +2 -0
  15. package/dist/__tests__/provider-mode.test.d.ts.map +1 -0
  16. package/dist/__tests__/provider-mode.test.js +180 -0
  17. package/dist/__tests__/provider-mode.test.js.map +1 -0
  18. package/dist/__tests__/provider-resolver.test.d.ts +2 -0
  19. package/dist/__tests__/provider-resolver.test.d.ts.map +1 -0
  20. package/dist/__tests__/provider-resolver.test.js +353 -0
  21. package/dist/__tests__/provider-resolver.test.js.map +1 -0
  22. package/dist/__tests__/voice-config.test.d.ts +2 -0
  23. package/dist/__tests__/voice-config.test.d.ts.map +1 -0
  24. package/dist/__tests__/voice-config.test.js +98 -0
  25. package/dist/__tests__/voice-config.test.js.map +1 -0
  26. package/dist/appearance-config.d.ts +55 -0
  27. package/dist/appearance-config.d.ts.map +1 -0
  28. package/dist/appearance-config.js +79 -0
  29. package/dist/appearance-config.js.map +1 -0
  30. package/dist/credential-types-doc.d.ts +73 -0
  31. package/dist/credential-types-doc.d.ts.map +1 -0
  32. package/dist/credential-types-doc.js +2 -0
  33. package/dist/credential-types-doc.js.map +1 -0
  34. package/dist/governance-config.d.ts +5 -0
  35. package/dist/governance-config.d.ts.map +1 -1
  36. package/dist/governance-config.js +8 -1
  37. package/dist/governance-config.js.map +1 -1
  38. package/dist/index.d.ts +55 -4
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +10 -4
  41. package/dist/index.js.map +1 -1
  42. package/dist/models.d.ts +44 -14
  43. package/dist/models.d.ts.map +1 -1
  44. package/dist/models.js +55 -18
  45. package/dist/models.js.map +1 -1
  46. package/dist/provider-mode.d.ts +109 -0
  47. package/dist/provider-mode.d.ts.map +1 -0
  48. package/dist/provider-mode.js +179 -0
  49. package/dist/provider-mode.js.map +1 -0
  50. package/dist/provider-resolver.d.ts +179 -0
  51. package/dist/provider-resolver.d.ts.map +1 -0
  52. package/dist/provider-resolver.js +224 -0
  53. package/dist/provider-resolver.js.map +1 -0
  54. package/dist/risk-labels.d.ts +22 -0
  55. package/dist/risk-labels.d.ts.map +1 -0
  56. package/dist/risk-labels.js +28 -0
  57. package/dist/risk-labels.js.map +1 -0
  58. package/dist/surface-options.d.ts +34 -0
  59. package/dist/surface-options.d.ts.map +1 -0
  60. package/dist/surface-options.js +36 -0
  61. package/dist/surface-options.js.map +1 -0
  62. package/dist/voice-config.d.ts +52 -0
  63. package/dist/voice-config.d.ts.map +1 -0
  64. package/dist/voice-config.js +61 -0
  65. package/dist/voice-config.js.map +1 -0
  66. package/package.json +7 -7
@@ -0,0 +1,353 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { resolveProviderSpec, normalizeLocalServerEndpoint, defaultModelForVendor, canonicalVendorBaseUrl, GOOGLE_OPENAI_COMPAT_URL, DEFAULT_MOTEBIT_CLOUD_URL, UnsupportedBackendError, DEFAULT_PROXY_MODEL, DEFAULT_ANTHROPIC_MODEL, DEFAULT_OPENAI_MODEL, DEFAULT_GOOGLE_MODEL, DEFAULT_OLLAMA_MODEL, } from "../index";
3
+ // === Test fixtures ===
4
+ /**
5
+ * A maximally permissive env: every backend supported, no URL substitution,
6
+ * no motebit-cloud overrides. Specific tests override individual fields to
7
+ * exercise the surface-specific behaviors.
8
+ */
9
+ function makeEnv(overrides = {}) {
10
+ return {
11
+ cloudBaseUrl: (_protocol, canonical) => canonical,
12
+ defaultLocalServerUrl: "http://127.0.0.1:11434",
13
+ supportedBackends: new Set(["webllm", "apple-fm", "mlx", "local-server"]),
14
+ ...overrides,
15
+ };
16
+ }
17
+ // === defaultModelForVendor ===
18
+ describe("defaultModelForVendor", () => {
19
+ it("returns the default model for each vendor", () => {
20
+ expect(defaultModelForVendor("anthropic")).toBe(DEFAULT_ANTHROPIC_MODEL);
21
+ expect(defaultModelForVendor("openai")).toBe(DEFAULT_OPENAI_MODEL);
22
+ expect(defaultModelForVendor("google")).toBe(DEFAULT_GOOGLE_MODEL);
23
+ });
24
+ });
25
+ // === canonicalVendorBaseUrl ===
26
+ describe("canonicalVendorBaseUrl", () => {
27
+ it("returns the canonical URL for each vendor", () => {
28
+ expect(canonicalVendorBaseUrl("anthropic")).toBe("https://api.anthropic.com");
29
+ expect(canonicalVendorBaseUrl("openai")).toBe("https://api.openai.com/v1");
30
+ expect(canonicalVendorBaseUrl("google")).toBe(GOOGLE_OPENAI_COMPAT_URL);
31
+ });
32
+ });
33
+ // === resolveProviderSpec — motebit-cloud mode ===
34
+ describe("resolveProviderSpec — motebit-cloud", () => {
35
+ it("returns a cloud spec speaking the anthropic protocol", () => {
36
+ const spec = resolveProviderSpec({ mode: "motebit-cloud", model: "claude-sonnet-4-6", proxyToken: "tok-xxx" }, makeEnv());
37
+ expect(spec.kind).toBe("cloud");
38
+ if (spec.kind === "cloud") {
39
+ expect(spec.wireProtocol).toBe("anthropic");
40
+ expect(spec.apiKey).toBe(""); // relay injects
41
+ expect(spec.model).toBe("claude-sonnet-4-6");
42
+ expect(spec.extraHeaders).toEqual({ "x-proxy-token": "tok-xxx" });
43
+ }
44
+ });
45
+ it("falls back to DEFAULT_PROXY_MODEL when no model is specified", () => {
46
+ const spec = resolveProviderSpec({ mode: "motebit-cloud" }, makeEnv());
47
+ if (spec.kind === "cloud")
48
+ expect(spec.model).toBe(DEFAULT_PROXY_MODEL);
49
+ });
50
+ it("uses the env's motebitCloudBaseUrl when config.baseUrl is absent", () => {
51
+ const spec = resolveProviderSpec({ mode: "motebit-cloud" }, makeEnv({ motebitCloudBaseUrl: "https://staging.motebit.com" }));
52
+ if (spec.kind === "cloud")
53
+ expect(spec.baseUrl).toBe("https://staging.motebit.com");
54
+ });
55
+ it("config.baseUrl takes precedence over env.motebitCloudBaseUrl", () => {
56
+ const spec = resolveProviderSpec({ mode: "motebit-cloud", baseUrl: "https://override.example.com" }, makeEnv({ motebitCloudBaseUrl: "https://staging.motebit.com" }));
57
+ if (spec.kind === "cloud")
58
+ expect(spec.baseUrl).toBe("https://override.example.com");
59
+ });
60
+ it("defaults baseUrl to DEFAULT_MOTEBIT_CLOUD_URL when both absent", () => {
61
+ const spec = resolveProviderSpec({ mode: "motebit-cloud" }, makeEnv());
62
+ if (spec.kind === "cloud")
63
+ expect(spec.baseUrl).toBe(DEFAULT_MOTEBIT_CLOUD_URL);
64
+ });
65
+ it("merges config proxyToken with env motebitCloudHeaders", () => {
66
+ const spec = resolveProviderSpec({ mode: "motebit-cloud", proxyToken: "tok-config" }, makeEnv({ motebitCloudHeaders: { "x-trace-id": "abc" } }));
67
+ if (spec.kind === "cloud") {
68
+ expect(spec.extraHeaders).toEqual({
69
+ "x-proxy-token": "tok-config",
70
+ "x-trace-id": "abc",
71
+ });
72
+ }
73
+ });
74
+ it("propagates env headers when no proxy token is set", () => {
75
+ const spec = resolveProviderSpec({ mode: "motebit-cloud" }, makeEnv({ motebitCloudHeaders: { "x-session": "s1" } }));
76
+ if (spec.kind === "cloud")
77
+ expect(spec.extraHeaders).toEqual({ "x-session": "s1" });
78
+ });
79
+ it("uses env.motebitCloudDefaultModel before falling back to DEFAULT_PROXY_MODEL", () => {
80
+ const spec = resolveProviderSpec({ mode: "motebit-cloud" }, makeEnv({ motebitCloudDefaultModel: "claude-haiku-4-5-20251001" }));
81
+ if (spec.kind === "cloud")
82
+ expect(spec.model).toBe("claude-haiku-4-5-20251001");
83
+ });
84
+ });
85
+ // === resolveProviderSpec — byok mode ===
86
+ describe("resolveProviderSpec — byok anthropic", () => {
87
+ it("returns a cloud spec speaking the anthropic protocol", () => {
88
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "anthropic", apiKey: "sk-ant-xxx", model: "claude-opus-4-6" }, makeEnv());
89
+ expect(spec.kind).toBe("cloud");
90
+ if (spec.kind === "cloud") {
91
+ expect(spec.wireProtocol).toBe("anthropic");
92
+ expect(spec.apiKey).toBe("sk-ant-xxx");
93
+ expect(spec.model).toBe("claude-opus-4-6");
94
+ expect(spec.baseUrl).toBe("https://api.anthropic.com");
95
+ }
96
+ });
97
+ it("falls back to DEFAULT_ANTHROPIC_MODEL", () => {
98
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "anthropic", apiKey: "sk-ant-xxx" }, makeEnv());
99
+ if (spec.kind === "cloud")
100
+ expect(spec.model).toBe(DEFAULT_ANTHROPIC_MODEL);
101
+ });
102
+ it("env.cloudBaseUrl can substitute the canonical URL (browser CORS proxy)", () => {
103
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "anthropic", apiKey: "sk-ant-xxx" }, makeEnv({
104
+ cloudBaseUrl: (proto, canonical) => proto === "anthropic" ? "https://proxy.example.com" : canonical,
105
+ }));
106
+ if (spec.kind === "cloud")
107
+ expect(spec.baseUrl).toBe("https://proxy.example.com");
108
+ });
109
+ it("user-supplied baseUrl wins over canonical", () => {
110
+ const spec = resolveProviderSpec({
111
+ mode: "byok",
112
+ vendor: "anthropic",
113
+ apiKey: "sk-ant-xxx",
114
+ baseUrl: "https://my-anthropic-proxy.example.com",
115
+ }, makeEnv());
116
+ if (spec.kind === "cloud") {
117
+ expect(spec.baseUrl).toBe("https://my-anthropic-proxy.example.com");
118
+ }
119
+ });
120
+ });
121
+ describe("resolveProviderSpec — byok openai", () => {
122
+ it("returns a cloud spec speaking the openai protocol", () => {
123
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "openai", apiKey: "sk-xxx", model: "gpt-5.4" }, makeEnv());
124
+ expect(spec.kind).toBe("cloud");
125
+ if (spec.kind === "cloud") {
126
+ expect(spec.wireProtocol).toBe("openai");
127
+ expect(spec.apiKey).toBe("sk-xxx");
128
+ expect(spec.baseUrl).toBe("https://api.openai.com/v1");
129
+ }
130
+ });
131
+ it("falls back to DEFAULT_OPENAI_MODEL", () => {
132
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "openai", apiKey: "sk-xxx" }, makeEnv());
133
+ if (spec.kind === "cloud")
134
+ expect(spec.model).toBe(DEFAULT_OPENAI_MODEL);
135
+ });
136
+ });
137
+ describe("resolveProviderSpec — byok google", () => {
138
+ it("dispatches Google as openai-compat at the Google endpoint", () => {
139
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "google", apiKey: "AIza-xxx", model: "gemini-2.5-pro" }, makeEnv());
140
+ expect(spec.kind).toBe("cloud");
141
+ if (spec.kind === "cloud") {
142
+ expect(spec.wireProtocol).toBe("openai");
143
+ expect(spec.apiKey).toBe("AIza-xxx");
144
+ expect(spec.baseUrl).toBe(GOOGLE_OPENAI_COMPAT_URL);
145
+ }
146
+ });
147
+ it("falls back to DEFAULT_GOOGLE_MODEL", () => {
148
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "google", apiKey: "AIza-xxx" }, makeEnv());
149
+ if (spec.kind === "cloud")
150
+ expect(spec.model).toBe(DEFAULT_GOOGLE_MODEL);
151
+ });
152
+ });
153
+ // === resolveProviderSpec — on-device mode ===
154
+ describe("resolveProviderSpec — on-device webllm", () => {
155
+ it("returns a webllm spec", () => {
156
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "webllm", model: "Phi-3.5" }, makeEnv());
157
+ expect(spec.kind).toBe("webllm");
158
+ if (spec.kind === "webllm")
159
+ expect(spec.model).toBe("Phi-3.5");
160
+ });
161
+ it("falls back to DEFAULT_WEBLLM_MODEL", () => {
162
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "webllm" }, makeEnv());
163
+ if (spec.kind === "webllm") {
164
+ expect(spec.model).toMatch(/Llama|Phi/);
165
+ }
166
+ });
167
+ it("throws when webllm is not supported on the surface", () => {
168
+ expect(() => resolveProviderSpec({ mode: "on-device", backend: "webllm" }, makeEnv({ supportedBackends: new Set(["local-server"]) }))).toThrow(UnsupportedBackendError);
169
+ });
170
+ });
171
+ describe("resolveProviderSpec — on-device apple-fm", () => {
172
+ it("returns an apple-fm spec", () => {
173
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "apple-fm" }, makeEnv());
174
+ expect(spec.kind).toBe("apple-fm");
175
+ });
176
+ it("throws when apple-fm is not supported (web/desktop/cli)", () => {
177
+ expect(() => resolveProviderSpec({ mode: "on-device", backend: "apple-fm" }, makeEnv({ supportedBackends: new Set(["local-server"]) }))).toThrow(UnsupportedBackendError);
178
+ });
179
+ });
180
+ describe("resolveProviderSpec — on-device mlx", () => {
181
+ it("returns an mlx spec", () => {
182
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "mlx", model: "mlx-llama-3-8b" }, makeEnv());
183
+ expect(spec.kind).toBe("mlx");
184
+ if (spec.kind === "mlx")
185
+ expect(spec.model).toBe("mlx-llama-3-8b");
186
+ });
187
+ });
188
+ describe("resolveProviderSpec — on-device local-server (always OpenAI-compat)", () => {
189
+ it("dispatches Ollama as OpenAI-compat with /v1 appended", () => {
190
+ const spec = resolveProviderSpec({
191
+ mode: "on-device",
192
+ backend: "local-server",
193
+ endpoint: "http://localhost:11434",
194
+ model: "llama3.2",
195
+ }, makeEnv());
196
+ expect(spec.kind).toBe("cloud");
197
+ if (spec.kind === "cloud") {
198
+ expect(spec.wireProtocol).toBe("openai");
199
+ expect(spec.baseUrl).toBe("http://localhost:11434/v1");
200
+ expect(spec.model).toBe("llama3.2");
201
+ expect(spec.apiKey).toBe("local"); // sentinel for local servers
202
+ }
203
+ });
204
+ it("dispatches LM Studio (port 1234) as OpenAI-compat with /v1", () => {
205
+ const spec = resolveProviderSpec({
206
+ mode: "on-device",
207
+ backend: "local-server",
208
+ endpoint: "http://localhost:1234",
209
+ model: "phi-3",
210
+ }, makeEnv());
211
+ expect(spec.kind).toBe("cloud");
212
+ if (spec.kind === "cloud") {
213
+ expect(spec.wireProtocol).toBe("openai");
214
+ expect(spec.baseUrl).toBe("http://localhost:1234/v1");
215
+ expect(spec.model).toBe("phi-3");
216
+ }
217
+ });
218
+ it("dispatches llama.cpp (port 8080) as OpenAI-compat", () => {
219
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "local-server", endpoint: "http://localhost:8080" }, makeEnv());
220
+ expect(spec.kind).toBe("cloud");
221
+ if (spec.kind === "cloud")
222
+ expect(spec.baseUrl).toBe("http://localhost:8080/v1");
223
+ });
224
+ it("dispatches Jan (port 1337) as OpenAI-compat", () => {
225
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "local-server", endpoint: "http://localhost:1337" }, makeEnv());
226
+ expect(spec.kind).toBe("cloud");
227
+ if (spec.kind === "cloud")
228
+ expect(spec.baseUrl).toBe("http://localhost:1337/v1");
229
+ });
230
+ it("does not double-append /v1 when the user already includes it", () => {
231
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "local-server", endpoint: "http://localhost:11434/v1" }, makeEnv());
232
+ if (spec.kind === "cloud")
233
+ expect(spec.baseUrl).toBe("http://localhost:11434/v1");
234
+ });
235
+ it("falls back to env.defaultLocalServerUrl when no endpoint specified", () => {
236
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "local-server" }, makeEnv({ defaultLocalServerUrl: "http://192.168.1.50:11434" }));
237
+ expect(spec.kind).toBe("cloud");
238
+ if (spec.kind === "cloud")
239
+ expect(spec.baseUrl).toBe("http://192.168.1.50:11434/v1");
240
+ });
241
+ it("preserves LAN endpoints (not just localhost)", () => {
242
+ const spec = resolveProviderSpec({
243
+ mode: "on-device",
244
+ backend: "local-server",
245
+ endpoint: "http://192.168.1.42:11434",
246
+ }, makeEnv());
247
+ if (spec.kind === "cloud") {
248
+ expect(spec.baseUrl).toBe("http://192.168.1.42:11434/v1");
249
+ }
250
+ });
251
+ it("falls back to DEFAULT_OLLAMA_MODEL when no model specified", () => {
252
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "local-server", endpoint: "http://localhost:11434" }, makeEnv());
253
+ if (spec.kind === "cloud")
254
+ expect(spec.model).toBe(DEFAULT_OLLAMA_MODEL);
255
+ });
256
+ it("env.localServerBaseUrl substitution applied before /v1 normalization", () => {
257
+ // Desktop dev-mode pattern: env rewrites the endpoint to a Vite proxy
258
+ // path. The resolver still appends /v1 to that path so the upstream
259
+ // proxy delivers requests to {ollama}/v1/chat/completions.
260
+ const spec = resolveProviderSpec({ mode: "on-device", backend: "local-server", model: "llama3.2" }, makeEnv({
261
+ defaultLocalServerUrl: "http://127.0.0.1:11434",
262
+ localServerBaseUrl: () => "/api/ollama",
263
+ }));
264
+ expect(spec.kind).toBe("cloud");
265
+ if (spec.kind === "cloud") {
266
+ expect(spec.wireProtocol).toBe("openai");
267
+ expect(spec.baseUrl).toBe("/api/ollama/v1");
268
+ expect(spec.model).toBe("llama3.2");
269
+ }
270
+ });
271
+ });
272
+ describe("normalizeLocalServerEndpoint", () => {
273
+ it("appends /v1 to a bare host", () => {
274
+ expect(normalizeLocalServerEndpoint("http://localhost:11434")).toBe("http://localhost:11434/v1");
275
+ });
276
+ it("strips trailing slash before appending", () => {
277
+ expect(normalizeLocalServerEndpoint("http://localhost:11434/")).toBe("http://localhost:11434/v1");
278
+ });
279
+ it("is idempotent — preserves existing /v1", () => {
280
+ expect(normalizeLocalServerEndpoint("http://localhost:11434/v1")).toBe("http://localhost:11434/v1");
281
+ expect(normalizeLocalServerEndpoint("http://localhost:11434/v1/")).toBe("http://localhost:11434/v1");
282
+ });
283
+ it("preserves longer /v1 paths", () => {
284
+ expect(normalizeLocalServerEndpoint("https://generativelanguage.googleapis.com/v1beta/openai")).toBe("https://generativelanguage.googleapis.com/v1beta/openai/v1");
285
+ // Note: Google's URL is special — surfaces should pass it untransformed,
286
+ // not through this normalizer. The normalizer is for local-server only.
287
+ });
288
+ it("works on dev proxy paths", () => {
289
+ expect(normalizeLocalServerEndpoint("/api/ollama")).toBe("/api/ollama/v1");
290
+ });
291
+ });
292
+ // === Realistic per-surface env scenarios (integration smoke tests) ===
293
+ describe("resolveProviderSpec — surface scenarios", () => {
294
+ it("web env: anthropic byok routes through CORS proxy", () => {
295
+ const webEnv = {
296
+ cloudBaseUrl: (proto, canonical) => proto === "anthropic" ? "https://api.motebit.com" : canonical,
297
+ defaultLocalServerUrl: "http://127.0.0.1:11434",
298
+ supportedBackends: new Set(["webllm", "local-server"]),
299
+ };
300
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "anthropic", apiKey: "sk-ant-xxx" }, webEnv);
301
+ if (spec.kind === "cloud") {
302
+ expect(spec.baseUrl).toBe("https://api.motebit.com");
303
+ }
304
+ });
305
+ it("desktop dev env: anthropic byok goes through Vite proxy path", () => {
306
+ const desktopDevEnv = {
307
+ cloudBaseUrl: (proto) => (proto === "anthropic" ? "/api/anthropic" : "/api/openai"),
308
+ defaultLocalServerUrl: "/api/ollama",
309
+ supportedBackends: new Set(["local-server"]),
310
+ };
311
+ const spec = resolveProviderSpec({ mode: "byok", vendor: "anthropic", apiKey: "sk-ant-xxx" }, desktopDevEnv);
312
+ if (spec.kind === "cloud")
313
+ expect(spec.baseUrl).toBe("/api/anthropic");
314
+ });
315
+ it("mobile env: apple-fm allowed, webllm blocked", () => {
316
+ const mobileEnv = {
317
+ cloudBaseUrl: (_, canonical) => canonical,
318
+ defaultLocalServerUrl: "http://127.0.0.1:11434",
319
+ supportedBackends: new Set(["apple-fm", "mlx", "local-server"]),
320
+ };
321
+ expect(resolveProviderSpec({ mode: "on-device", backend: "apple-fm" }, mobileEnv).kind).toBe("apple-fm");
322
+ expect(() => resolveProviderSpec({ mode: "on-device", backend: "webllm" }, mobileEnv)).toThrow(UnsupportedBackendError);
323
+ });
324
+ it("cli env: only local-server backend, no on-device native", () => {
325
+ const cliEnv = {
326
+ cloudBaseUrl: (_, canonical) => canonical,
327
+ defaultLocalServerUrl: "http://127.0.0.1:11434",
328
+ supportedBackends: new Set(["local-server"]),
329
+ };
330
+ expect(() => resolveProviderSpec({ mode: "on-device", backend: "apple-fm" }, cliEnv)).toThrow(UnsupportedBackendError);
331
+ expect(() => resolveProviderSpec({ mode: "on-device", backend: "webllm" }, cliEnv)).toThrow(UnsupportedBackendError);
332
+ expect(resolveProviderSpec({ mode: "on-device", backend: "local-server", endpoint: "http://localhost:11434" }, cliEnv).kind).toBe("cloud");
333
+ });
334
+ });
335
+ // === Type-level invariants (compile-time) ===
336
+ describe("type invariants", () => {
337
+ it("UnifiedProviderConfig accepts every combination", () => {
338
+ // This is a compile-time check; the runtime body is just a sanity assertion.
339
+ const configs = [
340
+ { mode: "motebit-cloud" },
341
+ { mode: "motebit-cloud", model: "x", proxyToken: "y" },
342
+ { mode: "byok", vendor: "anthropic", apiKey: "k" },
343
+ { mode: "byok", vendor: "openai", apiKey: "k" },
344
+ { mode: "byok", vendor: "google", apiKey: "k" },
345
+ { mode: "on-device", backend: "webllm" },
346
+ { mode: "on-device", backend: "apple-fm" },
347
+ { mode: "on-device", backend: "mlx" },
348
+ { mode: "on-device", backend: "local-server" },
349
+ ];
350
+ expect(configs.length).toBe(9);
351
+ });
352
+ });
353
+ //# sourceMappingURL=provider-resolver.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-resolver.test.js","sourceRoot":"","sources":["../../src/__tests__/provider-resolver.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=voice-config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/voice-config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { DEFAULT_VOICE_CONFIG, migrateVoiceConfig } from "../voice-config.js";
3
+ describe("migrateVoiceConfig", () => {
4
+ it("returns a fresh default when input is null", () => {
5
+ const result = migrateVoiceConfig(null);
6
+ expect(result).toEqual(DEFAULT_VOICE_CONFIG);
7
+ // fresh copy, not the frozen default reference
8
+ expect(result).not.toBe(DEFAULT_VOICE_CONFIG);
9
+ });
10
+ it("returns a fresh default when input is undefined", () => {
11
+ expect(migrateVoiceConfig(undefined)).toEqual(DEFAULT_VOICE_CONFIG);
12
+ });
13
+ it("returns a fresh default when input is a primitive", () => {
14
+ expect(migrateVoiceConfig("not an object")).toEqual(DEFAULT_VOICE_CONFIG);
15
+ expect(migrateVoiceConfig(42)).toEqual(DEFAULT_VOICE_CONFIG);
16
+ expect(migrateVoiceConfig(true)).toEqual(DEFAULT_VOICE_CONFIG);
17
+ });
18
+ it("normalizes the canonical shape as-is", () => {
19
+ const input = {
20
+ enabled: true,
21
+ autoSend: false,
22
+ speakResponses: true,
23
+ ttsVoice: "nova",
24
+ neuralVad: false,
25
+ };
26
+ expect(migrateVoiceConfig(input)).toEqual(input);
27
+ });
28
+ it("migrates the web legacy shape {ttsVoice, autoSend, voiceResponse}", () => {
29
+ const result = migrateVoiceConfig({
30
+ ttsVoice: "echo",
31
+ autoSend: false,
32
+ voiceResponse: false,
33
+ });
34
+ expect(result.ttsVoice).toBe("echo");
35
+ expect(result.autoSend).toBe(false);
36
+ expect(result.speakResponses).toBe(false);
37
+ // `enabled` and `neuralVad` absent → defaults
38
+ expect(result.enabled).toBe(DEFAULT_VOICE_CONFIG.enabled);
39
+ });
40
+ it("migrates the mobile legacy shape with voiceEnabled + voiceAutoSend + voiceResponseEnabled + neuralVadEnabled", () => {
41
+ const result = migrateVoiceConfig({
42
+ voiceEnabled: true,
43
+ voiceAutoSend: true,
44
+ voiceResponseEnabled: true,
45
+ neuralVadEnabled: false,
46
+ ttsVoice: "fable",
47
+ });
48
+ expect(result).toEqual({
49
+ enabled: true,
50
+ autoSend: true,
51
+ speakResponses: true,
52
+ ttsVoice: "fable",
53
+ neuralVad: false,
54
+ });
55
+ });
56
+ it("migrates the desktop legacy shape {ttsVoice, voiceAutoSend, voiceResponseEnabled}", () => {
57
+ const result = migrateVoiceConfig({
58
+ ttsVoice: "onyx",
59
+ voiceAutoSend: false,
60
+ voiceResponseEnabled: true,
61
+ });
62
+ expect(result.ttsVoice).toBe("onyx");
63
+ expect(result.autoSend).toBe(false);
64
+ expect(result.speakResponses).toBe(true);
65
+ });
66
+ it("prefers the canonical key when both canonical and legacy keys are present", () => {
67
+ const result = migrateVoiceConfig({
68
+ enabled: true,
69
+ voiceEnabled: false,
70
+ speakResponses: true,
71
+ voiceResponseEnabled: false,
72
+ });
73
+ expect(result.enabled).toBe(true);
74
+ expect(result.speakResponses).toBe(true);
75
+ });
76
+ it("falls back to default when a field has the wrong type", () => {
77
+ const result = migrateVoiceConfig({
78
+ enabled: "yes", // wrong type
79
+ ttsVoice: 42, // wrong type
80
+ });
81
+ expect(result.enabled).toBe(DEFAULT_VOICE_CONFIG.enabled);
82
+ expect(result.ttsVoice).toBe(DEFAULT_VOICE_CONFIG.ttsVoice);
83
+ });
84
+ it("leaves neuralVad undefined when not provided (opt-in)", () => {
85
+ const result = migrateVoiceConfig({ enabled: true });
86
+ expect(result.neuralVad).toBeUndefined();
87
+ });
88
+ it("ignores unknown keys", () => {
89
+ const result = migrateVoiceConfig({
90
+ enabled: true,
91
+ foo: "bar",
92
+ baz: [1, 2, 3],
93
+ });
94
+ expect(result).not.toHaveProperty("foo");
95
+ expect(result).not.toHaveProperty("baz");
96
+ });
97
+ });
98
+ //# sourceMappingURL=voice-config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-config.test.js","sourceRoot":"","sources":["../../src/__tests__/voice-config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Canonical appearance / theme configuration shape.
3
+ *
4
+ * Every surface (web, mobile, desktop, spatial) has historically carried its
5
+ * own appearance config — with drifted field names (`preset` vs
6
+ * `colorPreset`, web's `SoulColorConfig` vs mobile's flat fields vs
7
+ * desktop's Tauri snake_case `interior_color_preset` + `custom_soul_color`)
8
+ * and different subsets of the feature set. This module is the
9
+ * authoritative vocabulary. Surfaces may keep UI-internal state in their
10
+ * own shapes, but anything crossing the SDK boundary — sync, import/export,
11
+ * cross-surface helpers — speaks `AppearanceConfig`.
12
+ *
13
+ * Migration helpers are provided for the legacy shapes so each surface can
14
+ * normalize on load without inventing its own migration one-offs.
15
+ */
16
+ /**
17
+ * The canonical appearance configuration. Narrow, descriptive, surface-agnostic.
18
+ *
19
+ * - `colorPreset`: opaque preset identifier — the specific string space depends
20
+ * on the surface (`"moonlight"`, `"amber"`, `"rose"`, …) plus the special
21
+ * value `"custom"` which means "render from `customHue` + `customSaturation`".
22
+ * - `customHue`: 0-360, only meaningful when `colorPreset === "custom"`.
23
+ * - `customSaturation`: 0-1, only meaningful when `colorPreset === "custom"`.
24
+ * - `theme`: master light/dark/system theme. Optional because some surfaces
25
+ * (web, spatial) derive it from the OS without exposing a setting.
26
+ */
27
+ export interface AppearanceConfig {
28
+ colorPreset: string;
29
+ customHue?: number;
30
+ customSaturation?: number;
31
+ theme?: "light" | "dark" | "system";
32
+ }
33
+ /** Default appearance — moonlight preset, no custom override, system theme. */
34
+ export declare const DEFAULT_APPEARANCE_CONFIG: AppearanceConfig;
35
+ /**
36
+ * Normalize any of the historical surface-specific appearance shapes onto
37
+ * the canonical `AppearanceConfig`. Unknown fields are ignored. Missing
38
+ * fields fall back to `DEFAULT_APPEARANCE_CONFIG`.
39
+ *
40
+ * Accepted legacy keys:
41
+ * - web: `{preset, customHue?, customSaturation?}` (the field is
42
+ * `preset`, not `colorPreset`, in `SoulColorConfig`).
43
+ * - mobile: `{colorPreset, customHue, customSaturation, theme}` flat
44
+ * on `MobileSettings`.
45
+ * - desktop: `{interior_color_preset, custom_soul_color: {hue, saturation}}`
46
+ * snake_case in the Tauri JSON config.
47
+ * - spatial: `{colorPreset, customHue, customSaturation}` flat on
48
+ * `SpatialSettings`.
49
+ *
50
+ * The function is intentionally defensive — it operates on `unknown` because
51
+ * the typical caller is reading from `localStorage` / `AsyncStorage` / a
52
+ * Tauri JSON config, all of which return untyped blobs.
53
+ */
54
+ export declare function migrateAppearanceConfig(raw: unknown): AppearanceConfig;
55
+ //# sourceMappingURL=appearance-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appearance-config.d.ts","sourceRoot":"","sources":["../src/appearance-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;CACrC;AAED,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAwCtE"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Canonical appearance / theme configuration shape.
3
+ *
4
+ * Every surface (web, mobile, desktop, spatial) has historically carried its
5
+ * own appearance config — with drifted field names (`preset` vs
6
+ * `colorPreset`, web's `SoulColorConfig` vs mobile's flat fields vs
7
+ * desktop's Tauri snake_case `interior_color_preset` + `custom_soul_color`)
8
+ * and different subsets of the feature set. This module is the
9
+ * authoritative vocabulary. Surfaces may keep UI-internal state in their
10
+ * own shapes, but anything crossing the SDK boundary — sync, import/export,
11
+ * cross-surface helpers — speaks `AppearanceConfig`.
12
+ *
13
+ * Migration helpers are provided for the legacy shapes so each surface can
14
+ * normalize on load without inventing its own migration one-offs.
15
+ */
16
+ /** Default appearance — moonlight preset, no custom override, system theme. */
17
+ export const DEFAULT_APPEARANCE_CONFIG = {
18
+ colorPreset: "moonlight",
19
+ customHue: 220,
20
+ customSaturation: 0.7,
21
+ theme: "system",
22
+ };
23
+ /**
24
+ * Normalize any of the historical surface-specific appearance shapes onto
25
+ * the canonical `AppearanceConfig`. Unknown fields are ignored. Missing
26
+ * fields fall back to `DEFAULT_APPEARANCE_CONFIG`.
27
+ *
28
+ * Accepted legacy keys:
29
+ * - web: `{preset, customHue?, customSaturation?}` (the field is
30
+ * `preset`, not `colorPreset`, in `SoulColorConfig`).
31
+ * - mobile: `{colorPreset, customHue, customSaturation, theme}` flat
32
+ * on `MobileSettings`.
33
+ * - desktop: `{interior_color_preset, custom_soul_color: {hue, saturation}}`
34
+ * snake_case in the Tauri JSON config.
35
+ * - spatial: `{colorPreset, customHue, customSaturation}` flat on
36
+ * `SpatialSettings`.
37
+ *
38
+ * The function is intentionally defensive — it operates on `unknown` because
39
+ * the typical caller is reading from `localStorage` / `AsyncStorage` / a
40
+ * Tauri JSON config, all of which return untyped blobs.
41
+ */
42
+ export function migrateAppearanceConfig(raw) {
43
+ if (raw == null || typeof raw !== "object")
44
+ return { ...DEFAULT_APPEARANCE_CONFIG };
45
+ const obj = raw;
46
+ const isStr = (v) => typeof v === "string";
47
+ const isNum = (v) => typeof v === "number";
48
+ // Color preset: prefer canonical `colorPreset`, fall back to web's
49
+ // legacy `preset`, then desktop's snake_case `interior_color_preset`.
50
+ const colorPreset = (isStr(obj.colorPreset) ? obj.colorPreset : undefined) ??
51
+ (isStr(obj.preset) ? obj.preset : undefined) ??
52
+ (isStr(obj.interior_color_preset) ? obj.interior_color_preset : undefined) ??
53
+ DEFAULT_APPEARANCE_CONFIG.colorPreset;
54
+ // Custom hue/saturation: canonical first, then desktop's nested
55
+ // `custom_soul_color: {hue, saturation}` shape.
56
+ let customHue;
57
+ let customSaturation;
58
+ if (isNum(obj.customHue))
59
+ customHue = obj.customHue;
60
+ if (isNum(obj.customSaturation))
61
+ customSaturation = obj.customSaturation;
62
+ const desktopCustom = obj.custom_soul_color;
63
+ if (customHue === undefined && desktopCustom != null && isNum(desktopCustom.hue)) {
64
+ customHue = desktopCustom.hue;
65
+ }
66
+ if (customSaturation === undefined && desktopCustom != null && isNum(desktopCustom.saturation)) {
67
+ customSaturation = desktopCustom.saturation;
68
+ }
69
+ // Theme: canonical key only — no surface uses an alternative name.
70
+ const themeRaw = obj.theme;
71
+ const theme = themeRaw === "light" || themeRaw === "dark" || themeRaw === "system" ? themeRaw : undefined;
72
+ return {
73
+ colorPreset,
74
+ customHue: customHue ?? DEFAULT_APPEARANCE_CONFIG.customHue,
75
+ customSaturation: customSaturation ?? DEFAULT_APPEARANCE_CONFIG.customSaturation,
76
+ theme: theme ?? DEFAULT_APPEARANCE_CONFIG.theme,
77
+ };
78
+ }
79
+ //# sourceMappingURL=appearance-config.js.map