@fonoster/common 0.30.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.
- package/dist/{assistants → agents}/index.d.ts +1 -1
- package/dist/{assistants → agents}/index.js +1 -1
- package/dist/{assistants/sessionAssistantSchema.d.ts → agents/sessionAgentSchema.d.ts} +20 -6
- package/dist/{assistants/sessionAssistantSchema.js → agents/sessionAgentSchema.js} +16 -11
- package/dist/cellsecrets/index.d.ts +2 -2
- package/dist/cellsecrets/index.js +3 -3
- package/dist/config/cli/render.d.ts +1 -1
- package/dist/config/cli/render.js +3 -3
- package/dist/config/configHints.d.ts +1 -14
- package/dist/config/configHints.js +16 -30
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +0 -1
- package/dist/config/interpolateEnv.d.ts +1 -1
- package/dist/config/loadServiceConfig.d.ts +0 -5
- package/dist/config/loadServiceConfig.js +3 -12
- package/dist/constants.d.ts +1 -3
- package/dist/constants.js +3 -5
- package/dist/errors/handleZodError.js +3 -1
- package/dist/filters/index.d.ts +70 -0
- package/dist/filters/index.js +101 -0
- package/dist/identity/roles.js +11 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +5 -2
- package/dist/protos/applications.proto +16 -2
- package/dist/protos/authz.proto +3 -0
- package/dist/protos/billing.proto +92 -0
- package/dist/protos/calls.proto +4 -0
- package/dist/protos/capabilities.proto +8 -0
- package/dist/protos/providers.proto +6 -2
- package/dist/protos/roomr.proto +146 -0
- package/dist/protos/session.proto +194 -0
- package/dist/protos/usage.proto +59 -0
- package/dist/registry/Registry.d.ts +13 -5
- package/dist/registry/Registry.js +49 -35
- package/dist/registry/cli.js +19 -7
- package/dist/registry/features.d.ts +2 -2
- package/dist/registry/features.js +7 -5
- package/dist/registry/prisma.d.ts +2 -2
- package/dist/registry/prisma.js +26 -18
- package/dist/registry/types.d.ts +17 -5
- package/dist/registry/types.js +2 -0
- package/dist/roomr/health.d.ts +9 -0
- package/dist/roomr/health.js +75 -0
- package/dist/roomr/index.d.ts +75 -0
- package/dist/roomr/index.js +146 -0
- package/dist/roomr/session.d.ts +133 -0
- package/dist/roomr/session.js +132 -0
- package/dist/session/schema.d.ts +196 -7
- package/dist/session/schema.js +59 -8
- package/dist/sessions/index.d.ts +15 -12
- package/dist/sessions/index.js +15 -12
- package/dist/telemetry.d.ts +1 -1
- package/dist/usage/CallUsage.d.ts +38 -0
- package/dist/usage/CallUsage.js +76 -0
- package/dist/{utils/types.d.ts → usage/index.d.ts} +4 -5
- package/dist/usage/index.js +38 -0
- package/dist/usage/meters.d.ts +113 -0
- package/dist/usage/meters.js +90 -0
- package/dist/usage/types.d.ts +159 -0
- package/dist/usage/types.js +84 -0
- package/dist/usage/usageId.d.ts +6 -0
- package/dist/usage/usageId.js +45 -0
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.js +0 -2
- package/dist/utils/withErrorHandling.js +1 -1
- package/dist/validators/calls.d.ts +5 -2
- package/dist/validators/calls.js +8 -1
- package/dist/validators/sip.d.ts +2 -2
- package/dist/validators/sip.js +3 -3
- package/dist/webhooks/bus.d.ts +29 -10
- package/dist/webhooks/bus.js +58 -50
- package/dist/webhooks/events.d.ts +1 -1
- package/dist/webhooks/events.js +1 -1
- package/package.json +9 -8
- package/schema/session.v1.json +82 -2
- package/dist/config/integrationsSchema.d.ts +0 -32
- package/dist/config/integrationsSchema.js +0 -56
- package/dist/utils/createFetchSingleCallByCallId.d.ts +0 -22
- package/dist/utils/createFetchSingleCallByCallId.js +0 -33
- package/dist/utils/types.js +0 -2
- /package/dist/{assistants → agents}/testCasesSchema.d.ts +0 -0
- /package/dist/{assistants → agents}/testCasesSchema.js +0 -0
- /package/dist/{assistants → agents}/tools/index.d.ts +0 -0
- /package/dist/{assistants → agents}/tools/index.js +0 -0
- /package/dist/{assistants → agents}/tools/propertySchema.d.ts +0 -0
- /package/dist/{assistants → agents}/tools/propertySchema.js +0 -0
- /package/dist/{assistants → agents}/tools/toolSchema.d.ts +0 -0
- /package/dist/{assistants → agents}/tools/toolSchema.js +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Session: the per-call control stream the Roomr node opens to the session endpoint (in Fonoster,
|
|
2
|
+
// AppBridge behind a Kubernetes Service). The node is the client: one new connection per call,
|
|
3
|
+
// `Start` first, then commands from the app and events from the node, in order. Audio never
|
|
4
|
+
// travels here; it is RTP between the two MediaEndpoints.
|
|
5
|
+
// Spec: openspec/changes/redesign-roomr/specs/media-control ("Each call opens one control stream
|
|
6
|
+
// to the session endpoint", "App audio is RTP negotiated on the control stream", "Transfer modes",
|
|
7
|
+
// "Hold silences the app and plays hold audio", "Listeners fork audio by track", "Endings carry a
|
|
8
|
+
// reason", "The app sends DTMF on the caller's leg"). Messages only grow: add fields, never
|
|
9
|
+
// renumber or reuse them, and receivers ignore fields they do not know.
|
|
10
|
+
//
|
|
11
|
+
// Regenerate the Go code with go/roomr/api/generate.sh.
|
|
12
|
+
syntax = "proto3";
|
|
13
|
+
|
|
14
|
+
package session.v1;
|
|
15
|
+
|
|
16
|
+
option go_package = "github.com/fonoster/roomr/api/session/v1;sessionv1";
|
|
17
|
+
|
|
18
|
+
service Session {
|
|
19
|
+
rpc Connect(stream NodeMessage) returns (stream AppMessage);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// An RTP endpoint: `codec` is "L16/16000" on app legs; `srtp_key` (SDES, base64) when encrypted.
|
|
23
|
+
message MediaEndpoint {
|
|
24
|
+
string ip = 1;
|
|
25
|
+
uint32 port = 2;
|
|
26
|
+
string codec = 3;
|
|
27
|
+
string srtp_key = 4;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
message SipAuth {
|
|
31
|
+
string username = 1;
|
|
32
|
+
string password = 2;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// A SIP destination: `proxy` carries the transport in its URI (sip:host:port;transport=tcp).
|
|
36
|
+
message SipTarget {
|
|
37
|
+
string uri = 1;
|
|
38
|
+
string proxy = 2;
|
|
39
|
+
map<string, string> headers = 3;
|
|
40
|
+
SipAuth auth = 4;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---- node to app
|
|
44
|
+
|
|
45
|
+
message NodeMessage {
|
|
46
|
+
oneof msg {
|
|
47
|
+
Start start = 1;
|
|
48
|
+
Dtmf dtmf = 2;
|
|
49
|
+
Event event = 3;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
message Start {
|
|
54
|
+
enum Direction {
|
|
55
|
+
INBOUND = 0;
|
|
56
|
+
OUTBOUND = 1;
|
|
57
|
+
}
|
|
58
|
+
string call_ref = 1;
|
|
59
|
+
Direction direction = 2;
|
|
60
|
+
string from = 3;
|
|
61
|
+
string to = 4;
|
|
62
|
+
// Every INVITE header, as received (inbound) or as sent (outbound).
|
|
63
|
+
map<string, string> headers = 5;
|
|
64
|
+
// Opaque to the node: CreateCall's metadata, passed through untouched.
|
|
65
|
+
map<string, string> metadata = 6;
|
|
66
|
+
string room_ref = 7;
|
|
67
|
+
// The node's RTP endpoint for this call's app leg.
|
|
68
|
+
MediaEndpoint media = 8;
|
|
69
|
+
string node_ref = 9;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
message Dtmf {
|
|
73
|
+
string digit = 1;
|
|
74
|
+
// rfc4733, sip_info or inband
|
|
75
|
+
string source = 2;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
message Event {
|
|
79
|
+
// 3 was operation_done (Play and Record left the node).
|
|
80
|
+
reserved 3;
|
|
81
|
+
reserved "operation_done";
|
|
82
|
+
oneof event {
|
|
83
|
+
ParticipantJoined participant_joined = 1;
|
|
84
|
+
ParticipantLeft participant_left = 2;
|
|
85
|
+
TransferResult transfer_result = 4;
|
|
86
|
+
Ended ended = 5;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
message ParticipantJoined {
|
|
91
|
+
string participant_ref = 1;
|
|
92
|
+
uint32 track_id = 2;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
message ParticipantLeft {
|
|
96
|
+
string participant_ref = 1;
|
|
97
|
+
string cause = 2;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
message TransferResult {
|
|
101
|
+
// completed, referred or failed
|
|
102
|
+
string status = 1;
|
|
103
|
+
string cause = 2;
|
|
104
|
+
uint32 sip_status = 3;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The call ended: `cause` is one of caller_hangup, callee_hangup, busy, no_answer, rejected,
|
|
108
|
+
// media_timeout, node_error, max_duration, removed, app_gone; `sip_status` when SIP gave one.
|
|
109
|
+
message Ended {
|
|
110
|
+
string cause = 1;
|
|
111
|
+
uint32 sip_status = 2;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---- app to node
|
|
115
|
+
|
|
116
|
+
message AppMessage {
|
|
117
|
+
// 4, 5 and 13 were play, stop and record: the node is the SIP and media edge, not an app server
|
|
118
|
+
// (an app plays its own audio on its RTP leg; recording is a listener).
|
|
119
|
+
reserved 4, 5, 13;
|
|
120
|
+
reserved "play", "stop", "record";
|
|
121
|
+
oneof msg {
|
|
122
|
+
Answer answer = 1;
|
|
123
|
+
Ready ready = 2;
|
|
124
|
+
Reject reject = 3;
|
|
125
|
+
Transfer transfer = 6;
|
|
126
|
+
TransferComplete transfer_complete = 7;
|
|
127
|
+
Hold hold = 8;
|
|
128
|
+
Resume resume = 9;
|
|
129
|
+
SendDtmf send_dtmf = 10;
|
|
130
|
+
AddListener add_listener = 11;
|
|
131
|
+
RemoveListener remove_listener = 12;
|
|
132
|
+
Hangup hangup = 14;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Inbound: the app takes the call; the node answers 200 OK.
|
|
137
|
+
message Answer {
|
|
138
|
+
MediaEndpoint media = 1;
|
|
139
|
+
// Lowers the node's maximum call duration for this call (0: the node's).
|
|
140
|
+
uint32 max_duration_s = 2;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Outbound: the app is ready for the answered call.
|
|
144
|
+
message Ready {
|
|
145
|
+
MediaEndpoint media = 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Inbound: the app refuses the call with this SIP status (4xx to 6xx).
|
|
149
|
+
message Reject {
|
|
150
|
+
uint32 sip_status = 1;
|
|
151
|
+
string reason = 2;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
message Transfer {
|
|
155
|
+
SipTarget to = 1;
|
|
156
|
+
// cold, warm or refer
|
|
157
|
+
string mode = 2;
|
|
158
|
+
// Warm: music for the caller while the new party is dialed.
|
|
159
|
+
string music = 3;
|
|
160
|
+
uint32 ring_timeout_s = 4;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
message TransferComplete {}
|
|
164
|
+
|
|
165
|
+
message Hold {
|
|
166
|
+
string music = 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
message Resume {}
|
|
170
|
+
|
|
171
|
+
// RFC 4733 events on the caller's leg, in order.
|
|
172
|
+
message SendDtmf {
|
|
173
|
+
// 0-9, *, #, A-D.
|
|
174
|
+
string digits = 1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
message ListenTrack {
|
|
178
|
+
uint32 track_id = 1;
|
|
179
|
+
MediaEndpoint rtp = 2;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
message AddListener {
|
|
183
|
+
string listener_ref = 1;
|
|
184
|
+
repeated ListenTrack listen = 2;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
message RemoveListener {
|
|
188
|
+
string listener_ref = 1;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
message Hangup {
|
|
192
|
+
uint32 sip_status = 1;
|
|
193
|
+
string reason = 2;
|
|
194
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
syntax = "proto3";
|
|
20
|
+
|
|
21
|
+
package fonoster.usage.v1beta2;
|
|
22
|
+
|
|
23
|
+
// What the workspace consumed: quantities only, never money (usage-metering spec).
|
|
24
|
+
service Usage {
|
|
25
|
+
// Gets usage totals for a range of days, grouped as requested
|
|
26
|
+
rpc GetUsage (GetUsageRequest) returns (GetUsageResponse) {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
message GetUsageRequest {
|
|
30
|
+
// First day of the range, YYYY-MM-DD (UTC), inclusive
|
|
31
|
+
string from_day = 1;
|
|
32
|
+
// Last day of the range, YYYY-MM-DD (UTC), inclusive
|
|
33
|
+
string to_day = 2;
|
|
34
|
+
// Dimensions to group by: day | workspace | meter | provider | model | key_source | origin.
|
|
35
|
+
// Totals are always split by meter, so meter is implied.
|
|
36
|
+
repeated string group_by = 3;
|
|
37
|
+
// Owners only: every workspace the caller owns, instead of the caller's workspace
|
|
38
|
+
bool all_owned = 4;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
message UsageRow {
|
|
42
|
+
// Set only for the dimensions requested in group_by
|
|
43
|
+
string day = 1;
|
|
44
|
+
string workspace_ref = 2;
|
|
45
|
+
string meter = 3;
|
|
46
|
+
// second | character | token | count | byte
|
|
47
|
+
string unit = 4;
|
|
48
|
+
string provider = 5;
|
|
49
|
+
string model = 6;
|
|
50
|
+
// platform | workspace; empty for meters that call no vendor
|
|
51
|
+
string key_source = 7;
|
|
52
|
+
// call | browser_test | eval | api
|
|
53
|
+
string origin = 8;
|
|
54
|
+
double quantity = 9;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
message GetUsageResponse {
|
|
58
|
+
repeated UsageRow rows = 1;
|
|
59
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Capability, CatalogEntry, ItemKind, ModelEntry, ProviderEntry, RegistryStore, Usage, UsageSource, VoiceEntry } from "./types";
|
|
1
|
+
import { Capability, CatalogEntry, ItemKind, ModelEntry, ProviderEndpoints, ProviderEntry, RegistryStore, Usage, UsageSource, VoiceEntry } from "./types";
|
|
2
2
|
/** Adapters that ship with this version (add-provider-registry, P2). */
|
|
3
3
|
export declare const BUILT_IN_ADAPTERS: string[];
|
|
4
4
|
export declare class RegistryError extends Error {
|
|
@@ -17,8 +17,6 @@ export type RegistryOptions = {
|
|
|
17
17
|
store: RegistryStore;
|
|
18
18
|
usages?: UsageSource;
|
|
19
19
|
actor?: string;
|
|
20
|
-
/** Allows the fake adapter (test deployments only; conformance "Implementations ship fake adapters"). */
|
|
21
|
-
testMode?: boolean;
|
|
22
20
|
now?: () => Date;
|
|
23
21
|
/** The command line that made the change, stored in every audit entry (spec "Every write is audited"). */
|
|
24
22
|
commandLine?: string;
|
|
@@ -32,13 +30,21 @@ export declare class Registry {
|
|
|
32
30
|
private audit;
|
|
33
31
|
/** Rejects anything that looks like a credential (the catalog never holds a secret). */
|
|
34
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;
|
|
35
38
|
private assertNew;
|
|
36
39
|
private require;
|
|
37
40
|
addProvider(input: {
|
|
38
41
|
id: string;
|
|
39
42
|
adapter: string;
|
|
40
43
|
capabilities: Capability[];
|
|
41
|
-
|
|
44
|
+
/** Omitted: a keyless provider (no key needed). */
|
|
45
|
+
credentialRef?: string;
|
|
46
|
+
endpoints?: ProviderEndpoints;
|
|
47
|
+
label?: string;
|
|
42
48
|
} & Record<string, unknown>): ProviderEntry;
|
|
43
49
|
addModel(input: {
|
|
44
50
|
id: string;
|
|
@@ -46,6 +52,7 @@ export declare class Registry {
|
|
|
46
52
|
features?: string[];
|
|
47
53
|
languages?: string[];
|
|
48
54
|
settings?: string[];
|
|
55
|
+
label?: string;
|
|
49
56
|
} & Record<string, unknown>): ModelEntry;
|
|
50
57
|
addVoice(input: {
|
|
51
58
|
id: string;
|
|
@@ -53,13 +60,14 @@ export declare class Registry {
|
|
|
53
60
|
language?: string;
|
|
54
61
|
gender?: string;
|
|
55
62
|
ownerWorkspace?: string;
|
|
63
|
+
label?: string;
|
|
56
64
|
} & Record<string, unknown>): VoiceEntry;
|
|
57
65
|
private requireProviderFor;
|
|
58
66
|
/** What moving the usages of `id` to `replacement` would do (shared by deprecate and retire). */
|
|
59
67
|
impact(id: string, replacement: string, voiceMap?: Record<string, string>): Impact;
|
|
60
68
|
deprecate(kind: ItemKind, id: string, replacement: string, retireOn: string): Impact;
|
|
61
69
|
retire(kind: ItemKind, id: string, voiceMap?: Record<string, string>): Impact;
|
|
62
|
-
/** What
|
|
70
|
+
/** What AppBridge does with an id at session start: retired ids resolve to their replacement. */
|
|
63
71
|
resolve(id: string): {
|
|
64
72
|
id: string;
|
|
65
73
|
event?: "provider.deprecated_used" | "provider.retired_used";
|
|
@@ -19,8 +19,10 @@ exports.Registry = exports.RegistryError = exports.BUILT_IN_ADAPTERS = void 0;
|
|
|
19
19
|
* See the License for the specific language governing permissions and
|
|
20
20
|
* limitations under the License.
|
|
21
21
|
*/
|
|
22
|
+
const url_1 = require("url");
|
|
22
23
|
const yaml_1 = require("yaml");
|
|
23
24
|
const features_1 = require("./features");
|
|
25
|
+
const types_1 = require("./types");
|
|
24
26
|
/** Adapters that ship with this version (add-provider-registry, P2). */
|
|
25
27
|
exports.BUILT_IN_ADAPTERS = [
|
|
26
28
|
"deepgram",
|
|
@@ -31,8 +33,10 @@ exports.BUILT_IN_ADAPTERS = [
|
|
|
31
33
|
"anthropic",
|
|
32
34
|
"groq",
|
|
33
35
|
"ollama",
|
|
36
|
+
"aicoustics",
|
|
34
37
|
"fake"
|
|
35
38
|
];
|
|
39
|
+
const ENDPOINT_SCHEMES = ["http:", "https:", "ws:", "wss:"];
|
|
36
40
|
// provider/name; the name may have its own slashes, as hosted models do (groq/openai/gpt-oss-120b).
|
|
37
41
|
const CATALOG_ID = /^[a-z0-9-]+(\/[A-Za-z0-9._-]+)+$/;
|
|
38
42
|
const REF = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -76,6 +80,35 @@ class Registry {
|
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
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
|
+
}
|
|
79
112
|
assertNew(id) {
|
|
80
113
|
if (!CATALOG_ID.test(id) && !REF.test(id))
|
|
81
114
|
throw new RegistryError(`invalid catalog id ${id}`);
|
|
@@ -93,21 +126,10 @@ class Registry {
|
|
|
93
126
|
addProvider(input) {
|
|
94
127
|
this.assertNoSecrets(input);
|
|
95
128
|
this.assertNew(input.id);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
};
|
|
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" });
|
|
111
133
|
this.store.put(entry);
|
|
112
134
|
this.audit("add-provider", entry.id, { entry, before: null, after: entry });
|
|
113
135
|
return entry;
|
|
@@ -120,15 +142,7 @@ class Registry {
|
|
|
120
142
|
const unknown = (0, features_1.unknownFeatures)(input.type, (_a = input.features) !== null && _a !== void 0 ? _a : []);
|
|
121
143
|
if (unknown.length)
|
|
122
144
|
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
|
-
};
|
|
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" });
|
|
132
146
|
this.store.put(entry);
|
|
133
147
|
this.audit("add-model", entry.id, { entry, before: null, after: entry });
|
|
134
148
|
return entry;
|
|
@@ -141,15 +155,7 @@ class Registry {
|
|
|
141
155
|
if (model.type !== "tts")
|
|
142
156
|
throw new RegistryError(`${m} is not a tts model`);
|
|
143
157
|
}
|
|
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
|
-
};
|
|
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" });
|
|
153
159
|
this.store.put(entry);
|
|
154
160
|
this.audit("add-voice", entry.id, { entry, before: null, after: entry });
|
|
155
161
|
return entry;
|
|
@@ -317,7 +323,7 @@ class Registry {
|
|
|
317
323
|
});
|
|
318
324
|
return report;
|
|
319
325
|
}
|
|
320
|
-
/** What
|
|
326
|
+
/** What AppBridge does with an id at session start: retired ids resolve to their replacement. */
|
|
321
327
|
resolve(id) {
|
|
322
328
|
const e = this.store.get(id);
|
|
323
329
|
if (!e)
|
|
@@ -386,6 +392,8 @@ class Registry {
|
|
|
386
392
|
const out = [];
|
|
387
393
|
for (const want of target) {
|
|
388
394
|
this.assertNoSecrets(want);
|
|
395
|
+
if (want.kind === "provider")
|
|
396
|
+
this.assertProvider(want);
|
|
389
397
|
const have = this.store.get(want.id);
|
|
390
398
|
if (!have)
|
|
391
399
|
out.push({ line: `+ add ${want.kind} ${want.id}`, entry: want });
|
|
@@ -414,5 +422,11 @@ exports.Registry = Registry;
|
|
|
414
422
|
function canonical(e) {
|
|
415
423
|
return Object.fromEntries(Object.entries(e)
|
|
416
424
|
.filter(([, v]) => v !== undefined)
|
|
417
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
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)));
|
|
418
432
|
}
|
package/dist/registry/cli.js
CHANGED
|
@@ -118,11 +118,14 @@ const str = (v, name) => {
|
|
|
118
118
|
throw new Registry_1.RegistryError(`--${name} is required`);
|
|
119
119
|
return v;
|
|
120
120
|
};
|
|
121
|
+
/** An optional string flag (`--label` given without a value is ignored). */
|
|
122
|
+
const opt = (v) => typeof v === "string" && v ? v : undefined;
|
|
121
123
|
const HELP = `fonoster-registry <command>
|
|
122
124
|
|
|
123
|
-
add-provider <id> --adapter <a> --capabilities stt,tts --credential-ref <name>
|
|
125
|
+
add-provider <id> --adapter <a> --capabilities stt,tts,llm,nc [--credential-ref <name>] [--endpoints tts=<url>,stt=<url>]
|
|
124
126
|
add-model <provider/model> --type stt|tts|llm [--features a,b] [--languages en,es] [--settings s]
|
|
125
127
|
add-voice <provider/voice> --models <m1,m2> [--language en] [--gender female]
|
|
128
|
+
add-* take [--label "<display name>"]; no --credential-ref: keyless
|
|
126
129
|
deprecate-provider|deprecate-model|deprecate-voice <id> --replacement <id> --retire-on YYYY-MM-DD
|
|
127
130
|
retire-provider|retire-model|retire-voice <id> [--voice-map old=new,...] --yes
|
|
128
131
|
usage <id> | list [provider|model|voice] | show <id> | history [<id>]
|
|
@@ -137,6 +140,8 @@ const OPTIONS = {
|
|
|
137
140
|
"adapter",
|
|
138
141
|
"capabilities",
|
|
139
142
|
"credential-ref",
|
|
143
|
+
"endpoints",
|
|
144
|
+
"label",
|
|
140
145
|
"type",
|
|
141
146
|
"features",
|
|
142
147
|
"languages",
|
|
@@ -156,7 +161,6 @@ function run(argv, out = console.log) {
|
|
|
156
161
|
delete flags[f];
|
|
157
162
|
const registry = new Registry_1.Registry({
|
|
158
163
|
store: new stores_1.FileRegistryStore((_a = process.env.FONOSTER_REGISTRY_FILE) !== null && _a !== void 0 ? _a : "registry.json"),
|
|
159
|
-
testMode: process.env.FONOSTER_TEST_MODE === "true",
|
|
160
164
|
commandLine: commandLine(argv)
|
|
161
165
|
});
|
|
162
166
|
return execute(registry, command, positional, flags, out);
|
|
@@ -206,7 +210,6 @@ function runWithDatabase(argv_1) {
|
|
|
206
210
|
store,
|
|
207
211
|
usages,
|
|
208
212
|
actor: deps.actor,
|
|
209
|
-
testMode: process.env.FONOSTER_TEST_MODE === "true",
|
|
210
213
|
commandLine: commandLine(argv)
|
|
211
214
|
});
|
|
212
215
|
const sessions = command === "usage" && positional[0]
|
|
@@ -263,7 +266,14 @@ function execute(registry, command, positional, flags, out, sessions) {
|
|
|
263
266
|
id,
|
|
264
267
|
adapter: str(flags.adapter, "adapter"),
|
|
265
268
|
capabilities: list(flags.capabilities),
|
|
266
|
-
credentialRef:
|
|
269
|
+
credentialRef: opt(flags["credential-ref"]),
|
|
270
|
+
label: opt(flags.label),
|
|
271
|
+
endpoints: Object.fromEntries(list(flags.endpoints).map((pair) => {
|
|
272
|
+
const at = pair.indexOf("=");
|
|
273
|
+
if (at <= 0)
|
|
274
|
+
throw new Registry_1.RegistryError("--endpoints takes capability=url pairs, e.g. tts=http://fakes:7001");
|
|
275
|
+
return [pair.slice(0, at), pair.slice(at + 1)];
|
|
276
|
+
}))
|
|
267
277
|
});
|
|
268
278
|
break;
|
|
269
279
|
}
|
|
@@ -273,7 +283,8 @@ function execute(registry, command, positional, flags, out, sessions) {
|
|
|
273
283
|
type: str(flags.type, "type"),
|
|
274
284
|
features: list(flags.features),
|
|
275
285
|
languages: list(flags.languages),
|
|
276
|
-
settings: list(flags.settings)
|
|
286
|
+
settings: list(flags.settings),
|
|
287
|
+
label: opt(flags.label)
|
|
277
288
|
});
|
|
278
289
|
break;
|
|
279
290
|
case "add-voice":
|
|
@@ -281,7 +292,8 @@ function execute(registry, command, positional, flags, out, sessions) {
|
|
|
281
292
|
id,
|
|
282
293
|
models: list(flags.models),
|
|
283
294
|
language: flags.language,
|
|
284
|
-
gender: flags.gender
|
|
295
|
+
gender: flags.gender,
|
|
296
|
+
label: opt(flags.label)
|
|
285
297
|
});
|
|
286
298
|
break;
|
|
287
299
|
case "deprecate-provider":
|
|
@@ -312,7 +324,7 @@ function execute(registry, command, positional, flags, out, sessions) {
|
|
|
312
324
|
break;
|
|
313
325
|
}
|
|
314
326
|
// With the database: per workspace, its sessions that resolved the id in the last 30
|
|
315
|
-
// days (from the
|
|
327
|
+
// days (from the AppBridge replicas' registry.used events, LO-B11), then its configs.
|
|
316
328
|
const workspaces = [
|
|
317
329
|
...new Set([
|
|
318
330
|
...usages.map((u) => u.workspace),
|
|
@@ -23,8 +23,8 @@ import { Capability } from "./types";
|
|
|
23
23
|
*/
|
|
24
24
|
export declare const FEATURES: Record<Capability, Record<string, "hard" | "soft">>;
|
|
25
25
|
export declare function unknownFeatures(type: Capability, features: string[]): string[];
|
|
26
|
-
/** Hard requirements derived from an
|
|
27
|
-
export declare function
|
|
26
|
+
/** Hard requirements derived from an agent config: tools need tool calling, replies stream. */
|
|
27
|
+
export declare function agentRequirements(agent: {
|
|
28
28
|
languageModel?: {
|
|
29
29
|
tools?: unknown[];
|
|
30
30
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FEATURES = void 0;
|
|
4
4
|
exports.unknownFeatures = unknownFeatures;
|
|
5
|
-
exports.
|
|
5
|
+
exports.agentRequirements = agentRequirements;
|
|
6
6
|
/**
|
|
7
7
|
* The feature vocabulary (add-provider-registry, P8). Hard features are needed for a config to
|
|
8
8
|
* work at all; soft ones only degrade it (and their settings are dropped on retirement).
|
|
@@ -21,16 +21,18 @@ exports.FEATURES = {
|
|
|
21
21
|
"word-timings": "soft",
|
|
22
22
|
"turn-detection": "soft",
|
|
23
23
|
multilingual: "soft"
|
|
24
|
-
}
|
|
24
|
+
},
|
|
25
|
+
// Noise cancelling has no model entries yet; the provider itself is the unit.
|
|
26
|
+
nc: {}
|
|
25
27
|
};
|
|
26
28
|
function unknownFeatures(type, features) {
|
|
27
29
|
return features.filter((f) => !(f in exports.FEATURES[type]));
|
|
28
30
|
}
|
|
29
|
-
/** Hard requirements derived from an
|
|
30
|
-
function
|
|
31
|
+
/** Hard requirements derived from an agent config: tools need tool calling, replies stream. */
|
|
32
|
+
function agentRequirements(agent) {
|
|
31
33
|
var _a, _b;
|
|
32
34
|
const req = ["streaming"];
|
|
33
|
-
if (((_b = (_a =
|
|
35
|
+
if (((_b = (_a = agent.languageModel) === null || _a === void 0 ? void 0 : _a.tools) !== null && _b !== void 0 ? _b : []).length)
|
|
34
36
|
req.push("tool-calling");
|
|
35
37
|
return req;
|
|
36
38
|
}
|
|
@@ -23,8 +23,8 @@ export type PrismaUsageSourceOptions = {
|
|
|
23
23
|
onNotice?: (workspace: string, notice: Record<string, unknown>) => void;
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
|
-
* Usages of catalog ids in apps (speech defaults: voice, TTS and STT models) and
|
|
27
|
-
* Autopilot
|
|
26
|
+
* Usages of catalog ids in apps (speech defaults: voice, TTS and STT models) and agents (the
|
|
27
|
+
* Autopilot agent in an app's Intelligence config: `languageModel.model`, and
|
|
28
28
|
* `conversationSettings.voice`). Legacy rows with raw vendor names are not usages until migrated
|
|
29
29
|
* to catalog ids (task 5.1).
|
|
30
30
|
*/
|