@fonoster/autopilot 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.
Files changed (138) hide show
  1. package/README.md +14 -51
  2. package/dist/index.d.ts +0 -5
  3. package/dist/index.js +0 -5
  4. package/dist/knowledge/AbstractKnowledgeBase.d.ts +3 -2
  5. package/dist/knowledge/AbstractKnowledgeBase.js +2 -7
  6. package/dist/knowledge/FilesKnowledgeBase.d.ts +1 -1
  7. package/dist/knowledge/types.d.ts +1 -1
  8. package/dist/models/LanguageModelFactory.d.ts +5 -24
  9. package/dist/models/LanguageModelFactory.js +0 -1
  10. package/dist/models/createLanguageModel.d.ts +2 -2
  11. package/dist/models/evaluations/createEvalEffectiveConfig.d.ts +10 -106
  12. package/dist/models/evaluations/createEvalEffectiveConfig.js +47 -7
  13. package/dist/models/evaluations/createEvaluateIntelligence.d.ts +10 -20
  14. package/dist/models/evaluations/createEvaluateIntelligence.js +32 -23
  15. package/dist/models/evaluations/evalTestCases.d.ts +2 -2
  16. package/dist/models/evaluations/extractAiResponse.d.ts +2 -3
  17. package/dist/models/evaluations/index.d.ts +1 -0
  18. package/dist/models/evaluations/index.js +1 -0
  19. package/dist/models/evaluations/runEval.d.ts +2 -20
  20. package/dist/models/evaluations/session/EvalSession.d.ts +73 -0
  21. package/dist/models/evaluations/session/EvalSession.js +134 -0
  22. package/dist/models/evaluations/session/evaluateSessionScenario.d.ts +58 -0
  23. package/dist/models/evaluations/session/evaluateSessionScenario.js +182 -0
  24. package/dist/{vad → models/evaluations/session}/index.d.ts +3 -2
  25. package/dist/{vad → models/evaluations/session}/index.js +3 -2
  26. package/dist/models/evaluations/session/runSessionEval.d.ts +17 -0
  27. package/dist/models/evaluations/session/runSessionEval.js +59 -0
  28. package/dist/models/evaluations/types.d.ts +7 -6
  29. package/dist/models/types.d.ts +19 -1
  30. package/dist/session/Agent.d.ts +138 -0
  31. package/dist/session/Agent.js +474 -0
  32. package/dist/{loadAssistantFromAPI.d.ts → session/AsyncQueue.d.ts} +12 -7
  33. package/dist/session/AsyncQueue.js +67 -0
  34. package/dist/{machine/guards/index.d.ts → session/config.d.ts} +2 -2
  35. package/dist/session/config.js +27 -0
  36. package/dist/session/convertLegacyAssistant.d.ts +77 -0
  37. package/dist/session/convertLegacyAssistant.js +118 -0
  38. package/dist/session/createAutopilot.d.ts +47 -0
  39. package/dist/session/createAutopilot.js +119 -0
  40. package/dist/session/hosted.d.ts +456 -0
  41. package/dist/session/hosted.js +506 -0
  42. package/dist/{assistants → session}/index.d.ts +11 -2
  43. package/dist/{machine/actors → session}/index.js +11 -3
  44. package/dist/{machine/actions/appendSpeech.js → session/knowledge.d.ts} +14 -17
  45. package/dist/session/knowledge.js +10 -0
  46. package/dist/session/llm.d.ts +86 -0
  47. package/dist/session/llm.js +83 -0
  48. package/dist/session/machine.d.ts +206 -0
  49. package/dist/session/machine.js +126 -0
  50. package/dist/session/main.js +44 -0
  51. package/dist/session/sentences.d.ts +5 -0
  52. package/dist/session/sentences.js +53 -0
  53. package/dist/{machine/actors/index.d.ts → session/think.d.ts} +2 -3
  54. package/dist/{machine/actions/announceSystemError.js → session/think.js} +8 -10
  55. package/dist/session/tools.d.ts +22 -0
  56. package/dist/session/tools.js +83 -0
  57. package/dist/tools/builtin/transferToolDefinition.js +1 -1
  58. package/dist/voice/index.d.ts +0 -1
  59. package/dist/voice/index.js +0 -1
  60. package/dist/voice/types.d.ts +5 -18
  61. package/package.json +13 -14
  62. package/dist/Autopilot.d.ts +0 -14
  63. package/dist/Autopilot.js +0 -133
  64. package/dist/assistants/index.js +0 -36
  65. package/dist/assistants/loadAndValidateAssistant.d.ts +0 -3
  66. package/dist/assistants/loadAndValidateAssistant.js +0 -49
  67. package/dist/assistants/types.d.ts +0 -24
  68. package/dist/assistants/types.js +0 -2
  69. package/dist/envs.d.ts +0 -15
  70. package/dist/envs.js +0 -78
  71. package/dist/handleVoiceRequest.d.ts +0 -3
  72. package/dist/handleVoiceRequest.js +0 -167
  73. package/dist/loadAssistantConfigFromFile.d.ts +0 -3
  74. package/dist/loadAssistantConfigFromFile.js +0 -68
  75. package/dist/loadAssistantFromAPI.js +0 -98
  76. package/dist/machine/actions/announceSystemError.d.ts +0 -4
  77. package/dist/machine/actions/appendSpeech.d.ts +0 -1
  78. package/dist/machine/actions/cleanSpeech.d.ts +0 -1
  79. package/dist/machine/actions/cleanSpeech.js +0 -23
  80. package/dist/machine/actions/goodbye.d.ts +0 -4
  81. package/dist/machine/actions/goodbye.js +0 -31
  82. package/dist/machine/actions/increaseIdleTimeoutCount.d.ts +0 -1
  83. package/dist/machine/actions/increaseIdleTimeoutCount.js +0 -31
  84. package/dist/machine/actions/index.d.ts +0 -26
  85. package/dist/machine/actions/index.js +0 -37
  86. package/dist/machine/actions/interruptPlayback.d.ts +0 -4
  87. package/dist/machine/actions/interruptPlayback.js +0 -43
  88. package/dist/machine/actions/resetIdleTimeoutCount.d.ts +0 -1
  89. package/dist/machine/actions/resetIdleTimeoutCount.js +0 -31
  90. package/dist/machine/actions/resetState.d.ts +0 -1
  91. package/dist/machine/actions/resetState.js +0 -32
  92. package/dist/machine/actors/doAnnounceIdleTimeout.d.ts +0 -4
  93. package/dist/machine/actors/doAnnounceIdleTimeout.js +0 -41
  94. package/dist/machine/actors/doGreetUser.d.ts +0 -4
  95. package/dist/machine/actors/doGreetUser.js +0 -45
  96. package/dist/machine/actors/doProcessUserRequest.d.ts +0 -4
  97. package/dist/machine/actors/doProcessUserRequest.js +0 -75
  98. package/dist/machine/context.d.ts +0 -52
  99. package/dist/machine/context.js +0 -28
  100. package/dist/machine/delays.d.ts +0 -30
  101. package/dist/machine/delays.js +0 -37
  102. package/dist/machine/guards/hasSpeechResult.d.ts +0 -4
  103. package/dist/machine/guards/hasSpeechResult.js +0 -30
  104. package/dist/machine/guards/idleTimeoutCountExceedsMax.d.ts +0 -4
  105. package/dist/machine/guards/idleTimeoutCountExceedsMax.js +0 -31
  106. package/dist/machine/guards/index.js +0 -25
  107. package/dist/machine/index.d.ts +0 -19
  108. package/dist/machine/index.js +0 -23
  109. package/dist/machine/machine.d.ts +0 -408
  110. package/dist/machine/machine.js +0 -206
  111. package/dist/machine/setup.d.ts +0 -328
  112. package/dist/machine/setup.js +0 -116
  113. package/dist/machine/types.d.ts +0 -54
  114. package/dist/machine/types.js +0 -2
  115. package/dist/sendConversationEndedEvent.d.ts +0 -32
  116. package/dist/sendConversationEndedEvent.js +0 -66
  117. package/dist/sendConversationStartedEvent.d.ts +0 -6
  118. package/dist/sendConversationStartedEvent.js +0 -63
  119. package/dist/server.d.ts +0 -2
  120. package/dist/server.js +0 -25
  121. package/dist/types.d.ts +0 -40
  122. package/dist/types.js +0 -8
  123. package/dist/vad/SileroVad.d.ts +0 -18
  124. package/dist/vad/SileroVad.js +0 -40
  125. package/dist/vad/SileroVadModel.d.ts +0 -14
  126. package/dist/vad/SileroVadModel.js +0 -83
  127. package/dist/vad/chunkToFloat32Array.d.ts +0 -20
  128. package/dist/vad/chunkToFloat32Array.js +0 -34
  129. package/dist/vad/createVad.d.ts +0 -3
  130. package/dist/vad/createVad.js +0 -126
  131. package/dist/vad/types.d.ts +0 -60
  132. package/dist/vad/types.js +0 -2
  133. package/dist/vadWorker.js +0 -36
  134. package/dist/voice/Voice.d.ts +0 -61
  135. package/dist/voice/Voice.js +0 -96
  136. package/dist/voiceServerSetup.d.ts +0 -2
  137. package/dist/voiceServerSetup.js +0 -29
  138. /package/dist/{vadWorker.d.ts → session/main.d.ts} +0 -0
