@fonoster/autopilot 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.
Files changed (53) hide show
  1. package/README.md +5 -5
  2. package/dist/knowledge/MeteredEmbeddings.d.ts +47 -0
  3. package/dist/knowledge/MeteredEmbeddings.js +58 -0
  4. package/dist/models/createLanguageModel.d.ts +2 -2
  5. package/dist/models/createLanguageModel.js +2 -2
  6. package/dist/models/evaluations/createEvalEffectiveConfig.d.ts +6 -6
  7. package/dist/models/evaluations/createEvalEffectiveConfig.js +7 -7
  8. package/dist/models/evaluations/createEvaluateIntelligence.d.ts +15 -4
  9. package/dist/models/evaluations/createEvaluateIntelligence.js +33 -12
  10. package/dist/models/evaluations/createTestTextSimilarity.d.ts +7 -1
  11. package/dist/models/evaluations/createTestTextSimilarity.js +19 -1
  12. package/dist/models/evaluations/evalTestCases.d.ts +2 -2
  13. package/dist/models/evaluations/evalTestCases.js +7 -7
  14. package/dist/models/evaluations/evaluateScenario.js +2 -2
  15. package/dist/models/evaluations/evaluateStep.d.ts +1 -1
  16. package/dist/models/evaluations/evaluateStep.js +2 -2
  17. package/dist/models/evaluations/extractAiResponse.d.ts +2 -2
  18. package/dist/models/evaluations/extractAiResponse.js +3 -3
  19. package/dist/models/evaluations/runEval.d.ts +2 -2
  20. package/dist/models/evaluations/runEval.js +7 -7
  21. package/dist/models/evaluations/session/EvalSession.d.ts +1 -1
  22. package/dist/models/evaluations/session/EvalSession.js +1 -1
  23. package/dist/models/evaluations/session/evaluateSessionScenario.d.ts +3 -3
  24. package/dist/models/evaluations/session/evaluateSessionScenario.js +4 -4
  25. package/dist/models/evaluations/session/runSessionEval.d.ts +7 -4
  26. package/dist/models/evaluations/session/runSessionEval.js +7 -7
  27. package/dist/models/evaluations/textSimilaryPrompt.d.ts +1 -1
  28. package/dist/models/evaluations/textSimilaryPrompt.js +1 -1
  29. package/dist/models/evaluations/types.d.ts +5 -5
  30. package/dist/models/types.d.ts +3 -3
  31. package/dist/session/Agent.d.ts +38 -3
  32. package/dist/session/Agent.js +90 -16
  33. package/dist/session/config.d.ts +2 -2
  34. package/dist/session/config.js +4 -4
  35. package/dist/session/createAutopilot.d.ts +12 -11
  36. package/dist/session/createAutopilot.js +48 -16
  37. package/dist/session/hosted.d.ts +23 -25
  38. package/dist/session/hosted.js +43 -48
  39. package/dist/session/index.d.ts +1 -1
  40. package/dist/session/index.js +1 -1
  41. package/dist/session/knowledge.d.ts +1 -1
  42. package/dist/session/llm.d.ts +28 -1
  43. package/dist/session/llm.js +50 -6
  44. package/dist/session/main.js +2 -1
  45. package/dist/session/meteredThink.d.ts +45 -0
  46. package/dist/session/meteredThink.js +125 -0
  47. package/dist/session/telemetry.d.ts +34 -0
  48. package/dist/session/telemetry.js +55 -0
  49. package/dist/session/tools.d.ts +1 -1
  50. package/dist/session/tools.js +1 -1
  51. package/package.json +9 -9
  52. package/dist/session/convertLegacyAssistant.d.ts +0 -77
  53. package/dist/session/convertLegacyAssistant.js +0 -118
@@ -23,7 +23,10 @@ const logger_1 = require("@fonoster/logger");
23
23
  const xstate_1 = require("xstate");
24
24
  const Agent_1 = require("./Agent");
25
25
  const config_1 = require("./config");
26
+ const llm_1 = require("./llm");
26
27
  const machine_1 = require("./machine");
28
+ const meteredThink_1 = require("./meteredThink");
29
+ const telemetry_1 = require("./telemetry");
27
30
  const think_1 = require("./think");
28
31
  const tools_1 = require("./tools");
29
32
  const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
@@ -32,8 +35,26 @@ const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filen
32
35
  * runs the state machine on the session's public messages only.
33
36
  */
