@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.
Files changed (88) hide show
  1. package/dist/{assistants → agents}/index.d.ts +1 -1
  2. package/dist/{assistants → agents}/index.js +1 -1
  3. package/dist/{assistants/sessionAssistantSchema.d.ts → agents/sessionAgentSchema.d.ts} +20 -6
  4. package/dist/{assistants/sessionAssistantSchema.js → agents/sessionAgentSchema.js} +16 -11
  5. package/dist/cellsecrets/index.d.ts +2 -2
  6. package/dist/cellsecrets/index.js +3 -3
  7. package/dist/config/cli/render.d.ts +1 -1
  8. package/dist/config/cli/render.js +3 -3
  9. package/dist/config/configHints.d.ts +1 -14
  10. package/dist/config/configHints.js +16 -30
  11. package/dist/config/index.d.ts +0 -1
  12. package/dist/config/index.js +0 -1
  13. package/dist/config/interpolateEnv.d.ts +1 -1
  14. package/dist/config/loadServiceConfig.d.ts +0 -5
  15. package/dist/config/loadServiceConfig.js +3 -12
  16. package/dist/constants.d.ts +1 -3
  17. package/dist/constants.js +3 -5
  18. package/dist/errors/handleZodError.js +3 -1
  19. package/dist/filters/index.d.ts +70 -0
  20. package/dist/filters/index.js +101 -0
  21. package/dist/identity/roles.js +11 -1
  22. package/dist/index.d.ts +4 -1
  23. package/dist/index.js +5 -2
  24. package/dist/protos/applications.proto +16 -2
  25. package/dist/protos/authz.proto +3 -0
  26. package/dist/protos/billing.proto +92 -0
  27. package/dist/protos/calls.proto +4 -0
  28. package/dist/protos/capabilities.proto +8 -0
  29. package/dist/protos/providers.proto +6 -2
  30. package/dist/protos/roomr.proto +146 -0
  31. package/dist/protos/session.proto +194 -0
  32. package/dist/protos/usage.proto +59 -0
  33. package/dist/registry/Registry.d.ts +13 -5
  34. package/dist/registry/Registry.js +49 -35
  35. package/dist/registry/cli.js +19 -7
  36. package/dist/registry/features.d.ts +2 -2
  37. package/dist/registry/features.js +7 -5
  38. package/dist/registry/prisma.d.ts +2 -2
  39. package/dist/registry/prisma.js +26 -18
  40. package/dist/registry/types.d.ts +17 -5
  41. package/dist/registry/types.js +2 -0
  42. package/dist/roomr/health.d.ts +9 -0
  43. package/dist/roomr/health.js +75 -0
  44. package/dist/roomr/index.d.ts +75 -0
  45. package/dist/roomr/index.js +146 -0
  46. package/dist/roomr/session.d.ts +133 -0
  47. package/dist/roomr/session.js +132 -0
  48. package/dist/session/schema.d.ts +196 -7
  49. package/dist/session/schema.js +59 -8
  50. package/dist/sessions/index.d.ts +15 -12
  51. package/dist/sessions/index.js +15 -12
  52. package/dist/telemetry.d.ts +1 -1
  53. package/dist/usage/CallUsage.d.ts +38 -0
  54. package/dist/usage/CallUsage.js +76 -0
  55. package/dist/{utils/types.d.ts → usage/index.d.ts} +4 -5
  56. package/dist/usage/index.js +38 -0
  57. package/dist/usage/meters.d.ts +113 -0
  58. package/dist/usage/meters.js +90 -0
  59. package/dist/usage/types.d.ts +159 -0
  60. package/dist/usage/types.js +84 -0
  61. package/dist/usage/usageId.d.ts +6 -0
  62. package/dist/usage/usageId.js +45 -0
  63. package/dist/utils/index.d.ts +0 -2
  64. package/dist/utils/index.js +0 -2
  65. package/dist/utils/withErrorHandling.js +1 -1
  66. package/dist/validators/calls.d.ts +5 -2
  67. package/dist/validators/calls.js +8 -1
  68. package/dist/validators/sip.d.ts +2 -2
  69. package/dist/validators/sip.js +3 -3
  70. package/dist/webhooks/bus.d.ts +29 -10
  71. package/dist/webhooks/bus.js +58 -50
  72. package/dist/webhooks/events.d.ts +1 -1
  73. package/dist/webhooks/events.js +1 -1
  74. package/package.json +9 -8
  75. package/schema/session.v1.json +82 -2
  76. package/dist/config/integrationsSchema.d.ts +0 -32
  77. package/dist/config/integrationsSchema.js +0 -56
  78. package/dist/utils/createFetchSingleCallByCallId.d.ts +0 -22
  79. package/dist/utils/createFetchSingleCallByCallId.js +0 -33
  80. package/dist/utils/types.js +0 -2
  81. /package/dist/{assistants → agents}/testCasesSchema.d.ts +0 -0
  82. /package/dist/{assistants → agents}/testCasesSchema.js +0 -0
  83. /package/dist/{assistants → agents}/tools/index.d.ts +0 -0
  84. /package/dist/{assistants → agents}/tools/index.js +0 -0
  85. /package/dist/{assistants → agents}/tools/propertySchema.d.ts +0 -0
  86. /package/dist/{assistants → agents}/tools/propertySchema.js +0 -0
  87. /package/dist/{assistants → agents}/tools/toolSchema.d.ts +0 -0
  88. /package/dist/{assistants → agents}/tools/toolSchema.js +0 -0
