@fonoster/common 0.27.0 → 0.31.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 (209) hide show
  1. package/dist/{voice/PlayDtmf.d.ts → agents/index.d.ts} +3 -5
  2. package/dist/{assistants → agents}/index.js +2 -6
  3. package/dist/agents/sessionAgentSchema.d.ts +755 -0
  4. package/dist/agents/sessionAgentSchema.js +117 -0
  5. package/dist/{voice/Dial.d.ts → calls/index.d.ts} +16 -13
  6. package/dist/calls/index.js +44 -0
  7. package/dist/cellsecrets/index.d.ts +91 -0
  8. package/dist/cellsecrets/index.js +299 -0
  9. package/dist/config/cli/check.d.ts +6 -0
  10. package/dist/config/cli/check.js +202 -0
  11. package/dist/config/cli/envFile.d.ts +23 -0
  12. package/dist/config/cli/envFile.js +81 -0
  13. package/dist/config/cli/exposure.d.ts +10 -0
  14. package/dist/config/cli/exposure.js +93 -0
  15. package/dist/config/cli/index.d.ts +2 -0
  16. package/dist/{identity/tokenHasAccessKeyId.js → config/cli/index.js} +7 -8
  17. package/dist/config/cli/info.d.ts +6 -0
  18. package/dist/config/cli/info.js +62 -0
  19. package/dist/config/cli/init.d.ts +9 -0
  20. package/dist/config/cli/init.js +152 -0
  21. package/dist/config/cli/main.d.ts +11 -0
  22. package/dist/config/cli/main.js +155 -0
  23. package/dist/config/cli/render.d.ts +44 -0
  24. package/dist/config/cli/render.js +129 -0
  25. package/dist/config/cli/secrets.d.ts +21 -0
  26. package/dist/config/cli/secrets.js +117 -0
  27. package/dist/config/cli/types.d.ts +24 -0
  28. package/dist/config/cli/types.js +7 -0
  29. package/dist/{assistants/index.d.ts → config/configHints.d.ts} +8 -7
  30. package/dist/config/configHints.js +47 -0
  31. package/dist/{voice → config}/index.d.ts +11 -13
  32. package/dist/{voice → config}/index.js +11 -13
  33. package/dist/config/interpolateEnv.d.ts +34 -0
  34. package/dist/config/interpolateEnv.js +70 -0
  35. package/dist/config/loadServiceConfig.d.ts +28 -0
  36. package/dist/config/loadServiceConfig.js +112 -0
  37. package/dist/config/logsSchema.d.ts +76 -0
  38. package/dist/config/logsSchema.js +49 -0
  39. package/dist/config/natsConnection.d.ts +53 -0
  40. package/dist/config/natsConnection.js +58 -0
  41. package/dist/config/parseConfigFile.d.ts +7 -0
  42. package/dist/config/parseConfigFile.js +60 -0
  43. package/dist/config/resolveConfigFile.d.ts +17 -0
  44. package/dist/config/resolveConfigFile.js +54 -0
  45. package/dist/config/resolveSecretRefs.d.ts +35 -0
  46. package/dist/config/resolveSecretRefs.js +129 -0
  47. package/dist/{assistants/audioFiltersSchema.d.ts → config/strictDeep.d.ts} +9 -12
  48. package/dist/config/strictDeep.js +58 -0
  49. package/dist/config/telemetrySchema.d.ts +39 -0
  50. package/dist/config/telemetrySchema.js +44 -0
  51. package/dist/config/toJsonSchema.d.ts +30 -0
  52. package/dist/config/toJsonSchema.js +161 -0
  53. package/dist/constants.d.ts +1 -6
  54. package/dist/constants.js +4 -7
  55. package/dist/envs.d.ts +2 -1
  56. package/dist/envs.js +13 -11
  57. package/dist/errors/handleZodError.js +3 -1
  58. package/dist/filters/index.d.ts +70 -0
  59. package/dist/filters/index.js +101 -0
  60. package/dist/identity/createAuthInterceptor.d.ts +9 -2
  61. package/dist/identity/createAuthInterceptor.js +36 -30
  62. package/dist/identity/hasAccess.d.ts +9 -3
  63. package/dist/identity/hasAccess.js +32 -8
  64. package/dist/identity/index.d.ts +1 -0
  65. package/dist/identity/index.js +1 -0
  66. package/dist/identity/isValidToken.d.ts +12 -2
  67. package/dist/identity/isValidToken.js +25 -9
  68. package/dist/identity/roles.js +33 -7
  69. package/dist/identity/types.d.ts +6 -1
  70. package/dist/identity/types.js +1 -0
  71. package/dist/{assistants/LanguageModelProvider.js → identity/verifyAccessToken.d.ts} +10 -11
  72. package/dist/identity/verifyAccessToken.js +44 -0
  73. package/dist/index.d.ts +15 -2
  74. package/dist/index.js +18 -3
  75. package/dist/net/assertPublicDestination.d.ts +52 -0
  76. package/dist/net/assertPublicDestination.js +342 -0
  77. package/dist/{voice/StopSay.d.ts → net/index.d.ts} +1 -6
  78. package/dist/net/index.js +35 -0
  79. package/dist/notifications/createSendEmail.js +3 -1
  80. package/dist/protos/applications.proto +138 -3
  81. package/dist/protos/authz.proto +26 -4
  82. package/dist/protos/billing.proto +92 -0
  83. package/dist/protos/calls.proto +4 -0
  84. package/dist/protos/capabilities.proto +102 -0
  85. package/dist/protos/identity.proto +26 -1
  86. package/dist/protos/providers.proto +139 -0
  87. package/dist/protos/roomr.proto +146 -0
  88. package/dist/protos/session.proto +194 -0
  89. package/dist/protos/usage.proto +59 -0
  90. package/dist/protos/webhooks.proto +140 -0
  91. package/dist/{utils/types.d.ts → regions/hub/index.d.ts} +1 -5
  92. package/dist/regions/hub/index.js +35 -0
  93. package/dist/regions/hub/natsJwt.d.ts +63 -0
  94. package/dist/regions/hub/natsJwt.js +195 -0
  95. package/dist/registry/Registry.d.ts +87 -0
  96. package/dist/registry/Registry.js +432 -0
  97. package/dist/registry/cli.d.ts +20 -0
  98. package/dist/registry/cli.js +436 -0
  99. package/dist/registry/features.d.ts +31 -0
  100. package/dist/registry/features.js +38 -0
  101. package/dist/registry/index.d.ts +24 -0
  102. package/dist/registry/index.js +40 -0
  103. package/dist/registry/prisma.d.ts +67 -0
  104. package/dist/registry/prisma.js +399 -0
  105. package/dist/registry/stores.d.ts +21 -0
  106. package/dist/{assistants/languageModelConfigSchema.js → registry/stores.js} +55 -38
  107. package/dist/registry/types.d.ts +110 -0
  108. package/dist/{assistants/LanguageModelProvider.d.ts → registry/types.js} +6 -8
  109. package/dist/registry/workspaceKeys.d.ts +41 -0
  110. package/dist/registry/workspaceKeys.js +64 -0
  111. package/dist/roomr/health.d.ts +9 -0
  112. package/dist/{assistants/eventsHookSchema.js → roomr/health.js} +22 -18
  113. package/dist/roomr/index.d.ts +75 -0
  114. package/dist/roomr/index.js +146 -0
  115. package/dist/roomr/session.d.ts +133 -0
  116. package/dist/roomr/session.js +132 -0
  117. package/dist/session/index.d.ts +19 -0
  118. package/dist/session/index.js +35 -0
  119. package/dist/session/schema.d.ts +2140 -0
  120. package/dist/session/schema.js +448 -0
  121. package/dist/sessions/index.d.ts +96 -0
  122. package/dist/sessions/index.js +197 -0
  123. package/dist/telemetry.d.ts +76 -0
  124. package/dist/telemetry.js +360 -0
  125. package/dist/types.d.ts +3 -5
  126. package/dist/{voice/Gather.d.ts → usage/CallUsage.d.ts} +19 -18
  127. package/dist/usage/CallUsage.js +76 -0
  128. package/dist/usage/index.d.ts +22 -0
  129. package/dist/usage/index.js +38 -0
  130. package/dist/usage/meters.d.ts +113 -0
  131. package/dist/usage/meters.js +90 -0
  132. package/dist/usage/types.d.ts +159 -0
  133. package/dist/usage/types.js +84 -0
  134. package/dist/usage/usageId.d.ts +6 -0
  135. package/dist/usage/usageId.js +45 -0
  136. package/dist/utils/{findIntegrationsCredentials.js → WriteBehind.d.ts} +13 -10
  137. package/dist/utils/WriteBehind.js +64 -0
  138. package/dist/utils/index.d.ts +1 -3
  139. package/dist/utils/index.js +1 -3
  140. package/dist/utils/{createFetchSingleCallByCallId.d.ts → logUnhandledRejections.d.ts} +8 -4
  141. package/dist/{assistants/audioFiltersSchema.js → utils/logUnhandledRejections.js} +17 -12
  142. package/dist/utils/withErrorHandling.js +1 -1
  143. package/dist/validators/calls.d.ts +3 -0
  144. package/dist/validators/calls.js +12 -2
  145. package/dist/validators/identity.d.ts +37 -8
  146. package/dist/validators/identity.js +22 -3
  147. package/dist/validators/index.d.ts +1 -0
  148. package/dist/validators/index.js +1 -0
  149. package/dist/validators/sip.d.ts +40 -0
  150. package/dist/validators/sip.js +99 -0
  151. package/dist/validators/sipnet/credentials.d.ts +2 -2
  152. package/dist/validators/sipnet/domains.js +7 -7
  153. package/dist/validators/sipnet/trunks.d.ts +14 -14
  154. package/dist/validators/sipnet/trunks.js +4 -2
  155. package/dist/webhooks/Dispatcher.d.ts +87 -0
  156. package/dist/webhooks/Dispatcher.js +225 -0
  157. package/dist/webhooks/bus.d.ts +68 -0
  158. package/dist/webhooks/bus.js +178 -0
  159. package/dist/webhooks/events.d.ts +66 -0
  160. package/dist/webhooks/events.js +62 -0
  161. package/dist/webhooks/index.d.ts +23 -0
  162. package/dist/webhooks/index.js +39 -0
  163. package/dist/webhooks/prisma.d.ts +55 -0
  164. package/dist/webhooks/prisma.js +242 -0
  165. package/dist/webhooks/signing.d.ts +8 -0
  166. package/dist/webhooks/signing.js +53 -0
  167. package/package.json +25 -11
  168. package/schema/session.v1.json +871 -0
  169. package/dist/assistants/assistantSchema.d.ts +0 -739
  170. package/dist/assistants/assistantSchema.js +0 -37
  171. package/dist/assistants/conversationSettingsSchema.d.ts +0 -122
  172. package/dist/assistants/conversationSettingsSchema.js +0 -133
  173. package/dist/assistants/eventsHookSchema.d.ts +0 -38
  174. package/dist/assistants/languageModelConfigSchema.d.ts +0 -206
  175. package/dist/identity/tokenHasAccessKeyId.d.ts +0 -2
  176. package/dist/protos/voice.proto +0 -557
  177. package/dist/utils/createFetchSingleCallByCallId.js +0 -33
  178. package/dist/utils/findIntegrationsCredentials.d.ts +0 -23
  179. package/dist/utils/types.js +0 -2
  180. package/dist/voice/Dial.js +0 -20
  181. package/dist/voice/Gather.js +0 -9
  182. package/dist/voice/Mute.d.ts +0 -31
  183. package/dist/voice/Mute.js +0 -9
  184. package/dist/voice/Play.d.ts +0 -30
  185. package/dist/voice/Play.js +0 -2
  186. package/dist/voice/PlayDtmf.js +0 -2
  187. package/dist/voice/PlaybackControl.d.ts +0 -31
  188. package/dist/voice/PlaybackControl.js +0 -11
  189. package/dist/voice/Record.d.ts +0 -36
  190. package/dist/voice/Record.js +0 -7
  191. package/dist/voice/Say.d.ts +0 -28
  192. package/dist/voice/Say.js +0 -2
  193. package/dist/voice/StopSay.js +0 -2
  194. package/dist/voice/Stream.d.ts +0 -53
  195. package/dist/voice/Stream.js +0 -19
  196. package/dist/voice/StreamGather.d.ts +0 -34
  197. package/dist/voice/StreamGather.js +0 -9
  198. package/dist/voice/Verb.d.ts +0 -27
  199. package/dist/voice/Verb.js +0 -2
  200. package/dist/voice/voice.d.ts +0 -171
  201. package/dist/voice/voice.js +0 -65
  202. package/dist/{assistants → agents}/testCasesSchema.d.ts +4 -4
  203. /package/dist/{assistants → agents}/testCasesSchema.js +0 -0
  204. /package/dist/{assistants → agents}/tools/index.d.ts +0 -0
  205. /package/dist/{assistants → agents}/tools/index.js +0 -0
  206. /package/dist/{assistants → agents}/tools/propertySchema.d.ts +0 -0
  207. /package/dist/{assistants → agents}/tools/propertySchema.js +0 -0
  208. /package/dist/{assistants → agents}/tools/toolSchema.d.ts +0 -0
  209. /package/dist/{assistants → agents}/tools/toolSchema.js +0 -0