@@ -0,0 +1,506 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FileWorkspaceSecrets = exports.CellSecretCredentials = exports.DatabaseWorkspaceSecrets = exports.PrismaAssistantSource = exports.FileAssistantSource = exports.AUTOPILOT_CONFIG_DIRS = exports.hostedConfigSchema = void 0;
37
+ exports.findHostedConfig = findHostedConfig;
38
+ exports.loadHostedConfig = loadHostedConfig;
39
+ exports.s3Knowledge = s3Knowledge;
40
+ exports.hostedOptions = hostedOptions;
41
+ exports.eagerKnowledge = eagerKnowledge;
42
+ exports.signedWorkspaceOnly = signedWorkspaceOnly;
43
+ exports.startHostedAutopilot = startHostedAutopilot;
44
+ /**
45
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
46
+ * http://github.com/fonoster/fonoster
47
+ *
48
+ * This file is part of Fonoster
49
+ *
50
+ * Licensed under the MIT License (the "License");
51
+ * you may not use this file except in compliance with
52
+ * the License. You may obtain a copy of the License at
53
+ *
54
+ * https://opensource.org/licenses/MIT
55
+ *
56
+ * Unless required by applicable law or agreed to in writing, software
57
+ * distributed under the License is distributed on an "AS IS" BASIS,
58
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
59
+ * See the License for the specific language governing permissions and
60
+ * limitations under the License.
61
+ */
62
+ const fs = __importStar(require("fs"));
63
+ const path = __importStar(require("path"));
64
+ const common_1 = require("@fonoster/common");
65
+ const logger_1 = require("@fonoster/logger");
66
+ const voice_1 = require("@fonoster/voice");
67
+ const yaml = __importStar(require("js-yaml"));
68
+ const zod_1 = require("zod");
69
+ const createAutopilot_1 = require("./createAutopilot");
70
+ const knowledge_1 = require("./knowledge");
71
+ const llm_1 = require("./llm");
72
+ // The hosted Autopilot (rework-autopilot A2, task 4.3): an ordinary VoiceServer that the gateway
73
+ // dials like any app. Everything it needs comes from one operator config file; no vendor key is
74
+ // read from Autopilot's environment by name (A8, A11), only through credential refs.
75
+ const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
76
+ const secretRef = zod_1.z.union([
77
+ zod_1.z.object({ env: zod_1.z.string() }).strict(),
78
+ zod_1.z.object({ value: zod_1.z.string() }).strict()
79
+ ]);
80
+ const wsUrl = zod_1.z.string().regex(/^wss?:\/\//, "must be a ws:// or wss:// URL");
81
+ exports.hostedConfigSchema = zod_1.z
82
+ .object({
83
+ host: zod_1.z.string().default("0.0.0.0"),
84
+ port: zod_1.z.number().int().nonnegative().default(50061),
85
+ /**
86
+ * Private destinations HTTP tools may call (host names, IPs, CIDRs; RV-B2); defaults to
87
+ * FONOSTER_ALLOWED_DESTINATIONS.
88
+ */
89
+ allowedDestinations: zod_1.z.array(zod_1.z.string()).optional(),
90
+ /** The secret the gateway signs Autopilot sessions with (one for every Autopilot app). */
91
+ signingSecret: secretRef,
92
+ /**
93
+ * The URL Autopilot apps point to, exactly as the apps have it (the apiserver's
94
+ * `apps.autopilotUrl`, e.g. ws://autopilot:3000), or several. Signatures are checked against
95
+ * it, never against the request's Host header.
96
+ */
97
+ publicUrl: zod_1.z.union([wsUrl, zod_1.z.array(wsUrl).min(1)], {
98
+ errorMap: (issue, ctx) => ({
99
+ message: issue.code !== "invalid_union"
100
+ ? ctx.defaultError
101
+ : ctx.data === undefined
102
+ ? "is required: the URL Autopilot apps point to (the apiserver's apps.autopilotUrl)"
103
+ : "must be a ws:// or wss:// URL, or a list of them"
104
+ })
105
+ }),
106
+ /**
107
+ * Apps and their assistants: a file `{apps: {<appRef>: {workspaceRef?, assistant}}}` (JSON or
108
+ * YAML), or `database`: the apps' Intelligence rows, cached for `cacheMs` (CP-B11).
109
+ */
110
+ assistants: zod_1.z.union([
111
+ zod_1.z.object({ file: zod_1.z.string() }).strict(),
112
+ zod_1.z
113
+ .object({
114
+ source: zod_1.z.literal("database"),
115
+ cacheMs: zod_1.z.number().int().nonnegative().default(5000)
116
+ })
117
+ .strict()
118
+ ]),
119
+ /** The provider registry's catalog: the JSON file fonoster-registry writes, or the tables. */
120
+ registry: zod_1.z.union([
121
+ zod_1.z.object({ file: zod_1.z.string() }).strict(),
122
+ zod_1.z
123
+ .object({
124
+ source: zod_1.z.literal("database"),
125
+ refreshMs: zod_1.z.number().int().positive().default(30000)
126
+ })
127
+ .strict()
128
+ ]),
129
+ /** Platform keys by credentialRef. */
130
+ credentials: zod_1.z.record(secretRef).default({}),
131
+ /**
132
+ * Workspace Secrets (bring your own key): a JSON file by workspace and credentialRef, or
133
+ * `database`: the workspace's vendor keys (workspace_provider_keys).
134
+ */
135
+ workspaceSecrets: zod_1.z
136
+ .union([
137
+ zod_1.z.object({ file: zod_1.z.string() }).strict(),
138
+ zod_1.z.object({ source: zod_1.z.literal("database") }).strict(),
139
+ // A private region's cell secrets (add-private-regions PR-4, PR-9): by credentialRef or
140
+ // provider id, set on the cell with `fonoster-region secrets set <name>`.
141
+ zod_1.z
142
+ .object({
143
+ cellSecrets: zod_1.z
144
+ .object({
145
+ file: zod_1.z.string().optional(),
146
+ dir: zod_1.z.string().optional(),
147
+ keyEnv: zod_1.z.string().optional()
148
+ })
149
+ .strict()
150
+ })
151
+ .strict()
152
+ ])
153
+ .optional(),
154
+ /**
155
+ * The control-plane database (the apiserver's), for any `source: database`. The URL comes
156
+ * from this file only, usually as a placeholder: `url: "${APISERVER_DATABASE_URL}"`.
157
+ */
158
+ database: zod_1.z
159
+ .object({
160
+ url: zod_1.z.string().min(1),
161
+ /** A client generated from the apiserver schema (default `@prisma/client`). */
162
+ clientModule: zod_1.z.string().optional()
163
+ })
164
+ .strict()
165
+ .optional(),
166
+ /** Endpoint overrides by adapter or provider id (e.g. a local ollama, the fake LLM). */
167
+ endpoints: zod_1.z.record(zod_1.z.string()).default({}),
168
+ /** The S3 + Unstructured knowledge bases; keys by credentialRef (task 4.1). */
169
+ knowledge: zod_1.z
170
+ .object({
171
+ s3: zod_1.z.object({
172
+ endpoint: zod_1.z.string(),
173
+ region: zod_1.z.string().default("us-east-1"),
174
+ accessKeyIdRef: zod_1.z.string(),
175
+ secretAccessKeyRef: zod_1.z.string()
176
+ }),
177
+ unstructured: zod_1.z.object({ url: zod_1.z.string(), apiKeyRef: zod_1.z.string() }),
178
+ embeddings: zod_1.z.object({ apiKeyRef: zod_1.z.string() })
179
+ })
180
+ .optional(),
181
+ /**
182
+ * The apiserver's encryption key (fonoster.yaml `encryption.key`; vendor keys are encrypted
183
+ * Secrets). Usually "${ENCRYPTION_KEY}".
184
+ */
185
+ encryption: zod_1.z
186
+ .object({ key: zod_1.z.string().min(1) })
187
+ .strict()
188
+ .optional(),
189
+ /** OTLP export (empty = off): `otlpEndpoint: "${OTEL_EXPORTER_OTLP_ENDPOINT:-}"`. */
190
+ telemetry: common_1.telemetrySchema,
191
+ testMode: zod_1.z.boolean().default(false)
192
+ })
193
+ .strict();
194
+ function readStructured(file) {
195
+ const text = fs.readFileSync(file, "utf8");
196
+ return /\.ya?ml$/.test(file) ? yaml.load(text) : JSON.parse(text);
197
+ }
198
+ /** Where Autopilot looks for its file when none is named (autopilot.yaml, then the older name). */
199
+ exports.AUTOPILOT_CONFIG_DIRS = ["/config", "/etc/fonoster", "./config"];
200
+ /** The file `loadHostedConfig` reads without `--config`: the first one found, or undefined. */
201
+ function findHostedConfig() {
202
+ for (const base of ["autopilot", "autopilot-session"]) {
203
+ const found = (0, common_1.resolveConfigFile)(base, [], exports.AUTOPILOT_CONFIG_DIRS);
204
+ if (found.path)
205
+ return found.path;
206
+ }
207
+ return undefined;
208
+ }
209
+ /**
210
+ * Loads the hosted Autopilot's file the apiserver's way (unify-config-placeholders): `${VAR}`
211
+ * placeholders from the container environment, `secret://` references, strict validation.
212
+ */
213
+ function loadHostedConfig(file, env = process.env) {
214
+ const config = (0, common_1.loadServiceConfig)({
215
+ service: "autopilot",
216
+ schema: exports.hostedConfigSchema,
217
+ argv: ["--config", file],
218
+ env,
219
+ interpolatePaths: ["*"],
220
+ // The URL no longer falls back to APISERVER_DATABASE_URL (fix-config-and-placement-gaps).
221
+ movedKeys: {
222
+ "database.urlEnv": 'database.url ("${APISERVER_DATABASE_URL}")'
223
+ }
224
+ });
225
+ const dir = path.dirname(file);
226
+ const rel = (p) => (path.isAbsolute(p) ? p : path.join(dir, p));
227
+ const withFile = (c) => "file" in c ? { ...c, file: rel(c.file) } : c;
228
+ const needsDb = !("file" in config.assistants) ||
229
+ !("file" in config.registry) ||
230
+ (config.workspaceSecrets &&
231
+ "source" in config.workspaceSecrets &&
232
+ config.workspaceSecrets.source === "database");
233
+ if (needsDb && !config.database)
234
+ throw new Error("source: database needs a database section");
235
+ return {
236
+ ...config,
237
+ assistants: withFile(config.assistants),
238
+ registry: withFile(config.registry),
239
+ ...(config.workspaceSecrets
240
+ ? { workspaceSecrets: withFile(config.workspaceSecrets) }
241
+ : {})
242
+ };
243
+ }
244
+ const resolveSecret = (s) => "value" in s && s.value !== undefined ? s.value : process.env[s.env ?? ""];
245
+ /** Apps and assistants from a file (a stand-in until assistants come from the control plane). */
246
+ class FileAssistantSource {
247
+ constructor(file) {
248
+ this.file = file;
249
+ }
250
+ async get(appRef) {
251
+ const data = readStructured(this.file);
252
+ const app = data.apps?.[appRef];
253
+ return app
254
+ ? { assistant: app.assistant, workspaceRef: app.workspaceRef }
255
+ : undefined;
256
+ }
257
+ }
258
+ exports.FileAssistantSource = FileAssistantSource;
259
+ /**
260
+ * Assistants from the control plane: an Autopilot app's Intelligence row holds its assistant
261
+ * (CP-B3). Each app is cached for `cacheMs` (misses too), so edits show up within seconds.
262
+ */
263
+ class PrismaAssistantSource {
264
+ constructor(
265
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
266
+ prisma, options = {}) {
267
+ this.prisma = prisma;
268
+ this.options = options;
269
+ this.cache = new Map();
270
+ }
271
+ async get(appRef) {
272
+ const now = this.options.now?.() ?? Date.now();
273
+ const hit = this.cache.get(appRef);
274
+ if (hit && now - hit.at < (this.options.cacheMs ?? 5000))
275
+ return hit.value;
276
+ const row = await this.prisma.application.findUnique({
277
+ where: { ref: appRef },
278
+ include: { intelligence: true }
279
+ });
280
+ const value = row && row.type === "AUTOPILOT" && row.intelligence?.config
281
+ ? { assistant: row.intelligence.config, workspaceRef: row.accessKeyId }
282
+ : undefined;
283
+ this.cache.set(appRef, { at: now, value });
284
+ return value;
285
+ }
286
+ }
287
+ exports.PrismaAssistantSource = PrismaAssistantSource;
288
+ /** Workspace vendor keys from the database, looked up by the credentialRef's providers. */
289
+ class DatabaseWorkspaceSecrets {
290
+ constructor(keys, providers) {
291
+ this.keys = keys;
292
+ this.providers = providers;
293
+ }
294
+ get(ref, scope) {
295
+ if (!scope.workspaceRef)
296
+ return undefined;
297
+ return this.keys.forCredentialRef(scope.workspaceRef, ref, this.providers());
298
+ }
299
+ }
300
+ exports.DatabaseWorkspaceSecrets = DatabaseWorkspaceSecrets;
301
+ /** A private region's cell secrets (PR-4): keys never leave the cell. */
302
+ class CellSecretCredentials {
303
+ constructor(keys, providers) {
304
+ this.keys = keys;
305
+ this.providers = providers;
306
+ }
307
+ get(ref) {
308
+ return this.keys.forCredentialRef(ref, this.providers());
309
+ }
310
+ }
311
+ exports.CellSecretCredentials = CellSecretCredentials;
312
+ /** Workspace Secrets from a JSON file: `{<workspaceRef>: {<credentialRef>: value}}`. */
313
+ class FileWorkspaceSecrets {
314
+ constructor(file) {
315
+ this.file = file;
316
+ }
317
+ get(ref, scope) {
318
+ if (!scope.workspaceRef)
319
+ return undefined;
320
+ const data = readStructured(this.file);
321
+ return data[scope.workspaceRef]?.[ref];
322
+ }
323
+ }
324
+ exports.FileWorkspaceSecrets = FileWorkspaceSecrets;
325
+ /**
326
+ * Builds the knowledge port for an assistant from the operator's S3 + Unstructured settings,
327
+ * with keys from the credential source (never Autopilot's environment). The bucket is the
328
+ * workspace's, as today. `make` is the knowledge base constructor (the S3 one by default).
329
+ */
330
+ function s3Knowledge(config, credentials, make) {
331
+ return async (assistant, scope) => {
332
+ const documents = assistant.languageModel.knowledgeBase.map((d) => d.document);
333
+ if (!documents.length || !scope.workspaceRef)
334
+ return undefined;
335
+ const key = (ref) => credentials.get(ref, scope);
336
+ const [accessKeyId, secretAccessKey, unstructuredKey, embeddingsKey] = await Promise.all([
337
+ key(config.s3.accessKeyIdRef),
338
+ key(config.s3.secretAccessKeyRef),
339
+ key(config.unstructured.apiKeyRef),
340
+ key(config.embeddings.apiKeyRef)
341
+ ]);
342
+ const params = {
343
+ bucket: scope.workspaceRef.toLowerCase(),
344
+ documents,
345
+ s3Config: {
346
+ endpoint: config.s3.endpoint,
347
+ region: config.s3.region,
348
+ credentials: {
349
+ accessKeyId: accessKeyId ?? "",
350
+ secretAccessKey: secretAccessKey ?? ""
351
+ },
352
+ forcePathStyle: true
353
+ },
354
+ unstructuredAPIURL: config.unstructured.url,
355
+ unstructuredAPIKey: unstructuredKey ?? "",
356
+ embeddingsApiKey: embeddingsKey ?? ""
357
+ };
358
+ const kb = (make ?? defaultKnowledgeBase)(params);
359
+ kb.load().catch((e) => logger.warn(`knowledge base did not load: ${e.message}`));
360
+ return (0, knowledge_1.fromKnowledgeBase)(kb);
361
+ };
362
+ }
363
+ function defaultKnowledgeBase(params) {
364
+ // Loaded lazily: the S3 loader and embeddings pull in LangChain, which only the knowledge
365
+ // port still uses.
366
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
367
+ const { S3KnowledgeBase } = require("../knowledge/S3KnowledgeBase");
368
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
369
+ const { OpenAIEmbeddings } = require("@langchain/openai");
370
+ return new S3KnowledgeBase({
371
+ ...params,
372
+ embeddings: new OpenAIEmbeddings({ apiKey: params.embeddingsApiKey })
373
+ });
374
+ }
375
+ /** Opens the control-plane database named in the config (once). */
376
+ function databaseOf(config) {
377
+ const d = config.database;
378
+ return common_1.Registry.openPrismaClient({
379
+ url: d.url,
380
+ encryptionKey: config.encryption?.key,
381
+ clientModule: d.clientModule
382
+ });
383
+ }
384
+ /** The registry store: the file, or the tables refreshed every `refreshMs`. */
385
+ function registryStore(config,
386
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
387
+ db) {
388
+ if ("file" in config.registry)
389
+ return new common_1.Registry.FileRegistryStore(config.registry.file);
390
+ const store = new common_1.Registry.MemoryRegistryStore();
391
+ const every = config.registry.refreshMs ?? 30000;
392
+ const load = () => common_1.Registry.PrismaRegistryStore.open(db())
393
+ .then((loaded) => {
394
+ for (const e of loaded.all())
395
+ store.put(e);
396
+ })
397
+ .catch((e) => logger.warn(`registry refresh failed, keeping the last: ${e.message}`));
398
+ void load();
399
+ setInterval(load, every).unref();
400
+ return store;
401
+ }
402
+ /** The handler options the hosted Autopilot runs with, built from its config. */
403
+ function hostedOptions(config,
404
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
405
+ deps = {}) {
406
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
407
+ let prisma = deps.prisma;
408
+ const db = () => (prisma ?? (prisma = databaseOf(config)));
409
+ const store = registryStore(config, db);
410
+ const registry = new common_1.Registry.Registry({
411
+ store,
412
+ testMode: config.testMode
413
+ });
414
+ const providers = () => store
415
+ .all()
416
+ .filter((e) => e.kind === "provider")
417
+ .map((e) => ({
418
+ id: e.id,
419
+ credentialRef: e.credentialRef
420
+ }));
421
+ const workspace = config.workspaceSecrets;
422
+ const credentials = new llm_1.LayeredCredentials([
423
+ ...(workspace
424
+ ? [
425
+ "file" in workspace
426
+ ? new FileWorkspaceSecrets(workspace.file)
427
+ : "cellSecrets" in workspace
428
+ ? new CellSecretCredentials(new common_1.CellSecrets.CellSecretKeys(common_1.CellSecrets.openCellSecrets(workspace.cellSecrets)), providers)
429
+ : new DatabaseWorkspaceSecrets(new common_1.Registry.PrismaWorkspaceKeys(db()), providers)
430
+ ]
431
+ : []),
432
+ new llm_1.ConfigCredentials(config.credentials)
433
+ ]);
434
+ const assistants = "file" in config.assistants
435
+ ? new FileAssistantSource(config.assistants.file)
436
+ : new PrismaAssistantSource(db(), {
437
+ cacheMs: config.assistants.cacheMs
438
+ });
439
+ const knowledge = config.knowledge
440
+ ? s3Knowledge(config.knowledge, credentials)
441
+ : undefined;
442
+ return {
443
+ load: async (session) => signedWorkspaceOnly(session, await assistants.get(session.appRef)),
444
+ allowedDestinations: config.allowedDestinations,
445
+ llm: new llm_1.RegistryLlmResolver({
446
+ registry,
447
+ credentials,
448
+ endpoints: config.endpoints
449
+ }),
450
+ knowledgeFor: knowledge ? eagerKnowledge(knowledge) : undefined
451
+ };
452
+ }
453
+ /**
454
+ * One knowledge port per call, built eagerly; a lookup waits for it (and gives up after 800 ms
455
+ * like any). A port that fails to build is logged and answers nothing: the eager promise always
456
+ * has a handler, so a failure never becomes an unhandled rejection (RV-B5).
457
+ */
458
+ function eagerKnowledge(build) {
459
+ return (assistant, scope) => {
460
+ const port = Promise.resolve()
461
+ .then(() => build(assistant, scope))
462
+ .catch((e) => {
463
+ logger.warn(`knowledge port unavailable: ${e?.message ?? e}`);
464
+ return undefined;
465
+ });
466
+ return {
467
+ find: async (question, signal) => {
468
+ const p = await port;
469
+ return p ? p.find(question, signal) : "";
470
+ }
471
+ };
472
+ };
473
+ }
474
+ /**
475
+ * The app's assistant only when the app belongs to the workspace the gateway signed for: one
476
+ * secret serves every Autopilot app, so a session is bound to its app and workspace
477
+ * (voice-session, "Connection requests are bound to the app").
478
+ */
479
+ function signedWorkspaceOnly(session, loaded) {
480
+ const signed = session.signedFor;
481
+ if (!loaded || !signed || loaded.workspaceRef === undefined)
482
+ return loaded;
483
+ if (signed.workspaceRef === loaded.workspaceRef)
484
+ return loaded;
485
+ logger.warn("refused a session: the app is not in the signed workspace", {
486
+ appRef: session.appRef
487
+ });
488
+ return undefined;
489
+ }
490
+ /** Starts the hosted Autopilot. */
491
+ async function startHostedAutopilot(config) {
492
+ const secret = resolveSecret(config.signingSecret);
493
+ if (!secret)
494
+ throw new Error("signingSecret resolves to nothing");
495
+ const server = await new voice_1.VoiceServer({
496
+ host: config.host,
497
+ port: config.port,
498
+ signingSecret: secret,
499
+ // Every Autopilot app shares this secret: check the configured URL only (never Host) and
500
+ // require the signature bound to the app and workspace.
501
+ publicUrl: config.publicUrl,
502
+ requireAppBinding: true
503
+ }).listen((0, createAutopilot_1.createAutopilotHandler)(hostedOptions(config)));
504
+ logger.info(`hosted autopilot listening on ${config.host}:${server.port}`);
505
+ return server;
506
+ }
@@ -16,5 +16,14 @@
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */
19
- export * from "./loadAndValidateAssistant";
20
- export * from "./types";
19
+ export * from "./Agent";
20
+ export * from "./config";
21
+ export * from "./convertLegacyAssistant";
22
+ export * from "./createAutopilot";
23
+ export * from "./hosted";
24
+ export * from "./knowledge";
25
+ export * from "./llm";
26
+ export * from "./machine";
27
+ export * from "./sentences";
28
+ export * from "./think";
29
+ export * from "./tools";
@@ -32,6 +32,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
32
32
  * See the License for the specific language governing permissions and
33
33
  * limitations under the License.
34
34
  */
35
- __exportStar(require("./doProcessUserRequest"), exports);
36
- __exportStar(require("./doGreetUser"), exports);
37
- __exportStar(require("./doAnnounceIdleTimeout"), exports);
35
+ __exportStar(require("./Agent"), exports);
36
+ __exportStar(require("./config"), exports);
37
+ __exportStar(require("./convertLegacyAssistant"), exports);
38
+ __exportStar(require("./createAutopilot"), exports);
39
+ __exportStar(require("./hosted"), exports);
40
+ __exportStar(require("./knowledge"), exports);
41
+ __exportStar(require("./llm"), exports);
42
+ __exportStar(require("./machine"), exports);
43
+ __exportStar(require("./sentences"), exports);
44
+ __exportStar(require("./think"), exports);
45
+ __exportStar(require("./tools"), exports);
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.appendSpeech = void 0;
4
1
  /**
5
2
  * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
3
  * http://github.com/fonoster/fonoster
@@ -19,17 +16,17 @@ exports.appendSpeech = void 0;
19
16
  * See the License for the specific language governing permissions and
20
17
  * limitations under the License.
21
18
  */
22
- const logger_1 = require("@fonoster/logger");
23
- const xstate_1 = require("xstate");
24
- const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
25
- exports.appendSpeech = (0, xstate_1.assign)(({ context, event }) => {
26
- const speech = event.speech;
27
- logger.verbose("called the appendSpeech action", { speech });
28
- if (!speech) {
29
- return context;
30
- }
31
- context.speechBuffer = ((context.speechBuffer ?? "") +
32
- " " +
33
- speech).trimStart();
34
- return context;
35
- });
19
+ /**
20
+ * The knowledge port (rework-autopilot A8): one operation, find what is relevant to a question.
21
+ * Implementations (S3 + Unstructured today) are wired by the operator; their keys never live in
22
+ * the assistant config.
23
+ */
24
+ export interface KnowledgePort {
25
+ find(question: string, signal: AbortSignal): Promise<string>;
26
+ }
27
+ /** Knowledge lookups never hold a reply back longer than this. */
28
+ export declare const KNOWLEDGE_TIMEOUT_MS = 800;
29
+ /** Adapts the current knowledge bases (mods/autopilot/src/knowledge) to the port. */
30
+ export declare function fromKnowledgeBase(kb: {
31
+ queryKnowledgeBase(query: string, k?: number): Promise<string>;
32
+ }): KnowledgePort;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KNOWLEDGE_TIMEOUT_MS = void 0;
4
+ exports.fromKnowledgeBase = fromKnowledgeBase;
5
+ /** Knowledge lookups never hold a reply back longer than this. */
6
+ exports.KNOWLEDGE_TIMEOUT_MS = 800;
7
+ /** Adapts the current knowledge bases (mods/autopilot/src/knowledge) to the port. */
8
+ function fromKnowledgeBase(kb) {
9
+ return { find: (question) => kb.queryKnowledgeBase(question) };
10
+ }
@@ -0,0 +1,86 @@
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
+ import { Registry } from "@fonoster/common";
20
+ import { Think } from "@fonoster/community";
21
+ import { OPENAI_COMPATIBLE } from "@fonoster/community/openai";
22
+ /** Where keys come from: workspace Secrets first (bring your own key), then platform keys. */
23
+ export interface CredentialSource {
24
+ get(ref: string, scope: {
25
+ workspaceRef?: string;
26
+ }): Promise<string | undefined> | string | undefined;
27
+ }
28
+ /**
29
+ * Platform credentials from operator config: `{ "<credentialRef>": { env } | { value } }`. The
30
+ * variable names are operator choices; Autopilot never reads a vendor variable by itself.
31
+ */
32
+ export declare class ConfigCredentials implements CredentialSource {
33
+ private readonly refs;
34
+ constructor(refs: Record<string, {
35
+ env?: string;
36
+ value?: string;
37
+ }>);
38
+ get(ref: string): string;
39
+ }
40
+ /** Workspace Secrets (BYOK) in front of platform credentials. */
41
+ export declare class LayeredCredentials implements CredentialSource {
42
+ private readonly sources;
43
+ constructor(sources: CredentialSource[]);
44
+ get(ref: string, scope: {
45
+ workspaceRef?: string;
46
+ }): Promise<string>;
47
+ }
48
+ /** Adapters whose LLMs speak OpenAI-compatible Chat Completions (moved to @fonoster/community/openai). */
49
+ export { OPENAI_COMPATIBLE };
50
+ export declare class LlmResolutionError extends Error {
51
+ }
52
+ export type ResolvedLlm = {
53
+ think: Think;
54
+ /** The id actually used (a retired id resolves to its replacement). */
55
+ id: string;
56
+ /** For the call record: provider.deprecated_used / provider.retired_used (registry PR4). */
57
+ event?: {
58
+ type: string;
59
+ id: string;
60
+ replacement?: string;
61
+ };
62
+ };
63
+ export interface LlmResolver {
64
+ resolve(catalogId: string, options: {
65
+ temperature: number;
66
+ maxTokens: number;
67
+ }, scope: {
68
+ workspaceRef?: string;
69
+ }): Promise<ResolvedLlm>;
70
+ }
71
+ export type RegistryLlmResolverOptions = {
72
+ registry: Registry.Registry;
73
+ credentials: CredentialSource;
74
+ /** Endpoint overrides by adapter or provider id (required for fake; e.g. a local ollama). */
75
+ endpoints?: Record<string, string>;
76
+ };
77
+ export declare class RegistryLlmResolver implements LlmResolver {
78
+ private readonly options;
79
+ constructor(options: RegistryLlmResolverOptions);
80
+ resolve(catalogId: string, settings: {
81
+ temperature: number;
82
+ maxTokens: number;
83
+ }, scope?: {
84
+ workspaceRef?: string;
85
+ }): Promise<ResolvedLlm>;
86
+ }