@@ -0,0 +1,6 @@
1
+ import { UsageLine } from "./types";
2
+ /**
3
+ * The id of a usage line (design D7): derived from what it measures, so reporting the same usage
4
+ * twice yields the same id. `scope` is the callRef, the eval step key, or workspaceRef + day.
5
+ */
6
+ export declare function usageId(scope: string, line: Pick<UsageLine, "meter" | "provider" | "model" | "keySource">): string;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usageId = usageId;
4
+ /**
5
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
+ * http://github.com/fonoster/fonoster
7
+ *
8
+ * This file is part of Fonoster
9
+ *
10
+ * Licensed under the MIT License (the "License");
11
+ * you may not use this file except in compliance with
12
+ * the License. You may obtain a copy of the License at
13
+ *
14
+ * https://opensource.org/licenses/MIT
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ */
22
+ const node_crypto_1 = require("node:crypto");
23
+ const ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
24
+ function base32(bytes) {
25
+ var _a;
26
+ const bits = Array.from(bytes, (b) => b.toString(2).padStart(8, "0")).join("");
27
+ return ((_a = bits.match(/.{1,5}/g)) !== null && _a !== void 0 ? _a : [])
28
+ .map((chunk) => ALPHABET[parseInt(chunk.padEnd(5, "0"), 2)])
29
+ .join("");
30
+ }
31
+ /**
32
+ * The id of a usage line (design D7): derived from what it measures, so reporting the same usage
33
+ * twice yields the same id. `scope` is the callRef, the eval step key, or workspaceRef + day.
34
+ */
35
+ function usageId(scope, line) {
36
+ var _a, _b, _c;
37
+ const key = [
38
+ scope,
39
+ line.meter,
40
+ (_a = line.provider) !== null && _a !== void 0 ? _a : "",
41
+ (_b = line.model) !== null && _b !== void 0 ? _b : "",
42
+ (_c = line.keySource) !== null && _c !== void 0 ? _c : ""
43
+ ].join("|");
44
+ return ("usg_" + base32((0, node_crypto_1.createHash)("sha256").update(key).digest()).slice(0, 26));
45
+ }
@@ -27,7 +27,5 @@ export * from "./toPascalCase";
27
27
  export * from "./withErrorHandling";
28
28
  export * from "./withErrorHandlingAndValidation";
29
29
  export * from "./withValidation";
30
- export * from "./types";
31
- export * from "./createFetchSingleCallByCallId";
32
30
  export * from "./sendHttpRequest";
33
31
  export * from "./logUnhandledRejections";
@@ -43,7 +43,5 @@ __exportStar(require("./toPascalCase"), exports);
43
43
  __exportStar(require("./withErrorHandling"), exports);
44
44
  __exportStar(require("./withErrorHandlingAndValidation"), exports);
45
45
  __exportStar(require("./withValidation"), exports);
46
- __exportStar(require("./types"), exports);
47
- __exportStar(require("./createFetchSingleCallByCallId"), exports);
48
46
  __exportStar(require("./sendHttpRequest"), exports);
49
47
  __exportStar(require("./logUnhandledRejections"), exports);
@@ -28,7 +28,7 @@ exports.withErrorHandling = withErrorHandling;
28
28
  * See the License for the specific language governing permissions and