@@ -0,0 +1,87 @@
1
+ import { Capability, CatalogEntry, ItemKind, ModelEntry, ProviderEndpoints, ProviderEntry, RegistryStore, Usage, UsageSource, VoiceEntry } from "./types";
2
+ /** Adapters that ship with this version (add-provider-registry, P2). */
3
+ export declare const BUILT_IN_ADAPTERS: string[];
4
+ export declare class RegistryError extends Error {
5
+ }
6
+ export type ImpactLine = {
7
+ mark: "✓" | "✗" | "~";
8
+ text: string;
9
+ count: number;
10
+ };
11
+ export type Impact = {
12
+ lines: ImpactLine[];
13
+ mappable: Usage[];
14
+ unmappable: Usage[];
15
+ };
16
+ export type RegistryOptions = {
17
+ store: RegistryStore;
18
+ usages?: UsageSource;
19
+ actor?: string;
20
+ now?: () => Date;
21
+ /** The command line that made the change, stored in every audit entry (spec "Every write is audited"). */
22
+ commandLine?: string;
23
+ };
24
+ /** The registry operations behind the `fonoster-registry` utility. */
25
+ export declare class Registry {
26
+ private readonly options;
27
+ private readonly usages;
28
+ constructor(options: RegistryOptions);
29
+ private get store();
30
+ private audit;
31
+ /** Rejects anything that looks like a credential (the catalog never holds a secret). */
32
+ private assertNoSecrets;
33
+ /**
34
+ * A provider names a built-in adapter, a credentialRef name when it needs a key (none:
35
+ * keyless), and plain endpoint URLs.
36
+ */
37
+ private assertProvider;
38
+ private assertNew;
39
+ private require;
40
+ addProvider(input: {
41
+ id: string;
42
+ adapter: string;
43
+ capabilities: Capability[];
44
+ /** Omitted: a keyless provider (no key needed). */
45
+ credentialRef?: string;
46
+ endpoints?: ProviderEndpoints;
47
+ label?: string;
48
+ } & Record<string, unknown>): ProviderEntry;
49
+ addModel(input: {
50
+ id: string;
51
+ type: Capability;
52
+ features?: string[];
53
+ languages?: string[];
54
+ settings?: string[];
55
+ label?: string;
56
+ } & Record<string, unknown>): ModelEntry;
57
+ addVoice(input: {
58
+ id: string;
59
+ models: string[];
60
+ language?: string;
61
+ gender?: string;
62
+ ownerWorkspace?: string;
63
+ label?: string;
64
+ } & Record<string, unknown>): VoiceEntry;
65
+ private requireProviderFor;
66
+ /** What moving the usages of `id` to `replacement` would do (shared by deprecate and retire). */
67
+ impact(id: string, replacement: string, voiceMap?: Record<string, string>): Impact;
68
+ deprecate(kind: ItemKind, id: string, replacement: string, retireOn: string): Impact;
69
+ retire(kind: ItemKind, id: string, voiceMap?: Record<string, string>): Impact;
70
+ /** What AppBridge does with an id at session start: retired ids resolve to their replacement. */
71
+ resolve(id: string): {
72
+ id: string;
73
+ event?: "provider.deprecated_used" | "provider.retired_used";
74
+ replacement?: string;
75
+ };
76
+ show(id: string): CatalogEntry;
77
+ list(kind?: ItemKind): CatalogEntry[];
78
+ /** Voices the dashboard offers for a model: active or deprecated voices that work on it. */
79
+ voicesFor(modelId: string): VoiceEntry[];
80
+ usage(id: string): Usage[];
81
+ history(id?: string): import("./types").AuditEntry[];
82
+ /** The whole catalog, sorted and without secrets, as YAML. Byte-identical for equal catalogs. */
83
+ export(): string;
84
+ plan(file: string): string[];
85
+ apply(file: string): string[];
86
+ private diff;
87
+ }
@@ -0,0 +1,432 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Registry = exports.RegistryError = exports.BUILT_IN_ADAPTERS = void 0;
4
+ /**
5
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
+ * http://github.com/fonoster/fonoster
7
+ *
8
+ * This file is part of Fonoster
9
+ *
10
+ * Licensed under the MIT License (the "License");
11
+ * you may not use this file except in compliance with
12
+ * the License. You may obtain a copy of the License at
13
+ *
14
+ * https://opensource.org/licenses/MIT
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ */
22
+ const url_1 = require("url");
23
+ const yaml_1 = require("yaml");
24
+ const features_1 = require("./features");
25
+ const types_1 = require("./types");
26
+ /** Adapters that ship with this version (add-provider-registry, P2). */
27
+ exports.BUILT_IN_ADAPTERS = [
28
+ "deepgram",
29
+ "elevenlabs",
30
+ "google",
31
+ "azure",
32
+ "openai",
33
+ "anthropic",
34
+ "groq",
35
+ "ollama",
36
+ "aicoustics",
37
+ "fake"
38
+ ];
39
+ const ENDPOINT_SCHEMES = ["http:", "https:", "ws:", "wss:"];
40
+ // provider/name; the name may have its own slashes, as hosted models do (groq/openai/gpt-oss-120b).
41
+ const CATALOG_ID = /^[a-z0-9-]+(\/[A-Za-z0-9._-]+)+$/;
42
+ const REF = /^[a-z0-9][a-z0-9-]*$/;
43
+ const SECRET_KEY = /(api[-_]?key|secret|token|password|private[-_]?key)/i;
44
+ const SECRET_VALUE = /^(sk-|sk_|xox[abp]-|AKIA|AIza|-----BEGIN)/;
45
+ class RegistryError extends Error {
46
+ }
47
+ exports.RegistryError = RegistryError;
48
+ const noUsages = {
49
+ find: () => [],
50
+ rewrite: () => true,
51
+ notify: () => undefined
52
+ };
53
+ /** The registry operations behind the `fonoster-registry` utility. */
54
+ class Registry {
55
+ constructor(options) {
56
+ var _a;
57
+ this.options = options;
58
+ this.usages = (_a = options.usages) !== null && _a !== void 0 ? _a : noUsages;
59
+ }
60
+ get store() {
61
+ return this.options.store;
62
+ }
63
+ audit(action, target, details) {
64
+ var _a, _b, _c, _d, _e;
65
+ this.store.audit({
66
+ at: ((_c = (_b = (_a = this.options).now) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : new Date()).toISOString(),
67
+ actor: (_e = (_d = this.options.actor) !== null && _d !== void 0 ? _d : process.env.USER) !== null && _e !== void 0 ? _e : "operator",
68
+ action,
69
+ target,
70
+ details: this.options.commandLine
71
+ ? Object.assign({ command: this.options.commandLine }, details) : details
72
+ });
73
+ }
74
+ /** Rejects anything that looks like a credential (the catalog never holds a secret). */
75
+ assertNoSecrets(input) {
76
+ for (const [k, v] of Object.entries(input)) {
77
+ if (SECRET_KEY.test(k) ||
78
+ (typeof v === "string" && SECRET_VALUE.test(v))) {
79
+ throw new RegistryError(`secrets cannot be stored in the catalog (${k}); store keys as workspace Secrets or platform credentials and pass --credential-ref`);
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * A provider names a built-in adapter, a credentialRef name when it needs a key (none:
85
+ * keyless), and plain endpoint URLs.
86
+ */
87
+ assertProvider(p) {
88
+ var _a, _b;
89
+ if (!exports.BUILT_IN_ADAPTERS.includes(p.adapter))
90
+ throw new RegistryError(`unknown adapter: ${p.adapter}`);
91
+ for (const c of (_a = p.capabilities) !== null && _a !== void 0 ? _a : [])
92
+ if (!types_1.CAPABILITIES.includes(c))
93
+ throw new RegistryError(`unknown capability: ${c}`);
94
+ if (p.credentialRef !== undefined && !REF.test(p.credentialRef))
95
+ throw new RegistryError("credentialRef must be a name such as deepgram-default");
96
+ for (const [capability, url] of Object.entries((_b = p.endpoints) !== null && _b !== void 0 ? _b : {})) {
97
+ if (!types_1.CAPABILITIES.includes(capability))
98
+ throw new RegistryError(`unknown endpoint capability: ${capability}`);
99
+ let parsed;
100
+ try {
101
+ parsed = new url_1.URL(String(url));
102
+ }
103
+ catch (_c) {
104
+ throw new RegistryError(`endpoints.${capability} must be a URL`);
105
+ }
106
+ if (!ENDPOINT_SCHEMES.includes(parsed.protocol))
107
+ throw new RegistryError(`endpoints.${capability} must be an http, https, ws or wss URL`);
108
+ if (parsed.username || parsed.password)
109
+ throw new RegistryError(`secrets cannot be stored in the catalog (endpoints.${capability} has user information)`);
110
+ }
111
+ }
112
+ assertNew(id) {
113
+ if (!CATALOG_ID.test(id) && !REF.test(id))
114
+ throw new RegistryError(`invalid catalog id ${id}`);
115
+ const existing = this.store.get(id);
116
+ if (existing)
117
+ throw new RegistryError(`catalog id ${id} already exists (state: ${existing.state})`);
118
+ }
119
+ require(id, kind) {
120
+ const e = this.store.get(id);
121
+ if (!e || e.kind !== kind)
122
+ throw new RegistryError(`no ${kind} ${id}`);
123
+ return e;
124
+ }
125
+ // ---- add --------------------------------------------------------------------------
126
+ addProvider(input) {
127
+ this.assertNoSecrets(input);
128
+ this.assertNew(input.id);
129
+ this.assertProvider(input);
130
+ const entry = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ kind: "provider", id: input.id }, (input.label ? { label: input.label } : {})), { adapter: input.adapter, capabilities: input.capabilities }), (input.credentialRef ? { credentialRef: input.credentialRef } : {})), (input.endpoints && Object.keys(input.endpoints).length
131
+ ? { endpoints: input.endpoints }
132
+ : {})), { state: "active" });
133
+ this.store.put(entry);
134
+ this.audit("add-provider", entry.id, { entry, before: null, after: entry });
135
+ return entry;
136
+ }
137
+ addModel(input) {
138
+ var _a, _b, _c, _d;
139
+ this.assertNoSecrets(input);
140
+ this.assertNew(input.id);
141
+ this.requireProviderFor(input.id, input.type);
142
+ const unknown = (0, features_1.unknownFeatures)(input.type, (_a = input.features) !== null && _a !== void 0 ? _a : []);
143
+ if (unknown.length)
144
+ throw new RegistryError(`unknown ${input.type} features: ${unknown.join(", ")}`);
145
+ const entry = Object.assign(Object.assign({ kind: "model", id: input.id }, (input.label ? { label: input.label } : {})), { type: input.type, features: (_b = input.features) !== null && _b !== void 0 ? _b : [], languages: (_c = input.languages) !== null && _c !== void 0 ? _c : [], settings: (_d = input.settings) !== null && _d !== void 0 ? _d : [], state: "active" });
146
+ this.store.put(entry);
147
+ this.audit("add-model", entry.id, { entry, before: null, after: entry });
148
+ return entry;
149
+ }
150
+ addVoice(input) {
151
+ this.assertNoSecrets(input);
152
+ this.assertNew(input.id);
153
+ for (const m of input.models) {
154
+ const model = this.require(m, "model");
155
+ if (model.type !== "tts")
156
+ throw new RegistryError(`${m} is not a tts model`);
157
+ }
158
+ const entry = Object.assign(Object.assign({ kind: "voice", id: input.id }, (input.label ? { label: input.label } : {})), { models: input.models, language: input.language, gender: input.gender, ownerWorkspace: input.ownerWorkspace, state: "active" });
159
+ this.store.put(entry);
160
+ this.audit("add-voice", entry.id, { entry, before: null, after: entry });
161
+ return entry;
162
+ }
163
+ requireProviderFor(id, type) {
164
+ const provider = this.store.get(id.split("/")[0]);
165
+ if (!provider || provider.kind !== "provider")
166
+ throw new RegistryError(`no provider ${id.split("/")[0]}`);
167
+ if (!provider.capabilities.includes(type))
168
+ throw new RegistryError(`provider ${provider.id} has no ${type} capability`);
169
+ }
170
+ // ---- impact, deprecate, retire ----------------------------------------------------
171
+ /** What moving the usages of `id` to `replacement` would do (shared by deprecate and retire). */
172
+ impact(id, replacement, voiceMap = {}) {
173
+ var _a;
174
+ const item = this.store.get(id);
175
+ const repl = this.store.get(replacement);
176
+ if (!item || !repl)
177
+ throw new RegistryError(`unknown ${!item ? id : replacement}`);
178
+ const usages = this.usages.find(id);
179
+ const mappable = [];
180
+ const unmappable = [];
181
+ const counts = new Map();
182
+ const bump = (key, line) => {
183
+ var _a;
184
+ const l = (_a = counts.get(key)) !== null && _a !== void 0 ? _a : Object.assign(Object.assign({}, line), { count: 0 });
185
+ l.count++;
186
+ counts.set(key, l);
187
+ };
188
+ for (const u of usages) {
189
+ let ok = true;
190
+ if (item.kind === "model" && repl.kind === "model") {
191
+ const missing = u.requires.filter((f) => features_1.FEATURES[repl.type][f] === "hard" && !repl.features.includes(f));
192
+ if (missing.length) {
193
+ ok = false;
194
+ bump(`req:${missing}`, {
195
+ mark: "✗",
196
+ text: `requires ${missing.join(", ")}, which ${replacement} lacks`
197
+ });
198
+ }
199
+ if (u.voice) {
200
+ const voice = this.store.get(u.voice);
201
+ const name = (_a = u.voice.split("/")[1]) !== null && _a !== void 0 ? _a : u.voice;
202
+ if (voice === null || voice === void 0 ? void 0 : voice.models.includes(replacement))
203
+ bump(`v:${u.voice}`, {
204
+ mark: "✓",
205
+ text: `voice ${name} works on replacement`
206
+ });
207
+ else if (voiceMap[name])
208
+ bump(`vm:${u.voice}`, {
209
+ mark: "✓",
210
+ text: `voice ${name} → ${voiceMap[name]}`
211
+ });
212
+ else {
213
+ ok = false;
214
+ const owned = (voice === null || voice === void 0 ? void 0 : voice.ownerWorkspace)
215
+ ? ` (workspace ${voice.ownerWorkspace}'s own voice)`
216
+ : "";
217
+ bump(`vx:${u.voice}`, {
218
+ mark: "✗",
219
+ text: `voice ${name}${owned} only exists on ${id.split("/")[1]} → needs --voice-map ${name}=<voice>`
220
+ });
221
+ }
222
+ }
223
+ for (const s of u.settings.filter((s) => !repl.settings.includes(s))) {
224
+ bump(`s:${s}`, {
225
+ mark: "~",
226
+ text: `setting "${s}" not supported, will be dropped (soft)`
227
+ });
228
+ }
229
+ }
230
+ else if (item.kind === "voice" && repl.kind === "voice") {
231
+ if (u.model && !repl.models.includes(u.model)) {
232
+ ok = false;
233
+ bump(`m:${u.model}`, {
234
+ mark: "✗",
235
+ text: `${replacement} does not work on ${u.model}`
236
+ });
237
+ }
238
+ else
239
+ bump("ok", { mark: "✓", text: `works on the same model` });
240
+ }
241
+ else
242
+ bump("ok", { mark: "✓", text: "config moves to the replacement" });
243
+ (ok ? mappable : unmappable).push(u);
244
+ }
245
+ return { lines: [...counts.values()], mappable, unmappable };
246
+ }
247
+ deprecate(kind, id, replacement, retireOn) {
248
+ const item = this.require(id, kind);
249
+ const repl = this.store.get(replacement);
250
+ if (!repl ||
251
+ repl.kind !== kind ||
252
+ repl.state !== "active" ||
253
+ (kind === "model" &&
254
+ repl.type !== item.type)) {
255
+ throw new RegistryError(`replacement must be an active ${kind}`);
256
+ }
257
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(retireOn))
258
+ throw new RegistryError("--retire-on must be a date (YYYY-MM-DD)");
259
+ const report = this.impact(id, replacement);
260
+ const after = Object.assign(Object.assign({}, item), { state: "deprecated", replacement,
261
+ retireOn });
262
+ this.store.put(after);
263
+ this.audit(`deprecate-${kind}`, id, {
264
+ replacement,
265
+ retireOn,
266
+ before: item,
267
+ after
268
+ });
269
+ const workspaces = new Set(this.usages.find(id).map((u) => u.workspace));
270
+ // One provider.deprecated notice per workspace (sent as a workspace webhook by the apiserver).
271
+ for (const w of workspaces)
272
+ this.usages.notify(w, {
273
+ type: "provider.deprecated",
274
+ id,
275
+ replacement,
276
+ retireOn
277
+ });
278
+ return report;
279
+ }
280
+ retire(kind, id, voiceMap = {}) {
281
+ var _a;
282
+ const item = this.require(id, kind);
283
+ if (!item.replacement)
284
+ throw new RegistryError(`${id} must be deprecated with a replacement before it is retired`);
285
+ const report = this.impact(id, item.replacement, voiceMap);
286
+ const repl = this.store.get(item.replacement);
287
+ for (const u of report.mappable) {
288
+ const name = (_a = u.voice) === null || _a === void 0 ? void 0 : _a.split("/")[1];
289
+ const voice = kind === "voice"
290
+ ? item.replacement
291
+ : name && voiceMap[name]
292
+ ? `${u.voice.split("/")[0]}/${voiceMap[name]}`
293
+ : undefined;
294
+ const dropSettings = repl.kind === "model"
295
+ ? u.settings.filter((s) => !repl.settings.includes(s))
296
+ : [];
297
+ const change = {
298
+ model: kind === "model" ? item.replacement : undefined,
299
+ voice,
300
+ dropSettings
301
+ };
302
+ if (this.usages.rewrite(u, change))
303
+ this.audit("rewrite-config", id, {
304
+ workspace: u.workspace,
305
+ config: u.config,
306
+ change
307
+ });
308
+ }
309
+ for (const w of new Set([...report.mappable, ...report.unmappable].map((u) => u.workspace))) {
310
+ this.usages.notify(w, {
311
+ type: "provider.retired",
312
+ id,
313
+ replacement: item.replacement
314
+ });
315
+ }
316
+ const after = Object.assign(Object.assign({}, item), { state: "retired" });
317
+ this.store.put(after);
318
+ this.audit(`retire-${kind}`, id, {
319
+ before: item,
320
+ after,
321
+ replacement: item.replacement,
322
+ unmappable: report.unmappable.length
323
+ });
324
+ return report;
325
+ }
326
+ /** What AppBridge does with an id at session start: retired ids resolve to their replacement. */
327
+ resolve(id) {
328
+ const e = this.store.get(id);
329
+ if (!e)
330
+ return { id };
331
+ if (e.state === "retired" && e.replacement)
332
+ return Object.assign(Object.assign({}, this.resolve(e.replacement)), { event: "provider.retired_used", replacement: e.replacement });
333
+ if (e.state === "deprecated")
334
+ return {
335
+ id,
336
+ event: "provider.deprecated_used",
337
+ replacement: e.replacement
338
+ };
339
+ return { id };
340
+ }
341
+ // ---- read ---------------------------------------------------------------------------
342
+ show(id) {
343
+ const e = this.store.get(id);
344
+ if (!e)
345
+ throw new RegistryError(`no catalog entry ${id}`);
346
+ return e;
347
+ }
348
+ list(kind) {
349
+ return this.store.all().filter((e) => !kind || e.kind === kind);
350
+ }
351
+ /** Voices the dashboard offers for a model: active or deprecated voices that work on it. */
352
+ voicesFor(modelId) {
353
+ return this.store
354
+ .all()
355
+ .filter((e) => e.kind === "voice" &&
356
+ e.state !== "retired" &&
357
+ e.models.includes(modelId));
358
+ }
359
+ usage(id) {
360
+ return this.usages.find(id);
361
+ }
362
+ history(id) {
363
+ return this.store.history(id);
364
+ }
365
+ // ---- export, plan, apply ----------------------------------------------------------
366
+ /** The whole catalog, sorted and without secrets, as YAML. Byte-identical for equal catalogs. */
367
+ export() {
368
+ return (0, yaml_1.stringify)({
369
+ format: "fonoster.catalog.v1",
370
+ entries: this.store.all().map(canonical)
371
+ });
372
+ }
373
+ plan(file) {
374
+ return this.diff(file).map((d) => d.line);
375
+ }
376
+ apply(file) {
377
+ var _a;
378
+ const changes = this.diff(file);
379
+ for (const c of changes) {
380
+ const before = (_a = this.store.get(c.entry.id)) !== null && _a !== void 0 ? _a : null;
381
+ this.store.put(c.entry);
382
+ this.audit("apply", c.entry.id, {
383
+ change: c.line,
384
+ before,
385
+ after: c.entry
386
+ });
387
+ }
388
+ return changes.length ? changes.map((c) => c.line) : ["no changes"];
389
+ }
390
+ diff(file) {
391
+ const target = (0, yaml_1.parse)(file).entries;
392
+ const out = [];
393
+ for (const want of target) {
394
+ this.assertNoSecrets(want);
395
+ if (want.kind === "provider")
396
+ this.assertProvider(want);
397
+ const have = this.store.get(want.id);
398
+ if (!have)
399
+ out.push({ line: `+ add ${want.kind} ${want.id}`, entry: want });
400
+ else if (JSON.stringify(canonical(have)) !== JSON.stringify(canonical(want))) {
401
+ const verb = have.state !== want.state
402
+ ? want.state === "deprecated"
403
+ ? "deprecate"
404
+ : want.state === "retired"
405
+ ? "retire"
406
+ : "update"
407
+ : "update";
408
+ const to = want.replacement && have.state !== want.state
409
+ ? ` → ${want.replacement}`
410
+ : "";
411
+ out.push({
412
+ line: `~ ${verb} ${want.kind} ${want.id}${to}`,
413
+ entry: want
414
+ });
415
+ }
416
+ }
417
+ return out;
418
+ }
419
+ }
420
+ exports.Registry = Registry;
421
+ /** Keys in a fixed order and no undefined values, so exports compare byte for byte. */
422
+ function canonical(e) {
423
+ return Object.fromEntries(Object.entries(e)
424
+ .filter(([, v]) => v !== undefined)
425
+ .sort(([a], [b]) => a.localeCompare(b))
426
+ .map(([k, v]) => k === "endpoints"
427
+ ? [k, sortedKeys(v)]
428
+ : [k, v]));
429
+ }
430
+ function sortedKeys(o) {
431
+ return Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b)));
432
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { PrismaLike } from "./prisma";
3
+ /** Runs one command against the JSON file store (synchronous; the default store). */
4
+ export declare function run(argv: string[], out?: (s: string) => void): number;
5
+ export declare const usesDatabase: (argv: string[]) => boolean;
6
+ /**
7
+ * Runs one command against the control-plane database: loads the catalog and usages, runs the
8
+ * command, then waits for every write, records skipped rewrites in the audit log and sends due
9
+ * `provider.deprecated` webhooks once (retries are adopted by a running dispatcher).
10
+ */
11
+ export declare function runWithDatabase(argv: string[], out?: (s: string) => void, deps?: {
12
+ prisma?: PrismaLike;
13
+ actor?: string;
14
+ }): Promise<number>;
15
+ /**
16
+ * Runs one command with telemetry (add-platform 2.5): a `registry.command` span and a
17
+ * `fonoster.registry.commands` count by command and outcome, flushed before returning. Off unless
18
+ * an OTLP endpoint is set.
19
+ */
20
+ export declare function runTraced(argv: string[], env?: Record<string, string | undefined>): Promise<number>;