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