29
29
  * limitations under the License.
30
30
  */
31
- /* eslint-disable @typescript-eslint/ban-types */
31
+ /* eslint-disable @typescript-eslint/no-unsafe-function-type */
32
32
  const errors_1 = require("../errors");
33
33
  function withErrorHandling(fn) {
34
34
  return (call, callback) => __awaiter(this, void 0, void 0, function* () {
@@ -27,14 +27,14 @@ declare const createCallRequestSchema: z.ZodObject<{
27
27
  }, "strip", z.ZodTypeAny, {
28
28
  timeout?: number;
29
29
  metadata?: Record<string, any>;
30
- from?: string;
31
30
  to?: string;
31
+ from?: string;
32
32
  appRef?: string;
33
33
  }, {
34
34
  timeout?: number;
35
35
  metadata?: Record<string, any>;
36
- from?: string;
37
36
  to?: string;
37
+ from?: string;
38
38
  appRef?: string;
39
39
  }>;
40
40
  declare const getCallRequestSchema: z.ZodObject<{
@@ -50,10 +50,12 @@ declare const listCallsRequestSchema: z.ZodObject<{
50
50
  pageSize: z.ZodOptional<z.ZodNumber>;
51
51
  type: z.ZodOptional<z.ZodNativeEnum<typeof CallType>>;
52
52
  status: z.ZodOptional<z.ZodNativeEnum<typeof CallStatus>>;
53
+ appRef: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
53
54
  pageToken: z.ZodOptional<z.ZodString>;
54
55
  }, "strip", z.ZodTypeAny, {
55
56
  type?: CallType;
56
57
  status?: CallStatus;
58
+ appRef?: string;
57
59
  after?: string;
58
60
  before?: string;
59
61
  pageSize?: number;
@@ -61,6 +63,7 @@ declare const listCallsRequestSchema: z.ZodObject<{
61
63
  }, {
62
64
  type?: CallType;
63
65
  status?: CallStatus;
66
+ appRef?: string;
64
67
  after?: string;
65
68
  before?: string;
66
69
  pageSize?: number;
@@ -24,7 +24,7 @@ const zod_1 = require("zod");
24
24
  const messages_1 = require("../messages");
25
25
  const sip_1 = require("./sip");
26
26
  // `from` is one of the workspace's Numbers (the Calls service checks ownership); it travels as
27
- // X-Dod-Number and picks the trunk, so it is an E.164 number. `to` is what the media node dials.
27
+ // X-Dod-Number and picks the trunk, so it is an E.164 number. `to` is what the Roomr node dials.
28
28
  const createCallRequestSchema = zod_1.z.object({
29
29
  from: sip_1.e164Schema,
30
30
  to: sip_1.sipTargetSchema,
@@ -60,6 +60,13 @@ const listCallsRequestSchema = zod_1.z.object({
60
60
  status: zod_1.z
61
61
  .nativeEnum(types_1.CallStatus, { message: "Invalid call status" })
62
62
  .optional(),
63
+ // Empty or unset means no filter (proto3 sends ""); anything else must be an app ref.
64
+ appRef: zod_1.z
65
+ .string()
66
+ .refine((v) => v === "" || zod_1.z.string().uuid().safeParse(v).success, {
67
+ message: "Invalid application reference"
68
+ })
69
+ .optional(),
63
70
  pageToken: zod_1.z.string().optional()
64
71
  });
65
72
  exports.listCallsRequestSchema = listCallsRequestSchema;
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { z } from "zod";
20
20
  /**
21
- * Headers the platform (Routr, the media node, the gateway, the apiserver) sets and trusts: they
21
+ * Headers the platform (Routr, the Roomr node, AppBridge, the apiserver) sets and trusts: they
22
22
  * pick the app, the call, the workspace and the trunk. Callers and apps may never set them.
23
23
  */
24
24
  export declare const RESERVED_SIP_HEADERS: ReadonlySet<string>;
@@ -34,7 +34,7 @@ export declare const SIP_TARGET: RegExp;
34
34
  export declare function sipHeaderProblem(name: string, value: unknown): string | undefined;
35
35
  /** Why a set of custom SIP headers may not be sent (undefined: they may). */
36
36
  export declare function sipHeadersProblem(headers: unknown): string | undefined;
37
- /** Whether `to` is a target the media node may dial or refer to. */
37
+ /** Whether `to` is a target the Roomr node may dial or refer to. */
38
38
  export declare function isSipTarget(to: unknown): to is string;
39
39
  export declare const e164Schema: z.ZodString;
40
40
  export declare const sipTargetSchema: z.ZodString;
@@ -27,7 +27,7 @@ const zod_1 = require("zod");
27
27
  // from/to, the `transfer` command's target and headers). sipgo does not escape header values or
28
28
  // URI users, so anything that reaches an INVITE or REFER must be safe as-is.
29
29
  /**
30
- * Headers the platform (Routr, the media node, the gateway, the apiserver) sets and trusts: they
30
+ * Headers the platform (Routr, the Roomr node, AppBridge, the apiserver) sets and trusts: they
31
31
  * pick the app, the call, the workspace and the trunk. Callers and apps may never set them.
32
32
  */
33
33
  exports.RESERVED_SIP_HEADERS = new Set([
@@ -40,7 +40,7 @@ exports.RESERVED_SIP_HEADERS = new Set([
40
40
  "X-Is-Api-Originated-Type",
41
41
  "X-Connect-Token",
42
42
  "X-Edgeport-Ref",
43
- "X-Gateway-Auth",
43
+ "X-AppBridge-Auth",
44
44
  "X-Request-Uri",
45
45
  "X-Session-Count"
46
46
  ].map((h) => h.toLowerCase()));
@@ -87,7 +87,7 @@ function sipHeadersProblem(headers) {
87
87
  }
88
88
  return undefined;
89
89
  }
90
- /** Whether `to` is a target the media node may dial or refer to. */
90
+ /** Whether `to` is a target the Roomr node may dial or refer to. */
91
91
  function isSipTarget(to) {
92
92
  return typeof to === "string" && exports.SIP_TARGET.test(to);
93
93
  }
@@ -17,18 +17,37 @@ export type BusConnection = {
17
17
  export declare function busWebhookPublisher(nc: Pick<BusConnection, "publish">): (event: WebhookEvent) => void;
18
18
  /** Parses a bus message; undefined for anything that is not a v1 webhook event. */
19
19
  export declare function parseBusEvent(data: Uint8Array): WebhookEvent | undefined;
20
- /** Where media nodes report calls no gateway claimed (the last token is never a room ref). */
21
- export declare const NODE_CALL_FAILED_SUBJECT = "media.*.events.call-failed";
22
- /** The reasons a node may give: it only ever fails a call nobody claimed. */
23
- export declare const NODE_CALL_FAILED_REASONS: readonly ["node_error"];
20
+ /** Where Roomr nodes publish a call's events (`roomr.events.<call_ref>`; room events have one more token). */
21
+ export declare const ROOMR_CALL_EVENTS_SUBJECT = "roomr.events.*";
24
22
  /**
25
- * The call.failed event for a media node's report on `subject`
26
- * (`media.<nodeId>.events.call-failed`); undefined for anything else. Only `call.failed` with a
27
- * node reason is accepted, the report must name the node of its subject, and the workspace is left
28
- * empty for the fan-out to resolve from the app: a node never chooses the workspace, the event type
29
- * or extra data. The id depends on the node and the call, so a repeated report is delivered once.
23
+ * The `call.failed` reasons an unanswered outbound call can end with (the node's `cause`); any
24
+ * other cause is reported as `node_error`.
30
25
  */
31
- export declare function nodeCallFailedEvent(subject: string, data: Uint8Array, now?: Date): WebhookEvent | undefined;
26
+ export declare const UNANSWERED_CALL_REASONS: readonly ["busy", "no_answer", "rejected", "removed", "node_error"];
27
+ /** An outbound call the callee never answered, as its node's `call.ended` tells it. */
28
+ export type UnansweredCall = {
29
+ callRef: string;
30
+ appRef: string;
31
+ from: string;
32
+ to: string;
33
+ reason: (typeof UNANSWERED_CALL_REASONS)[number];
34
+ sipStatus?: number;
35
+ nodeRef: string;
36
+ };
37
+ /**
38
+ * The unanswered outbound call a Roomr node's event on `subject` (`roomr.events.<call_ref>`)
39
+ * reports, or undefined: only a `call.ended` with `answered: false` of a call the control plane
40
+ * placed (its `metadata.app_ref`, passed through by the node) for the subject's own call. The
41
+ * parties come from the metadata the control plane set; the node never chooses the workspace
42
+ * (left for the fan-out to resolve from the app), the event type or extra data (M6).
43
+ */
44
+ export declare function unansweredOutboundCall(subject: string, event: Record<string, unknown> | undefined): UnansweredCall | undefined;
45
+ /**
46
+ * The `call.failed` event for an unanswered outbound call. Its id depends on the call only, so
47
+ * every replica that sees the node's `call.ended` builds the same event and each endpoint gets one
48
+ * delivery.
49
+ */
50
+ export declare function unansweredCallFailed(call: UnansweredCall, now?: Date): WebhookEvent;
32
51
  export type FanoutOptions = {
33
52
  /** Usually `WebhookDispatcher.publish`: stores one pending delivery per subscribed endpoint. */
34
53
  publish(event: WebhookEvent): Delivery[];
@@ -9,10 +9,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.NODE_CALL_FAILED_REASONS = exports.NODE_CALL_FAILED_SUBJECT = exports.WEBHOOK_EVENTS_SUBJECT = void 0;
12
+ exports.UNANSWERED_CALL_REASONS = exports.ROOMR_CALL_EVENTS_SUBJECT = exports.WEBHOOK_EVENTS_SUBJECT = void 0;
13
13
  exports.busWebhookPublisher = busWebhookPublisher;
14
14
  exports.parseBusEvent = parseBusEvent;
15
- exports.nodeCallFailedEvent = nodeCallFailedEvent;
15
+ exports.unansweredOutboundCall = unansweredOutboundCall;
16
+ exports.unansweredCallFailed = unansweredCallFailed;
16
17
  exports.resolveWorkspace = resolveWorkspace;
17
18
  exports.subscribeWebhookFanout = subscribeWebhookFanout;
18
19
  /**
@@ -37,15 +38,15 @@ const crypto_1 = require("crypto");
37
38
  const util_1 = require("util");
38
39
  const events_1 = require("./events");
39
40
  // Event fan-out from the event bus (add-workspace-webhooks task 2.2, build log LO-B1). Producers
40
- // that have no database (media nodes, gateways in bus mode, other regions) publish events on
41
+ // that have no database (Roomr nodes, appbridges in bus mode, other regions) publish events on
41
42
  // `webhooks.events`; one control-plane replica per event (queue group) fans each event out to the
42
43
  // workspace's subscribed endpoints. A producer that does not know the workspace leaves
43
44
  // `workspaceRef` empty and the fan-out resolves it from `data.appRef`.
44
45
  //
45
- // Media nodes may not publish `webhooks.events` (M6: a node could otherwise forge any event for
46
- // any workspace). A node reports a call no gateway claimed on its own subject,
47
- // `media.<nodeId>.events.call-failed`; the control plane builds the call.failed event from it
48
- // (nodeCallFailedEvent), with the workspace taken from the app and nothing else trusted.
46
+ // Roomr nodes may not publish `webhooks.events` (M6: a node could otherwise forge any event for
47
+ // any workspace). An outbound call that is never answered reaches no AppBridge; the control plane
48
+ // builds its call.failed from the node's own `call.ended` on `roomr.events.<call_ref>`
49
+ // (unansweredOutboundCall), with the workspace taken from the app and nothing else trusted.
49
50
  exports.WEBHOOK_EVENTS_SUBJECT = "webhooks.events";
50
51
  const encoder = new util_1.TextEncoder();
51
52
  const decoder = new util_1.TextDecoder();
@@ -71,63 +72,70 @@ function parseBusEvent(data) {
71
72
  return undefined;
72
73
  return e;
73
74
  }
74
- /** Where media nodes report calls no gateway claimed (the last token is never a room ref). */
75
- exports.NODE_CALL_FAILED_SUBJECT = "media.*.events.call-failed";
76
- /** The reasons a node may give: it only ever fails a call nobody claimed. */
77
- exports.NODE_CALL_FAILED_REASONS = ["node_error"];
75
+ /** Where Roomr nodes publish a call's events (`roomr.events.<call_ref>`; room events have one more token). */
76
+ exports.ROOMR_CALL_EVENTS_SUBJECT = "roomr.events.*";
77
+ /**
78
+ * The `call.failed` reasons an unanswered outbound call can end with (the node's `cause`); any
79
+ * other cause is reported as `node_error`.
80
+ */
81
+ exports.UNANSWERED_CALL_REASONS = [
82
+ "busy",
83
+ "no_answer",
84
+ "rejected",
85
+ "removed",
86
+ "node_error"
87
+ ];
78
88
  const MAX_FIELD = 256;
79
89
  const field = (v, required = false) => typeof v === "string" && v.length <= MAX_FIELD && (!required || v.length > 0)
80
90
  ? v
81
91
  : undefined;
82
92
  /**
83
- * The call.failed event for a media node's report on `subject`
84
- * (`media.<nodeId>.events.call-failed`); undefined for anything else. Only `call.failed` with a
85
- * node reason is accepted, the report must name the node of its subject, and the workspace is left
86
- * empty for the fan-out to resolve from the app: a node never chooses the workspace, the event type
87
- * or extra data. The id depends on the node and the call, so a repeated report is delivered once.
93
+ * The unanswered outbound call a Roomr node's event on `subject` (`roomr.events.<call_ref>`)
94
+ * reports, or undefined: only a `call.ended` with `answered: false` of a call the control plane
95
+ * placed (its `metadata.app_ref`, passed through by the node) for the subject's own call. The
96
+ * parties come from the metadata the control plane set; the node never chooses the workspace
97
+ * (left for the fan-out to resolve from the app), the event type or extra data (M6).
88
98
  */
89
- function nodeCallFailedEvent(subject, data, now = new Date()) {
90
- var _a;
99
+ function unansweredOutboundCall(subject, event) {
100
+ var _a, _b, _c, _d;
91
101
  const tokens = subject.split(".");
92
- if (tokens.length !== 4 ||
93
- tokens[0] !== "media" ||
94
- tokens[2] !== "events" ||
95
- tokens[3] !== "call-failed")
102
+ if (tokens.length !== 3 || tokens[0] !== "roomr" || tokens[1] !== "events")
96
103
  return undefined;
97
- let r;
98
- try {
99
- r = JSON.parse(decoder.decode(data));
100
- }
101
- catch (_b) {
104
+ if (!event || typeof event !== "object" || event.type !== "call.ended")
102
105
  return undefined;
103
- }
104
- if (!r || typeof r !== "object" || r.type !== "call.failed")
106
+ if (event.answered !== false)
107
+ return undefined;
108
+ const callRef = field(event.call_ref, true);
109
+ if (!callRef || callRef !== tokens[2])
105
110
  return undefined;
106
- const nodeId = tokens[1];
107
- const callRef = field(r.callRef, true);
108
- const appRef = field(r.appRef, true);
109
- const from = field(r.from);
110
- const to = field(r.to);
111
- const reason = field(r.reason);
112
- const direction = (_a = r.direction) !== null && _a !== void 0 ? _a : "inbound";
113
- if (r.nodeId !== nodeId ||
114
- !callRef ||
115
- !appRef ||
116
- from === undefined ||
117
- to === undefined ||
118
- !exports.NODE_CALL_FAILED_REASONS.includes(reason !== null && reason !== void 0 ? reason : "") ||
119
- (direction !== "inbound" && direction !== "outbound"))
111
+ const metadata = (event.metadata && typeof event.metadata === "object" ? event.metadata : {});
112
+ const appRef = field(metadata.app_ref, true);
113
+ if (!appRef)
120
114
  return undefined;
115
+ const cause = String((_a = event.cause) !== null && _a !== void 0 ? _a : "");
116
+ const reason = exports.UNANSWERED_CALL_REASONS.includes(cause)
117
+ ? cause
118
+ : "node_error";
119
+ const sipStatus = Number(event.sip_status);
120
+ return Object.assign(Object.assign({ callRef,
121
+ appRef, from: (_b = field(metadata.from)) !== null && _b !== void 0 ? _b : "", to: (_c = field(metadata.to)) !== null && _c !== void 0 ? _c : "", reason }, (Number.isInteger(sipStatus) && sipStatus > 0 ? { sipStatus } : {})), { nodeRef: (_d = field(event.node_ref)) !== null && _d !== void 0 ? _d : "" });
122
+ }
123
+ /**
124
+ * The `call.failed` event for an unanswered outbound call. Its id depends on the call only, so
125
+ * every replica that sees the node's `call.ended` builds the same event and each endpoint gets one
126
+ * delivery.
127
+ */
128
+ function unansweredCallFailed(call, now = new Date()) {
121
129
  const event = (0, events_1.callFailed)("", {
122
- callRef,
123
- appRef,
124
- from,
125
- to,
126
- direction,
127
- reason: reason
130
+ callRef: call.callRef,
131
+ appRef: call.appRef,
132
+ from: call.from,
133
+ to: call.to,
134
+ direction: "outbound",
135
+ reason: call.reason
128
136
  });
129
137
  const digest = (0, crypto_1.createHash)("sha256")
130
- .update(`${nodeId}\n${callRef}`)
138
+ .update(`call.failed\n${call.callRef}`)
131
139
  .digest("hex")
132
140
  .slice(0, 32);
133
141
  return Object.assign(Object.assign({}, event), { id: `evt_node_${digest}`, createdAt: now.toISOString() });
@@ -1,7 +1,7 @@
1
1
  export declare const WEBHOOK_EVENT_TYPES: readonly ["call.started", "call.ended", "call.failed", "call_record.sealed", "provider.deprecated"];
2
2
  export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
3
3
  /**
4
- * Event names of the retired Autopilot assistant webhook (`eventsHook`) and what replaces them
4
+ * Event names of the retired Autopilot agent webhook (`eventsHook`) and what replaces them
5
5
  * (add-call-record 7.3). The record of a conversation (transcript, turns, recording) is in the call
6
6
  * record that `call_record.sealed` links to.
7
7
  */
@@ -30,7 +30,7 @@ exports.WEBHOOK_EVENT_TYPES = [
30
30
  "provider.deprecated"
31
31
  ];
32
32
  /**
33
- * Event names of the retired Autopilot assistant webhook (`eventsHook`) and what replaces them
33
+ * Event names of the retired Autopilot agent webhook (`eventsHook`) and what replaces them
34
34
  * (add-call-record 7.3). The record of a conversation (transcript, turns, recording) is in the call
35
35
  * record that `call_record.sealed` links to.
36
36
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonoster/common",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Common library for Fonoster projects",
5
5
  "author": "Pedro Sanders <psanders@fonoster.com>",
6
6
  "homepage": "https://github.com/fonoster/fonoster#readme",
@@ -15,15 +15,14 @@
15
15
  "prebuild": "rimraf ./dist tsconfig.tsbuildinfo",
16
16
  "build": "tsc -b tsconfig.json",
17
17
  "postbuild": "cp -a src/protos dist",
18
- "clean": "rimraf ./dist node_modules tsconfig.tsbuildinfo",
19
- "generate:session-schema": "tsx scripts/export-session-schema.ts"
18
+ "generate:session-schema": "tsx scripts/export-session-schema.ts",
19
+ "generate:config-schemas": "tsx scripts/export-config-schemas.ts"
20
20
  },
21
21
  "dependencies": {
22
- "@fonoster/logger": "^0.30.0",
23
- "@fonoster/types": "^0.30.0",
22
+ "@fonoster/logger": "^0.31.0",
23
+ "@fonoster/types": "^0.31.0",
24
24
  "@grpc/grpc-js": "~1.14.6",
25
25
  "@grpc/proto-loader": "^0.7.15",
26
- "@influxdata/influxdb-client": "^1.35.0",
27
26
  "@opentelemetry/api": "^1.9.0",
28
27
  "@opentelemetry/resources": "^2.12.0",
29
28
  "@opentelemetry/sdk-trace-base": "^2.12.0",
@@ -58,8 +57,10 @@
58
57
  "bugs": {
59
58
  "url": "https://github.com/fonoster/fonoster/issues"
60
59
  },
61
- "devDependencies": {},
62
- "gitHead": "08c9cfd7932675b6b16111daa4d03f14b4e287d1",
60
+ "devDependencies": {
61
+ "@fonoster/roomr": "0.2.5"
62
+ },
63
+ "gitHead": "f0a5102dddeea0f70f659234108805bc10690f2e",
63
64
  "bin": {
64
65
  "fonoster-registry": "dist/registry/cli.js",
65
66
  "fonoster-config": "dist/config/cli/index.js"
@@ -4,7 +4,7 @@
4
4
  "title": "fonoster.session.v1",
5
5
  "description": "Messages of the Fonoster session protocol (a stream with protocol fonoster). Unknown types and fields must be ignored.",
6
6
  "protocol": "fonoster.session.v1",
7
- "gatewayToApp": {
7
+ "appBridgeToApp": {
8
8
  "session.start": {
9
9
  "type": "object",
10
10
  "required": [
@@ -63,6 +63,9 @@
63
63
  }
64
64
  ]
65
65
  },
66
+ "traceparent": {
67
+ "type": "string"
68
+ },
66
69
  "audio": {
67
70
  "type": "object",
68
71
  "required": [
@@ -417,6 +420,23 @@
417
420
  }
418
421
  }
419
422
  },
423
+ "transfer.answered": {
424
+ "type": "object",
425
+ "required": [
426
+ "type",
427
+ "relatesTo"
428
+ ],
429
+ "properties": {
430
+ "type": {
431
+ "const": "transfer.answered"
432
+ },
433
+ "relatesTo": {
434
+ "type": "string",
435
+ "minLength": 1,
436
+ "maxLength": 128
437
+ }
438
+ }
439
+ },
420
440
  "amd.result": {
421
441
  "type": "object",
422
442
  "required": [
@@ -461,7 +481,7 @@
461
481
  }
462
482
  }
463
483
  },
464
- "appToGateway": {
484
+ "appToAppBridge": {
465
485
  "speak": {
466
486
  "type": "object",
467
487
  "required": [
@@ -786,6 +806,66 @@
786
806
  "type": "object"
787
807
  }
788
808
  }
809
+ },
810
+ "usage.report": {
811
+ "type": "object",
812
+ "required": [
813
+ "type",
814
+ "lines"
815
+ ],
816
+ "properties": {
817
+ "type": {
818
+ "const": "usage.report"
819
+ },
820
+ "id": {
821
+ "type": "string",
822
+ "minLength": 1,
823
+ "maxLength": 128
824
+ },
825
+ "lines": {
826
+ "type": "array",
827
+ "minItems": 1,
828
+ "maxItems": 64,
829
+ "items": {
830
+ "type": "object",
831
+ "required": [
832
+ "meter",
833
+ "quantity",
834
+ "provider",
835
+ "keySource"
836
+ ],
837
+ "additionalProperties": false,
838
+ "properties": {
839
+ "meter": {
840
+ "enum": [
841
+ "llm.tokens.in",
842
+ "llm.tokens.out",
843
+ "llm.tokens.cached",
844
+ "embedding.tokens"
845
+ ]
846
+ },
847
+ "quantity": {
848
+ "type": "integer",
849
+ "minimum": 0
850
+ },
851
+ "provider": {
852
+ "type": "string",
853
+ "minLength": 1
854
+ },
855
+ "model": {
856
+ "type": "string",
857
+ "minLength": 1
858
+ },
859
+ "keySource": {
860
+ "enum": [
861
+ "platform",
862
+ "workspace"
863
+ ]
864
+ }
865
+ }
866
+ }
867
+ }
868
+ }
789
869
  }
790
870
  }
791
871
  }
@@ -1,32 +0,0 @@
1
- import { z } from "zod";
2
- declare const integrationsConfigSchema: z.ZodArray<z.ZodObject<{
3
- name: z.ZodString;
4
- productRef: z.ZodString;
5
- type: z.ZodEnum<["tts", "stt", "llm"]>;
6
- credentials: z.ZodRecord<z.ZodString, z.ZodUnknown>;
7
- }, "strip", z.ZodTypeAny, {
8
- type?: "tts" | "stt" | "llm";
9
- name?: string;
10
- productRef?: string;
11
- credentials?: Record<string, unknown>;
12
- }, {
13
- type?: "tts" | "stt" | "llm";
14
- name?: string;
15
- productRef?: string;
16
- credentials?: Record<string, unknown>;
17
- }>, "many">;
18
- /** Spelled out so callers get required fields regardless of tsconfig strictness. */
19
- type IntegrationsConfig = Array<{
20
- name: string;
21
- productRef: string;
22
- type: "tts" | "stt" | "llm";
23
- credentials: Record<string, unknown>;
24
- }>;
25
- /**
26
- * Reads and validates the retired integrations file. Only `fonoster-migrate-legacy --integrations`
27
- * reads it now, to move its keys into operator config by credentialRef (add-provider-registry 5.3).
28
- * YAML (`.yaml`/`.yml`) or JSON with comments (`.json`/`.jsonc`). Throws with the
29
- * file path when the file can't be read or doesn't validate.
30
- */
31
- declare function getIntegrationsFromFile(pathToIntegrations: string): IntegrationsConfig;
32
- export { integrationsConfigSchema, IntegrationsConfig, getIntegrationsFromFile };