34
37
  function createAutopilotHandler(options) {
38
+ const run = createSessionRunner(options);
35
39
  return async (session) => {
36
- // Listen before any await: turn events that arrive while the assistant and model load are
40
+ // The session span joins the call's trace when AppBridge sent a traceparent.
41
+ const span = telemetry_1.telemetry.startCall(session.callRef, "autopilot.session", {
42
+ "fonoster.app_ref": session.appRef,
43
+ "fonoster.session_ref": session.sessionRef
44
+ }, session.traceparent);
45
+ try {
46
+ await run(session);
47
+ span.end();
48
+ }
49
+ catch (e) {
50
+ span.end({}, e.message);
51
+ throw e;
52
+ }
53
+ };
54
+ }
55
+ function createSessionRunner(options) {
56
+ return async (session) => {
57
+ // Listen before any await: turn events that arrive while the agent and model load are
37
58
  // held and delivered once the machine runs.
38
59
  const early = [];
39
60
  let deliver = (event) => early.push(event);
@@ -47,37 +68,48 @@ function createAutopilotHandler(options) {
47
68
  deliver({ type: "TURN_ENDED", text: e.text });
48
69
  });
49
70
  session.on("turn.backchannel", () => deliver({ type: "TURN_BACKCHANNEL" }));
71
+ // Open drains of interrupted replies stop with the session: their usage can no longer
72
+ // reach the call (add-usage-metering D6).
73
+ const closed = new AbortController();
50
74
  const ended = new Promise((resolve) => session.on("end", () => {
75
+ closed.abort();
51
76
  deliver({ type: "SESSION_END" });
52
77
  resolve();
53
78
  }));
54
79
  const loaded = options.load
55
80
  ? await options.load(session)
56
81
  : {
57
- assistant: typeof options.assistant === "function"
58
- ? await options.assistant(session)
59
- : options.assistant
82
+ agent: typeof options.agent === "function"
83
+ ? await options.agent(session)
84
+ : options.agent
60
85
  };
61
86
  if (!loaded) {
62
- logger.warn("no assistant for this app; ending the call", {
87
+ logger.warn("no agent for this app; ending the call", {
63
88
  appRef: session.appRef,
64
89
  callRef: session.callRef
65
90
  });
66
91
  await session.hangup().catch(() => undefined);
67
92
  return;
68
93
  }
69
- const assistant = config_1.sessionAssistantSchema.parse(loaded.assistant);
94
+ const agentConfig = config_1.sessionAgentSchema.parse(loaded.agent);
70
95
  const scope = { workspaceRef: loaded.workspaceRef };
71
96
  const recorder = options.recorder ?? (0, Agent_1.sessionRecorder)(session);
72
97
  const settings = {
73
- temperature: assistant.languageModel.temperature,
74
- maxTokens: assistant.languageModel.maxTokens
98
+ temperature: agentConfig.languageModel.temperature,
99
+ maxTokens: agentConfig.languageModel.maxTokens
100
+ };
101
+ const sink = {
102
+ usage: (lines) => recorder.usage?.(lines),
103
+ missing: (catalogId) => {
104
+ telemetry_1.telemetry.count("fonoster.usage.missing", 1, { catalog_id: catalogId });
105
+ recorder.note({ type: "usage_missing", catalogId });
106
+ }
75
107
  };
76
108
  let think = options.think;
77
109
  if (!think && options.llm) {
78
110
  try {
79
- const resolved = await options.llm.resolve(assistant.languageModel.model, settings, scope);
80
- think = resolved.think;
111
+ const resolved = await options.llm.resolve(agentConfig.languageModel.model, settings, scope);
112
+ think = (0, meteredThink_1.meteredThink)(resolved.think, { catalogId: resolved.id, keySource: resolved.keySource }, sink, { closed: closed.signal });
81
113
  if (resolved.event)
82
114
  recorder.note(resolved.event);
83
115
  }
@@ -86,10 +118,10 @@ function createAutopilotHandler(options) {
86
118
  logger.error(`autopilot cannot reach its language model: ${e.message}`);
87
119
  recorder.note({
88
120
  type: "llm.unavailable",
89
- reason: e.message
121
+ reason: e instanceof llm_1.KeyUnavailableError ? e.reason : e.message
90
122
  });
91
123
  await session
92
- .say(assistant.conversationSettings.systemErrorMessage, {
124
+ .say(agentConfig.conversationSettings.systemErrorMessage, {
93
125
  interruptible: false
94
126
  })
95
127
  .catch(() => undefined);
@@ -97,11 +129,11 @@ function createAutopilotHandler(options) {
97
129
  return;
98
130
  }
99
131
  }
100
- think ?? (think = (0, think_1.resolveThink)(assistant.languageModel.model, options.providers ?? {}, settings));
101
- const agent = new Agent_1.Agent(session, assistant, think, options.tools ??
102
- new tools_1.HttpToolPort(assistant.languageModel.tools, {
132
+ think ?? (think = (0, meteredThink_1.meteredThink)((0, think_1.resolveThink)(agentConfig.languageModel.model, options.providers ?? {}, settings), { catalogId: agentConfig.languageModel.model, keySource: "platform" }, sink, { closed: closed.signal }));
133
+ const agent = new Agent_1.Agent(session, agentConfig, think, options.tools ??
134
+ new tools_1.HttpToolPort(agentConfig.languageModel.tools, {
103
135
  allow: options.allowedDestinations
104
- }), recorder, options.knowledge ?? options.knowledgeFor?.(assistant, scope));
136
+ }), recorder, options.knowledge ?? options.knowledgeFor?.(agentConfig, scope, sink));
105
137
  options.onAgent?.(agent);
106
138
  const actor = (0, xstate_1.createActor)(machine_1.autopilotMachine, { input: { agent } });
107
139
  agent.send = (event) => actor.send(event);
@@ -1,10 +1,11 @@
1
1
  import { CellSecrets, Registry } from "@fonoster/common";
2
2
  import { Session, VoiceServer } from "@fonoster/voice";
3
3
  import { z } from "zod";
4
- import { SessionAssistant } from "./config";
5
- import { AutopilotOptions, LoadedAssistant } from "./createAutopilot";
4
+ import { SessionAgent } from "./config";
5
+ import { AutopilotOptions, LoadedAgent } from "./createAutopilot";
6
6
  import { KnowledgePort } from "./knowledge";
7
7
  import { CredentialSource } from "./llm";
8
+ import { UsageSink } from "./meteredThink";
8
9
  export declare const hostedConfigSchema: z.ZodObject<{
9
10
  host: z.ZodDefault<z.ZodString>;
10
11
  port: z.ZodDefault<z.ZodNumber>;
@@ -13,7 +14,7 @@ export declare const hostedConfigSchema: z.ZodObject<{
13
14
  * FONOSTER_ALLOWED_DESTINATIONS.
14
15
  */
15
16
  allowedDestinations: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
16
- /** The secret the gateway signs Autopilot sessions with (one for every Autopilot app). */
17
+ /** The secret AppBridge signs Autopilot sessions with (one for every Autopilot app). */
17
18
  signingSecret: z.ZodUnion<[z.ZodObject<{
18
19
  env: z.ZodString;
19
20
  }, "strict", z.ZodTypeAny, {
@@ -34,10 +35,10 @@ export declare const hostedConfigSchema: z.ZodObject<{
34
35
  */
35
36
  publicUrl: z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>;
36
37
  /**
37
- * Apps and their assistants: a file `{apps: {<appRef>: {workspaceRef?, assistant}}}` (JSON or
38
+ * Apps and their agents: a file `{apps: {<appRef>: {workspaceRef?, agent}}}` (JSON or
38
39
  * YAML), or `database`: the apps' Intelligence rows, cached for `cacheMs` (CP-B11).
39
40
  */
40
- assistants: z.ZodUnion<[z.ZodObject<{
41
+ agents: z.ZodUnion<[z.ZodObject<{
41
42
  file: z.ZodString;
42
43
  }, "strict", z.ZodTypeAny, {
43
44
  file?: string;
@@ -142,7 +143,7 @@ export declare const hostedConfigSchema: z.ZodObject<{
142
143
  url?: string;
143
144
  clientModule?: string;
144
145
  }>>;
145
- /** Endpoint overrides by adapter or provider id (e.g. a local ollama, the fake LLM). */
146
+ /** Endpoint overrides by provider or adapter id (e.g. a local ollama); the catalog's otherwise. */
146
147
  endpoints: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
147
148
  /** The S3 + Unstructured knowledge bases; keys by credentialRef (task 4.1). */
148
149
  knowledge: z.ZodOptional<z.ZodObject<{
@@ -227,7 +228,6 @@ export declare const hostedConfigSchema: z.ZodObject<{
227
228
  }, {
228
229
  otlpEndpoint?: string;
229
230
  }>>;
230
- testMode: z.ZodDefault<z.ZodBoolean>;
231
231
  }, "strict", z.ZodTypeAny, {
232
232
  registry?: {
233
233
  file?: string;
@@ -244,7 +244,7 @@ export declare const hostedConfigSchema: z.ZodObject<{
244
244
  value?: string;
245
245
  };
246
246
  publicUrl?: string | string[];
247
- assistants?: {
247
+ agents?: {
248
248
  file?: string;
249
249
  } | {
250
250
  source?: "database";
@@ -292,7 +292,6 @@ export declare const hostedConfigSchema: z.ZodObject<{
292
292
  telemetry?: {
293
293
  otlpEndpoint?: string;
294
294
  };
295
- testMode?: boolean;
296
295
  }, {
297
296
  registry?: {
298
297
  file?: string;
@@ -309,7 +308,7 @@ export declare const hostedConfigSchema: z.ZodObject<{
309
308
  value?: string;
310
309
  };
311
310
  publicUrl?: string | string[];
312
- assistants?: {
311
+ agents?: {
313
312
  file?: string;
314
313
  } | {
315
314
  source?: "database";
@@ -357,7 +356,6 @@ export declare const hostedConfigSchema: z.ZodObject<{
357
356
  telemetry?: {
358
357
  otlpEndpoint?: string;
359
358
  };
360
- testMode?: boolean;
361
359
  }>;
362
360
  export type HostedConfig = z.infer<typeof hostedConfigSchema>;
363
361
  /** Where Autopilot looks for its file when none is named (autopilot.yaml, then the older name). */
@@ -369,17 +367,17 @@ export declare function findHostedConfig(): string | undefined;
369
367
  * placeholders from the container environment, `secret://` references, strict validation.
370
368
  */
371
369
  export declare function loadHostedConfig(file: string, env?: Record<string, string | undefined>): HostedConfig;
372
- /** Apps and assistants from a file (a stand-in until assistants come from the control plane). */
373
- export declare class FileAssistantSource {
370
+ /** Apps and agents from a file (a stand-in until agents come from the control plane). */
371
+ export declare class FileAgentSource {
374
372
  private readonly file;
375
373
  constructor(file: string);
376
- get(appRef: string): Promise<LoadedAssistant | undefined>;
374
+ get(appRef: string): Promise<LoadedAgent | undefined>;
377
375
  }
378
376
  /**
379
- * Assistants from the control plane: an Autopilot app's Intelligence row holds its assistant
377
+ * Agents from the control plane: an Autopilot app's Intelligence row holds its agent
380
378
  * (CP-B3). Each app is cached for `cacheMs` (misses too), so edits show up within seconds.
381
379
  */
382
- export declare class PrismaAssistantSource {
380
+ export declare class PrismaAgentSource {
383
381
  private readonly prisma;
384
382
  private readonly options;
385
383
  private cache;
@@ -387,7 +385,7 @@ export declare class PrismaAssistantSource {
387
385
  cacheMs?: number;
388
386
  now?: () => number;
389
387
  });
390
- get(appRef: string): Promise<LoadedAssistant | undefined>;
388
+ get(appRef: string): Promise<LoadedAgent | undefined>;
391
389
  }
392
390
  /** Workspace vendor keys from the database, looked up by the credentialRef's providers. */
393
391
  export declare class DatabaseWorkspaceSecrets implements CredentialSource {
@@ -424,13 +422,13 @@ type KnowledgeBaseLike = {
424
422
  queryKnowledgeBase(query: string, k?: number): Promise<string>;
425
423
  };
426
424
  /**
427
- * Builds the knowledge port for an assistant from the operator's S3 + Unstructured settings,
425
+ * Builds the knowledge port for an agent from the operator's S3 + Unstructured settings,
428
426
  * with keys from the credential source (never Autopilot's environment). The bucket is the
429
427
  * workspace's, as today. `make` is the knowledge base constructor (the S3 one by default).
430
428
  */
431
- export declare function s3Knowledge(config: NonNullable<HostedConfig["knowledge"]>, credentials: CredentialSource, make?: (params: Record<string, unknown>) => KnowledgeBaseLike): (assistant: SessionAssistant, scope: {
429
+ export declare function s3Knowledge(config: NonNullable<HostedConfig["knowledge"]>, credentials: CredentialSource, make?: (params: Record<string, unknown>) => KnowledgeBaseLike): (agent: SessionAgent, scope: {
432
430
  workspaceRef?: string;
433
- }) => Promise<KnowledgePort | undefined>;
431
+ }, usage?: UsageSink) => Promise<KnowledgePort | undefined>;
434
432
  /** The handler options the hosted Autopilot runs with, built from its config. */
435
433
  export declare function hostedOptions(config: HostedConfig, deps?: {
436
434
  prisma?: any;
@@ -440,17 +438,17 @@ export declare function hostedOptions(config: HostedConfig, deps?: {
440
438
  * like any). A port that fails to build is logged and answers nothing: the eager promise always
441
439
  * has a handler, so a failure never becomes an unhandled rejection (RV-B5).
442
440
  */
443
- export declare function eagerKnowledge(build: (assistant: SessionAssistant, scope: {
441
+ export declare function eagerKnowledge(build: (agent: SessionAgent, scope: {
444
442
  workspaceRef?: string;
445
- }) => Promise<KnowledgePort | undefined>): (assistant: SessionAssistant, scope: {
443
+ }, usage?: UsageSink) => Promise<KnowledgePort | undefined>): (agent: SessionAgent, scope: {
446
444
  workspaceRef?: string;
447
- }) => KnowledgePort;
445
+ }, usage?: UsageSink) => KnowledgePort;
448
446
  /**
449
- * The app's assistant only when the app belongs to the workspace the gateway signed for: one
447
+ * The app's agent only when the app belongs to the workspace AppBridge signed for: one
450
448
  * secret serves every Autopilot app, so a session is bound to its app and workspace
451
449
  * (voice-session, "Connection requests are bound to the app").
452
450
  */
453
- export declare function signedWorkspaceOnly(session: Pick<Session, "appRef" | "signedFor">, loaded: LoadedAssistant | undefined): LoadedAssistant | undefined;
451
+ export declare function signedWorkspaceOnly(session: Pick<Session, "appRef" | "signedFor">, loaded: LoadedAgent | undefined): LoadedAgent | undefined;
454
452
  /** Starts the hosted Autopilot. */
455
453
  export declare function startHostedAutopilot(config: HostedConfig): Promise<VoiceServer>;
456
454
  export {};
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
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;
36
+ exports.FileWorkspaceSecrets = exports.CellSecretCredentials = exports.DatabaseWorkspaceSecrets = exports.PrismaAgentSource = exports.FileAgentSource = exports.AUTOPILOT_CONFIG_DIRS = exports.hostedConfigSchema = void 0;
37
37
  exports.findHostedConfig = findHostedConfig;
38
38
  exports.loadHostedConfig = loadHostedConfig;
39
39
  exports.s3Knowledge = s3Knowledge;
@@ -66,10 +66,11 @@ const logger_1 = require("@fonoster/logger");
66
66
  const voice_1 = require("@fonoster/voice");
67
67
  const yaml = __importStar(require("js-yaml"));
68
68
  const zod_1 = require("zod");
69
+ const MeteredEmbeddings_1 = require("../knowledge/MeteredEmbeddings");
69
70
  const createAutopilot_1 = require("./createAutopilot");
70
71
  const knowledge_1 = require("./knowledge");
71
72
  const llm_1 = require("./llm");
72
- // The hosted Autopilot (rework-autopilot A2, task 4.3): an ordinary VoiceServer that the gateway
73
+ // The hosted Autopilot (rework-autopilot A2, task 4.3): an ordinary VoiceServer that AppBridge
73
74
  // dials like any app. Everything it needs comes from one operator config file; no vendor key is
74
75
  // read from Autopilot's environment by name (A8, A11), only through credential refs.
75
76
  const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
@@ -87,7 +88,7 @@ exports.hostedConfigSchema = zod_1.z
87
88
  * FONOSTER_ALLOWED_DESTINATIONS.
88
89
  */
89
90
  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
+ /** The secret AppBridge signs Autopilot sessions with (one for every Autopilot app). */
91
92
  signingSecret: secretRef,
92
93
  /**
93
94
  * The URL Autopilot apps point to, exactly as the apps have it (the apiserver's
@@ -104,10 +105,10 @@ exports.hostedConfigSchema = zod_1.z
104
105
  })
105
106
  }),
106
107
  /**
107
- * Apps and their assistants: a file `{apps: {<appRef>: {workspaceRef?, assistant}}}` (JSON or
108
+ * Apps and their agents: a file `{apps: {<appRef>: {workspaceRef?, agent}}}` (JSON or
108
109
  * YAML), or `database`: the apps' Intelligence rows, cached for `cacheMs` (CP-B11).
109
110
  */
110
- assistants: zod_1.z.union([
111
+ agents: zod_1.z.union([
111
112
  zod_1.z.object({ file: zod_1.z.string() }).strict(),
112
113
  zod_1.z
113
114
  .object({
@@ -163,7 +164,7 @@ exports.hostedConfigSchema = zod_1.z
163
164
  })
164
165
  .strict()
165
166
  .optional(),
166
- /** Endpoint overrides by adapter or provider id (e.g. a local ollama, the fake LLM). */
167
+ /** Endpoint overrides by provider or adapter id (e.g. a local ollama); the catalog's otherwise. */
167
168
  endpoints: zod_1.z.record(zod_1.z.string()).default({}),
168
169
  /** The S3 + Unstructured knowledge bases; keys by credentialRef (task 4.1). */
169
170
  knowledge: zod_1.z
@@ -187,8 +188,7 @@ exports.hostedConfigSchema = zod_1.z
187
188
  .strict()
188
189
  .optional(),
189
190
  /** OTLP export (empty = off): `otlpEndpoint: "${OTEL_EXPORTER_OTLP_ENDPOINT:-}"`. */
190
- telemetry: common_1.telemetrySchema,
191
- testMode: zod_1.z.boolean().default(false)
191
+ telemetry: common_1.telemetrySchema
192
192
  })
193
193
  .strict();
194
194
  function readStructured(file) {
@@ -216,16 +216,12 @@ function loadHostedConfig(file, env = process.env) {
216
216
  schema: exports.hostedConfigSchema,
217
217
  argv: ["--config", file],
218
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
- }
219
+ interpolatePaths: ["*"]
224
220
  });
225
221
  const dir = path.dirname(file);
226
222
  const rel = (p) => (path.isAbsolute(p) ? p : path.join(dir, p));
227
223
  const withFile = (c) => "file" in c ? { ...c, file: rel(c.file) } : c;
228
- const needsDb = !("file" in config.assistants) ||
224
+ const needsDb = !("file" in config.agents) ||
229
225
  !("file" in config.registry) ||
230
226
  (config.workspaceSecrets &&
231
227
  "source" in config.workspaceSecrets &&
@@ -234,7 +230,7 @@ function loadHostedConfig(file, env = process.env) {
234
230
  throw new Error("source: database needs a database section");
235
231
  return {
236
232
  ...config,
237
- assistants: withFile(config.assistants),
233
+ agents: withFile(config.agents),
238
234
  registry: withFile(config.registry),
239
235
  ...(config.workspaceSecrets
240
236
  ? { workspaceSecrets: withFile(config.workspaceSecrets) }
@@ -242,8 +238,8 @@ function loadHostedConfig(file, env = process.env) {
242
238
  };
243
239
  }
244
240
  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 {
241
+ /** Apps and agents from a file (a stand-in until agents come from the control plane). */
242
+ class FileAgentSource {
247
243
  constructor(file) {
248
244
  this.file = file;
249
245
  }
@@ -251,16 +247,16 @@ class FileAssistantSource {
251
247
  const data = readStructured(this.file);
252
248
  const app = data.apps?.[appRef];
253
249
  return app
254
- ? { assistant: app.assistant, workspaceRef: app.workspaceRef }
250
+ ? { agent: app.agent, workspaceRef: app.workspaceRef }
255
251
  : undefined;
256
252
  }
257
253
  }
258
- exports.FileAssistantSource = FileAssistantSource;
254
+ exports.FileAgentSource = FileAgentSource;
259
255
  /**
260
- * Assistants from the control plane: an Autopilot app's Intelligence row holds its assistant
256
+ * Agents from the control plane: an Autopilot app's Intelligence row holds its agent
261
257
  * (CP-B3). Each app is cached for `cacheMs` (misses too), so edits show up within seconds.
262
258
  */
263
- class PrismaAssistantSource {
259
+ class PrismaAgentSource {
264
260
  constructor(
265
261
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
266
262
  prisma, options = {}) {
@@ -278,13 +274,13 @@ class PrismaAssistantSource {
278
274
  include: { intelligence: true }
279
275
  });
280
276
  const value = row && row.type === "AUTOPILOT" && row.intelligence?.config
281
- ? { assistant: row.intelligence.config, workspaceRef: row.accessKeyId }
277
+ ? { agent: row.intelligence.config, workspaceRef: row.accessKeyId }
282
278
  : undefined;
283
279
  this.cache.set(appRef, { at: now, value });
284
280
  return value;
285
281
  }
286
282
  }
287
- exports.PrismaAssistantSource = PrismaAssistantSource;
283
+ exports.PrismaAgentSource = PrismaAgentSource;
288
284
  /** Workspace vendor keys from the database, looked up by the credentialRef's providers. */
289
285
  class DatabaseWorkspaceSecrets {
290
286
  constructor(keys, providers) {
@@ -323,13 +319,13 @@ class FileWorkspaceSecrets {
323
319
  }
324
320
  exports.FileWorkspaceSecrets = FileWorkspaceSecrets;
325
321
  /**
326
- * Builds the knowledge port for an assistant from the operator's S3 + Unstructured settings,
322
+ * Builds the knowledge port for an agent from the operator's S3 + Unstructured settings,
327
323
  * with keys from the credential source (never Autopilot's environment). The bucket is the
328
324
  * workspace's, as today. `make` is the knowledge base constructor (the S3 one by default).
329
325
  */
330
326
  function s3Knowledge(config, credentials, make) {
331
- return async (assistant, scope) => {
332
- const documents = assistant.languageModel.knowledgeBase.map((d) => d.document);
327
+ return async (agent, scope, usage) => {
328
+ const documents = agent.languageModel.knowledgeBase.map((d) => d.document);
333
329
  if (!documents.length || !scope.workspaceRef)
334
330
  return undefined;
335
331
  const key = (ref) => credentials.get(ref, scope);
@@ -337,7 +333,7 @@ function s3Knowledge(config, credentials, make) {
337
333
  key(config.s3.accessKeyIdRef),
338
334
  key(config.s3.secretAccessKeyRef),
339
335
  key(config.unstructured.apiKeyRef),
340
- key(config.embeddings.apiKeyRef)
336
+ (0, llm_1.resolveCredential)(credentials, config.embeddings.apiKeyRef, scope)
341
337
  ]);
342
338
  const params = {
343
339
  bucket: scope.workspaceRef.toLowerCase(),
@@ -353,7 +349,9 @@ function s3Knowledge(config, credentials, make) {
353
349
  },
354
350
  unstructuredAPIURL: config.unstructured.url,
355
351
  unstructuredAPIKey: unstructuredKey ?? "",
356
- embeddingsApiKey: embeddingsKey ?? ""
352
+ embeddingsApiKey: embeddingsKey?.key ?? "",
353
+ embeddingsKeySource: embeddingsKey?.source ?? "platform",
354
+ usage
357
355
  };
358
356
  const kb = (make ?? defaultKnowledgeBase)(params);
359
357
  kb.load().catch((e) => logger.warn(`knowledge base did not load: ${e.message}`));
@@ -365,11 +363,14 @@ function defaultKnowledgeBase(params) {
365
363
  // port still uses.
366
364
  // eslint-disable-next-line @typescript-eslint/no-var-requires
367
365
  const { S3KnowledgeBase } = require("../knowledge/S3KnowledgeBase");
368
- // eslint-disable-next-line @typescript-eslint/no-var-requires
369
- const { OpenAIEmbeddings } = require("@langchain/openai");
370
366
  return new S3KnowledgeBase({
371
367
  ...params,
372
- embeddings: new OpenAIEmbeddings({ apiKey: params.embeddingsApiKey })
368
+ // Metered: the vendor's token usage reaches the call (add-usage-metering).
369
+ embeddings: new MeteredEmbeddings_1.MeteredEmbeddings({
370
+ apiKey: String(params.embeddingsApiKey ?? ""),
371
+ keySource: params.embeddingsKeySource,
372
+ sink: params.usage
373
+ })
373
374
  });
374
375
  }
375
376
  /** Opens the control-plane database named in the config (once). */
@@ -407,17 +408,11 @@ deps = {}) {
407
408
  let prisma = deps.prisma;
408
409
  const db = () => (prisma ?? (prisma = databaseOf(config)));
409
410
  const store = registryStore(config, db);
410
- const registry = new common_1.Registry.Registry({
411
- store,
412
- testMode: config.testMode
413
- });
411
+ const registry = new common_1.Registry.Registry({ store });
414
412
  const providers = () => store
415
413
  .all()
416
- .filter((e) => e.kind === "provider")
417
- .map((e) => ({
418
- id: e.id,
419
- credentialRef: e.credentialRef
420
- }));
414
+ .filter((e) => e.kind === "provider" && Boolean(e.credentialRef))
415
+ .map((e) => ({ id: e.id, credentialRef: e.credentialRef }));
421
416
  const workspace = config.workspaceSecrets;
422
417
  const credentials = new llm_1.LayeredCredentials([
423
418
  ...(workspace
@@ -431,16 +426,16 @@ deps = {}) {
431
426
  : []),
432
427
  new llm_1.ConfigCredentials(config.credentials)
433
428
  ]);
434
- const assistants = "file" in config.assistants
435
- ? new FileAssistantSource(config.assistants.file)
436
- : new PrismaAssistantSource(db(), {
437
- cacheMs: config.assistants.cacheMs
429
+ const agents = "file" in config.agents
430
+ ? new FileAgentSource(config.agents.file)
431
+ : new PrismaAgentSource(db(), {
432
+ cacheMs: config.agents.cacheMs
438
433
  });
439
434
  const knowledge = config.knowledge
440
435
  ? s3Knowledge(config.knowledge, credentials)
441
436
  : undefined;
442
437
  return {
443
- load: async (session) => signedWorkspaceOnly(session, await assistants.get(session.appRef)),
438
+ load: async (session) => signedWorkspaceOnly(session, await agents.get(session.appRef)),
444
439
  allowedDestinations: config.allowedDestinations,
445
440
  llm: new llm_1.RegistryLlmResolver({
446
441
  registry,
@@ -456,9 +451,9 @@ deps = {}) {
456
451
  * has a handler, so a failure never becomes an unhandled rejection (RV-B5).
457
452
  */
458
453
  function eagerKnowledge(build) {
459
- return (assistant, scope) => {
454
+ return (agent, scope, usage) => {
460
455
  const port = Promise.resolve()
461
- .then(() => build(assistant, scope))
456
+ .then(() => build(agent, scope, usage))
462
457
  .catch((e) => {
463
458
  logger.warn(`knowledge port unavailable: ${e?.message ?? e}`);
464
459
  return undefined;
@@ -472,7 +467,7 @@ function eagerKnowledge(build) {
472
467
  };
473
468
  }
474
469
  /**
475
- * The app's assistant only when the app belongs to the workspace the gateway signed for: one
470
+ * The app's agent only when the app belongs to the workspace AppBridge signed for: one
476
471
  * secret serves every Autopilot app, so a session is bound to its app and workspace
477
472
  * (voice-session, "Connection requests are bound to the app").
478
473
  */
@@ -18,12 +18,12 @@
18
18
  */
19
19
  export * from "./Agent";
20
20
  export * from "./config";
21
- export * from "./convertLegacyAssistant";
22
21
  export * from "./createAutopilot";
23
22
  export * from "./hosted";
24
23
  export * from "./knowledge";
25
24
  export * from "./llm";
26
25
  export * from "./machine";
27
26
  export * from "./sentences";
27
+ export * from "./telemetry";
28
28
  export * from "./think";
29
29
  export * from "./tools";
@@ -34,12 +34,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
34
34
  */
35
35
  __exportStar(require("./Agent"), exports);
36
36
  __exportStar(require("./config"), exports);
37
- __exportStar(require("./convertLegacyAssistant"), exports);
38
37
  __exportStar(require("./createAutopilot"), exports);
39
38
  __exportStar(require("./hosted"), exports);
40
39
  __exportStar(require("./knowledge"), exports);
41
40
  __exportStar(require("./llm"), exports);
42
41
  __exportStar(require("./machine"), exports);
43
42
  __exportStar(require("./sentences"), exports);
43
+ __exportStar(require("./telemetry"), exports);
44
44
  __exportStar(require("./think"), exports);
45
45
  __exportStar(require("./tools"), exports);
@@ -19,7 +19,7 @@
19
19
  /**
20
20
  * The knowledge port (rework-autopilot A8): one operation, find what is relevant to a question.
21
21
  * Implementations (S3 + Unstructured today) are wired by the operator; their keys never live in
22
- * the assistant config.
22
+ * the agent config.
23
23
  */
24
24
  export interface KnowledgePort {
25
25
  find(question: string, signal: AbortSignal): Promise<string>;
@@ -37,6 +37,20 @@ export declare class ConfigCredentials implements CredentialSource {
37
37
  }>);
38
38
  get(ref: string): string;
39
39
  }
40
+ /**
41
+ * A workspace's own key could not be read (its store failed). It never falls back to the platform
42
+ * key (remove-platform-key-fallback): the session ends with reason key_unavailable.
43
+ */
44
+ export declare class KeyUnavailableError extends Error {
45
+ readonly ref: string;
46
+ readonly reason = "key_unavailable";
47
+ constructor(ref: string, cause?: unknown);
48
+ }
49
+ /** A key and whose it is: the workspace's own (or its private region's), or the platform's. */
50
+ export type ResolvedCredential = {
51
+ key: string;
52
+ source: "workspace" | "platform";
53
+ };
40
54
  /** Workspace Secrets (BYOK) in front of platform credentials. */
41
55
  export declare class LayeredCredentials implements CredentialSource {
42
56
  private readonly sources;
@@ -44,7 +58,18 @@ export declare class LayeredCredentials implements CredentialSource {
44
58
  get(ref: string, scope: {
45
59
  workspaceRef?: string;
46
60
  }): Promise<string>;
61
+ /**
62
+ * The first key found and whose it is (add-usage-metering): the operator's config holds the
63
+ * platform keys; every other source holds the workspace's own.
64
+ */
65
+ resolve(ref: string, scope: {
66
+ workspaceRef?: string;
67
+ }): Promise<ResolvedCredential | undefined>;
47
68
  }
69
+ /** A credential and its source; a source that cannot tell is the platform's. */
70
+ export declare function resolveCredential(credentials: CredentialSource, ref: string, scope: {
71
+ workspaceRef?: string;
72
+ }): Promise<ResolvedCredential | undefined>;
48
73
  /** Adapters whose LLMs speak OpenAI-compatible Chat Completions (moved to @fonoster/community/openai). */
49
74
  export { OPENAI_COMPATIBLE };
50
75
  export declare class LlmResolutionError extends Error {
@@ -53,6 +78,8 @@ export type ResolvedLlm = {
53
78
  think: Think;
54
79
  /** The id actually used (a retired id resolves to its replacement). */
55
80
  id: string;
81
+ /** Whose key the model is called with; keyless providers count as the platform's. */
82
+ keySource: "workspace" | "platform";
56
83
  /** For the call record: provider.deprecated_used / provider.retired_used (registry PR4). */
57
84
  event?: {
58
85
  type: string;
@@ -71,7 +98,7 @@ export interface LlmResolver {
71
98
  export type RegistryLlmResolverOptions = {
72
99
  registry: Registry.Registry;
73
100
  credentials: CredentialSource;
74
- /** Endpoint overrides by adapter or provider id (required for fake; e.g. a local ollama). */
101
+ /** Endpoint overrides by provider or adapter id (e.g. a local ollama); win over the catalog's. */
75
102
  endpoints?: Record<string, string>;
76
103
  };
77
104
  export declare class RegistryLlmResolver implements LlmResolver {