getpatter 0.6.5 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  openclawPostCallNotifier,
58
58
  resolveLogRoot,
59
59
  startSpan
60
- } from "./chunk-CRPJLVHB.mjs";
60
+ } from "./chunk-YJX2EKON.mjs";
61
61
  import {
62
62
  OpenAIRealtime2Adapter,
63
63
  OpenAIRealtimeAdapter,
@@ -106,6 +106,57 @@ init_esm_shims();
106
106
  // src/client.ts
107
107
  init_esm_shims();
108
108
 
109
+ // src/telephony/twilio.ts
110
+ init_esm_shims();
111
+ var Carrier2 = class {
112
+ kind = "twilio";
113
+ accountSid;
114
+ authToken;
115
+ constructor(opts = {}) {
116
+ const sid = opts.accountSid ?? process.env.TWILIO_ACCOUNT_SID;
117
+ const tok = opts.authToken ?? process.env.TWILIO_AUTH_TOKEN;
118
+ if (!sid) {
119
+ throw new Error(
120
+ "Twilio carrier requires accountSid. Pass { accountSid: 'AC...' } or set TWILIO_ACCOUNT_SID in the environment."
121
+ );
122
+ }
123
+ if (!tok) {
124
+ throw new Error(
125
+ "Twilio carrier requires authToken. Pass { authToken: '...' } or set TWILIO_AUTH_TOKEN in the environment."
126
+ );
127
+ }
128
+ this.accountSid = sid;
129
+ this.authToken = tok;
130
+ }
131
+ };
132
+
133
+ // src/telephony/telnyx.ts
134
+ init_esm_shims();
135
+ var Carrier3 = class {
136
+ kind = "telnyx";
137
+ apiKey;
138
+ connectionId;
139
+ publicKey;
140
+ constructor(opts = {}) {
141
+ const key = opts.apiKey ?? process.env.TELNYX_API_KEY;
142
+ const conn = opts.connectionId ?? process.env.TELNYX_CONNECTION_ID;
143
+ const pub = opts.publicKey ?? process.env.TELNYX_PUBLIC_KEY;
144
+ if (!key) {
145
+ throw new Error(
146
+ "Telnyx carrier requires apiKey. Pass { apiKey: '...' } or set TELNYX_API_KEY in the environment."
147
+ );
148
+ }
149
+ if (!conn) {
150
+ throw new Error(
151
+ "Telnyx carrier requires connectionId. Pass { connectionId: '...' } or set TELNYX_CONNECTION_ID in the environment."
152
+ );
153
+ }
154
+ this.apiKey = key;
155
+ this.connectionId = conn;
156
+ this.publicKey = pub;
157
+ }
158
+ };
159
+
109
160
  // src/engines/openai.ts
110
161
  init_esm_shims();
111
162
  var Realtime = class {
@@ -324,6 +375,566 @@ function validateAllToolSchemas(tools) {
324
375
  }
325
376
  }
326
377
 
378
+ // src/telemetry/index.ts
379
+ init_esm_shims();
380
+
381
+ // src/telemetry/client.ts
382
+ init_esm_shims();
383
+
384
+ // src/telemetry/consent.ts
385
+ init_esm_shims();
386
+
387
+ // src/telemetry/env.ts
388
+ init_esm_shims();
389
+ var CI_ENV_VARS = [
390
+ "CI",
391
+ "CONTINUOUS_INTEGRATION",
392
+ "GITHUB_ACTIONS",
393
+ "GITLAB_CI",
394
+ "TRAVIS",
395
+ "CIRCLECI",
396
+ "APPVEYOR",
397
+ "TF_BUILD",
398
+ "TEAMCITY_VERSION",
399
+ "BUILDKITE",
400
+ "DRONE",
401
+ "JENKINS_URL",
402
+ "HUDSON_URL",
403
+ "BAMBOO_BUILDKEY",
404
+ "CODEBUILD_BUILD_ID"
405
+ ];
406
+ var TEST_ENV_VARS = ["VITEST", "JEST_WORKER_ID"];
407
+ function isTruthy(value) {
408
+ if (value === void 0) return false;
409
+ const v = value.trim().toLowerCase();
410
+ return v !== "" && v !== "0" && v !== "false" && v !== "no" && v !== "off";
411
+ }
412
+ function isCi() {
413
+ return CI_ENV_VARS.some((name) => isTruthy(process.env[name]));
414
+ }
415
+ function isTest() {
416
+ if (TEST_ENV_VARS.some((name) => process.env[name] !== void 0)) return true;
417
+ const node = (process.env.NODE_ENV ?? "").trim().toLowerCase();
418
+ const patter = (process.env.PATTER_ENV ?? "").trim().toLowerCase();
419
+ return node === "test" || patter === "test";
420
+ }
421
+
422
+ // src/telemetry/install-id.ts
423
+ init_esm_shims();
424
+ import { randomUUID } from "crypto";
425
+ import * as fs from "fs";
426
+ import * as os from "os";
427
+ import * as path from "path";
428
+ var RUN_ID = randomUUID().replace(/-/g, "");
429
+ var HEX32 = /^[0-9a-f]{32}$/;
430
+ var VERSION_RE = /^[0-9][0-9a-z.+-]{0,31}$/;
431
+ var cachedInstallId = null;
432
+ function runId() {
433
+ return RUN_ID;
434
+ }
435
+ function statePath() {
436
+ const base = process.env.PATTER_TELEMETRY_STATE_DIR || process.env.XDG_STATE_HOME;
437
+ const root = base && base.length > 0 ? base : path.join(os.homedir(), ".getpatter");
438
+ return path.join(root, "install-id");
439
+ }
440
+ function installId() {
441
+ if (cachedInstallId !== null) return cachedInstallId;
442
+ const p = statePath();
443
+ try {
444
+ const existing = fs.readFileSync(p, "utf8").trim();
445
+ if (HEX32.test(existing)) {
446
+ cachedInstallId = existing;
447
+ return cachedInstallId;
448
+ }
449
+ } catch {
450
+ }
451
+ const newId = randomUUID().replace(/-/g, "");
452
+ try {
453
+ fs.mkdirSync(path.dirname(p), { recursive: true });
454
+ fs.writeFileSync(p, newId, "utf8");
455
+ cachedInstallId = newId;
456
+ } catch {
457
+ cachedInstallId = RUN_ID;
458
+ }
459
+ return cachedInstallId;
460
+ }
461
+ function versionPath() {
462
+ return path.join(path.dirname(statePath()), "version");
463
+ }
464
+ function previousVersion(current) {
465
+ const p = versionPath();
466
+ let prev = "";
467
+ try {
468
+ prev = fs.readFileSync(p, "utf8").trim();
469
+ } catch {
470
+ prev = "";
471
+ }
472
+ try {
473
+ fs.mkdirSync(path.dirname(p), { recursive: true });
474
+ fs.writeFileSync(p, current, "utf8");
475
+ } catch {
476
+ }
477
+ return VERSION_RE.test(prev) ? prev : "";
478
+ }
479
+ function daysSinceInstallBucket() {
480
+ let mtimeMs;
481
+ try {
482
+ mtimeMs = fs.statSync(statePath()).mtimeMs;
483
+ } catch {
484
+ return "0";
485
+ }
486
+ const days = Math.max(0, Math.floor((Date.now() - mtimeMs) / 864e5));
487
+ if (days === 0) return "0";
488
+ if (days <= 7) return "1_7";
489
+ if (days <= 30) return "8_30";
490
+ return "30_plus";
491
+ }
492
+ function firstRunPath() {
493
+ return path.join(path.dirname(statePath()), "first-run");
494
+ }
495
+ function isFirstRun() {
496
+ const p = firstRunPath();
497
+ try {
498
+ if (fs.existsSync(p)) return false;
499
+ } catch {
500
+ return false;
501
+ }
502
+ try {
503
+ fs.mkdirSync(path.dirname(p), { recursive: true });
504
+ fs.writeFileSync(p, "1", "utf8");
505
+ return true;
506
+ } catch {
507
+ return false;
508
+ }
509
+ }
510
+ function optOutPath() {
511
+ return path.join(path.dirname(statePath()), "telemetry-disabled");
512
+ }
513
+ function isOptedOut() {
514
+ try {
515
+ return fs.existsSync(optOutPath());
516
+ } catch {
517
+ return false;
518
+ }
519
+ }
520
+
521
+ // src/telemetry/consent.ts
522
+ function isEnabled(flag) {
523
+ if (isTruthy(process.env.DO_NOT_TRACK)) return false;
524
+ if (isTruthy(process.env.PATTER_TELEMETRY_DISABLED)) return false;
525
+ if (isOptedOut()) return false;
526
+ if (flag === false) return false;
527
+ if (isCi() || isTest()) return false;
528
+ return true;
529
+ }
530
+
531
+ // src/telemetry/events.ts
532
+ init_esm_shims();
533
+ import * as os2 from "os";
534
+
535
+ // src/telemetry/stack.ts
536
+ init_esm_shims();
537
+ var STACK_VENDORS = /* @__PURE__ */ new Set([
538
+ "openai",
539
+ "anthropic",
540
+ "google",
541
+ "cerebras",
542
+ "groq",
543
+ "deepgram",
544
+ "elevenlabs",
545
+ "cartesia",
546
+ "whisper",
547
+ "soniox",
548
+ "assemblyai",
549
+ "speechmatics",
550
+ "lmnt",
551
+ "rime",
552
+ "inworld",
553
+ "telnyx",
554
+ "other"
555
+ ]);
556
+ var VENDOR_ALIASES = {
557
+ cartesia_stt: "cartesia",
558
+ cartesia_tts: "cartesia",
559
+ openai_tts: "openai",
560
+ openai_transcribe: "openai",
561
+ elevenlabs_ws: "elevenlabs",
562
+ telnyx_stt: "telnyx",
563
+ telnyx_tts: "telnyx"
564
+ };
565
+ var RAW_UNSAFE_RE = /[^a-z0-9._-]/;
566
+ var DATE_SUFFIX_RE = /-\d{8}$/;
567
+ function vendorOf(providerKey) {
568
+ if (!providerKey) return "other";
569
+ const v = VENDOR_ALIASES[providerKey] ?? providerKey;
570
+ return STACK_VENDORS.has(v) ? v : "other";
571
+ }
572
+ function modelToken(vendor, rawModel) {
573
+ if (!rawModel) return `${vendor}-other`;
574
+ const m = rawModel.trim().toLowerCase();
575
+ if (m.length > 40 || RAW_UNSAFE_RE.test(m)) return `${vendor}-other`;
576
+ const token = m.replace(/_/g, "-").replace(DATE_SUFFIX_RE, "").replace(/^[-.]+|[-.]+$/g, "");
577
+ return token ? `${vendor}-${token}` : `${vendor}-other`;
578
+ }
579
+ function readProviderKey(obj) {
580
+ const ctor = obj?.constructor;
581
+ const key = ctor?.providerKey;
582
+ return typeof key === "string" && key ? key : null;
583
+ }
584
+ function readModel(obj) {
585
+ const rec = obj;
586
+ for (const attr of ["model", "modelId", "_model"]) {
587
+ const v = rec?.[attr];
588
+ if (typeof v === "string" && v) return v;
589
+ }
590
+ return "";
591
+ }
592
+ function layerDims(obj, providerField, modelField) {
593
+ if (obj === null || obj === void 0) return {};
594
+ const vendor = vendorOf(readProviderKey(obj));
595
+ return { [providerField]: vendor, [modelField]: modelToken(vendor, readModel(obj)) };
596
+ }
597
+ function stackDimensions(stt, tts, llm) {
598
+ return {
599
+ ...layerDims(stt, "stt_provider", "stt_model"),
600
+ ...layerDims(tts, "tts_provider", "tts_model"),
601
+ ...layerDims(llm, "llm_provider", "llm_model")
602
+ };
603
+ }
604
+
605
+ // src/telemetry/events.ts
606
+ var SCHEMA_VERSION = 5;
607
+ var EVENT_SDK_INITIALIZED = "sdk_initialized";
608
+ var EVENT_FIRST_RUN = "first_run";
609
+ var EVENT_CLI_COMMAND = "cli_command";
610
+ var EVENT_FEATURE_USED = "feature_used";
611
+ var EVENT_AGENT_CONFIGURED = "agent_configured";
612
+ var EVENT_CALL_STARTED = "call_started";
613
+ var EVENT_CALL_COMPLETED = "call_completed";
614
+ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
615
+ EVENT_SDK_INITIALIZED,
616
+ EVENT_FIRST_RUN,
617
+ EVENT_CLI_COMMAND,
618
+ EVENT_FEATURE_USED,
619
+ EVENT_AGENT_CONFIGURED,
620
+ EVENT_CALL_STARTED,
621
+ EVENT_CALL_COMPLETED
622
+ ]);
623
+ var DIMENSION_VALUES = {
624
+ carrier: /* @__PURE__ */ new Set(["twilio", "telnyx", "plivo", "none"]),
625
+ tunnel: /* @__PURE__ */ new Set(["static", "configured", "none"]),
626
+ engine: /* @__PURE__ */ new Set(["realtime", "convai", "pipeline"]),
627
+ provider: /* @__PURE__ */ new Set([
628
+ "openai",
629
+ "elevenlabs",
630
+ "deepgram",
631
+ "cartesia",
632
+ "cerebras",
633
+ "anthropic",
634
+ "google",
635
+ "whisper",
636
+ "other"
637
+ ]),
638
+ // agent_configured dimensions
639
+ custom_tool_count_bucket: /* @__PURE__ */ new Set(["0", "1", "2_3", "4_6", "7_12", "13_plus"]),
640
+ integration: /* @__PURE__ */ new Set(["openclaw", "mcp", "hermes", "other", "none"]),
641
+ integration_kind: /* @__PURE__ */ new Set(["consult", "mcp", "none"]),
642
+ mcp_server_count_bucket: /* @__PURE__ */ new Set(["0", "1", "2_3", "4_plus"]),
643
+ // call_started / call_completed: inbound vs outbound — a core usage split.
644
+ direction: /* @__PURE__ */ new Set(["inbound", "outbound", "none"]),
645
+ // cli_command: which CLI subcommand was invoked (never args/flags values).
646
+ cli_command: /* @__PURE__ */ new Set(["dashboard", "eval", "telemetry", "none", "other"]),
647
+ // call_completed: the call's terminal outcome
648
+ outcome: /* @__PURE__ */ new Set(["completed", "error", "no_answer", "busy", "failed"]),
649
+ // call_completed: terminal error code (mirrors ErrorCode, plus "other"). Never
650
+ // the error message.
651
+ error_code: /* @__PURE__ */ new Set([
652
+ "config",
653
+ "connection",
654
+ "auth",
655
+ "timeout",
656
+ "rate_limit",
657
+ "webhook_verification",
658
+ "input_validation",
659
+ "provider_error",
660
+ "provision",
661
+ "internal",
662
+ "other"
663
+ ]),
664
+ // feature_used (pipeline): per-layer vendor of the composed stack. A
665
+ // providerKey not on the closed allowlist collapses to "other"; an absent layer
666
+ // is omitted (the value set keeps "none" only as a safety token).
667
+ stt_provider: /* @__PURE__ */ new Set([...STACK_VENDORS, "none"]),
668
+ tts_provider: /* @__PURE__ */ new Set([...STACK_VENDORS, "none"]),
669
+ llm_provider: /* @__PURE__ */ new Set([...STACK_VENDORS, "none"]),
670
+ // sdk_initialized: anonymous deploy-shape (presence-only env/file probes).
671
+ invoked_by_agent: /* @__PURE__ */ new Set(["claude", "cursor", "copilot", "gemini", "windsurf", "other", "none"]),
672
+ serverless: /* @__PURE__ */ new Set(["lambda", "cloud_run", "vercel", "azure_functions", "none"]),
673
+ cloud: /* @__PURE__ */ new Set(["aws", "gcp", "azure", "fly", "none"]),
674
+ package_manager: /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun", "pip", "uv", "poetry", "pipenv", "conda", "none"]),
675
+ days_since_install_bucket: /* @__PURE__ */ new Set(["0", "1_7", "8_30", "30_plus"]),
676
+ // agent_configured: feature-adoption (Realtime tuning).
677
+ noise_reduction: /* @__PURE__ */ new Set(["near_field", "far_field", "none"]),
678
+ turn_detection: /* @__PURE__ */ new Set(["default", "custom", "none"]),
679
+ // call_completed: how many conversational turns the call had.
680
+ turn_count_bucket: /* @__PURE__ */ new Set(["0", "1", "2_3", "4_6", "7_12", "13_plus"])
681
+ };
682
+ var NUMERIC_DIMENSIONS = /* @__PURE__ */ new Set([
683
+ "builtin_tool_count",
684
+ "latency_ms",
685
+ "duration_seconds",
686
+ "cost_usd"
687
+ ]);
688
+ var STRING_DIMENSIONS = /* @__PURE__ */ new Set([
689
+ "stt_model",
690
+ "tts_model",
691
+ "llm_model",
692
+ "previous_sdk_version"
693
+ ]);
694
+ var MODEL_TOKEN_RE = /^[a-z0-9][a-z0-9.-]{0,40}$/;
695
+ var BOOL_DIMENSIONS = /* @__PURE__ */ new Set([
696
+ "container",
697
+ "preambles_used",
698
+ "per_tool_timeouts_set",
699
+ "llm_fallback_configured"
700
+ ]);
701
+ var ALLOWED_DIMENSIONS = /* @__PURE__ */ new Set([
702
+ ...Object.keys(DIMENSION_VALUES),
703
+ ...NUMERIC_DIMENSIONS,
704
+ ...STRING_DIMENSIONS,
705
+ ...BOOL_DIMENSIONS
706
+ ]);
707
+ function osFamily() {
708
+ const p = os2.platform();
709
+ if (p === "win32") return "windows";
710
+ return p || "unknown";
711
+ }
712
+ function arch2() {
713
+ const a = os2.arch();
714
+ if (a === "x64") return "x86_64";
715
+ if (a === "arm64") return "arm64";
716
+ return "other";
717
+ }
718
+ function runtimeVersion() {
719
+ const parts = (process.versions.node ?? "0.0").split(".");
720
+ return `${parts[0] ?? "0"}.${parts[1] ?? "0"}`;
721
+ }
722
+ function buildEvent(name, opts) {
723
+ if (!ALLOWED_EVENTS.has(name)) {
724
+ throw new Error(`unknown telemetry event: ${name}`);
725
+ }
726
+ const event = {
727
+ event: name,
728
+ schema_version: SCHEMA_VERSION,
729
+ run_id: runId(),
730
+ install_id: installId(),
731
+ sdk: "typescript",
732
+ sdk_version: opts.sdkVersion,
733
+ os: osFamily(),
734
+ arch: arch2(),
735
+ runtime: "node",
736
+ runtime_version: runtimeVersion(),
737
+ ci: isCi() || isTest()
738
+ };
739
+ for (const [key, raw] of Object.entries(opts.dimensions ?? {})) {
740
+ if (!ALLOWED_DIMENSIONS.has(key) || raw === null || raw === void 0) {
741
+ continue;
742
+ }
743
+ let value = raw;
744
+ const allowed = DIMENSION_VALUES[key];
745
+ if (allowed && !(typeof value === "string" && allowed.has(value))) {
746
+ value = "other";
747
+ } else if (STRING_DIMENSIONS.has(key)) {
748
+ if (!(typeof value === "string" && MODEL_TOKEN_RE.test(value))) {
749
+ continue;
750
+ }
751
+ } else if (BOOL_DIMENSIONS.has(key) && typeof value !== "boolean") {
752
+ continue;
753
+ }
754
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
755
+ event[key] = value;
756
+ }
757
+ }
758
+ return event;
759
+ }
760
+
761
+ // src/telemetry/client.ts
762
+ var DEFAULT_ENDPOINT = "https://telemetry.getpatter.com/v1/ingest";
763
+ var TIMEOUT_MS = 3e3;
764
+ var BUFFER_MAX = 256;
765
+ var noticeShown = false;
766
+ var liveClients = /* @__PURE__ */ new Set();
767
+ var exitHookRegistered = false;
768
+ function showNoticeOnce() {
769
+ if (noticeShown) return;
770
+ noticeShown = true;
771
+ getLogger().info(
772
+ "Anonymous usage telemetry is on (no PII, no call content). Collected: a random anonymous install id, SDK version, language, OS family, runtime version, coarse feature flags, the composed stack (provider + model per layer), tool counts, integration category, and per-call duration, latency, cost, and error codes (no call content, no message text). Disable with PATTER_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1, or telemetry: false. Details: https://docs.getpatter.com/telemetry"
773
+ );
774
+ }
775
+ function registerExitHook() {
776
+ if (exitHookRegistered) return;
777
+ exitHookRegistered = true;
778
+ process.once("beforeExit", () => {
779
+ for (const ref of [...liveClients]) {
780
+ const client = ref.deref();
781
+ if (client) void client.close();
782
+ else liveClients.delete(ref);
783
+ }
784
+ });
785
+ }
786
+ var TelemetryClient = class {
787
+ sdkVersion;
788
+ enabledFlag;
789
+ endpoint;
790
+ debug;
791
+ buffer = [];
792
+ flushing = false;
793
+ closed = false;
794
+ selfRef = new WeakRef(this);
795
+ constructor(options) {
796
+ this.sdkVersion = options.sdkVersion;
797
+ this.enabledFlag = isEnabled(options.flag);
798
+ this.endpoint = options.endpoint ?? process.env.PATTER_TELEMETRY_ENDPOINT ?? DEFAULT_ENDPOINT;
799
+ this.debug = isTruthy(process.env.PATTER_TELEMETRY_DEBUG);
800
+ if (this.enabledFlag && !this.debug) {
801
+ showNoticeOnce();
802
+ registerExitHook();
803
+ liveClients.add(this.selfRef);
804
+ }
805
+ }
806
+ get enabled() {
807
+ return this.enabledFlag;
808
+ }
809
+ /** Enqueue an event. Fire-and-forget; never throws, never blocks. */
810
+ record(name, dimensions) {
811
+ if (!this.enabledFlag || this.closed) return;
812
+ let event;
813
+ try {
814
+ event = buildEvent(name, { sdkVersion: this.sdkVersion, dimensions });
815
+ } catch (err) {
816
+ getLogger().debug("telemetry buildEvent failed", err);
817
+ return;
818
+ }
819
+ if (this.debug) {
820
+ try {
821
+ process.stderr.write(`[patter telemetry] ${JSON.stringify(event)}
822
+ `);
823
+ } catch {
824
+ }
825
+ return;
826
+ }
827
+ try {
828
+ if (this.buffer.length >= BUFFER_MAX) this.buffer.shift();
829
+ this.buffer.push(event);
830
+ this.scheduleFlush();
831
+ } catch (err) {
832
+ getLogger().debug("telemetry enqueue failed", err);
833
+ }
834
+ }
835
+ /**
836
+ * Schedule a flush of any buffered events. Events recorded before the server
837
+ * is running (e.g. at `new Patter(...)`) sit in the buffer; call this once the
838
+ * server is up so they ship promptly. Cheap when disabled or buffer is empty.
839
+ */
840
+ flushPending() {
841
+ if (!this.enabledFlag || this.debug) return;
842
+ try {
843
+ this.scheduleFlush();
844
+ } catch (err) {
845
+ getLogger().debug("telemetry flushPending failed", err);
846
+ }
847
+ }
848
+ /** Flush remaining events (graceful shutdown). Never throws. */
849
+ async close() {
850
+ if (this.closed) return;
851
+ this.closed = true;
852
+ liveClients.delete(this.selfRef);
853
+ if (!this.enabledFlag || this.debug) return;
854
+ try {
855
+ await this.flush();
856
+ } catch (err) {
857
+ getLogger().debug("telemetry close flush failed", err);
858
+ }
859
+ }
860
+ scheduleFlush() {
861
+ if (this.flushing) return;
862
+ this.flushing = true;
863
+ void this.flush().finally(() => {
864
+ this.flushing = false;
865
+ });
866
+ }
867
+ async flush() {
868
+ if (this.buffer.length === 0) return;
869
+ const events = this.buffer.splice(0, this.buffer.length);
870
+ const controller = new AbortController();
871
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
872
+ timer.unref?.();
873
+ try {
874
+ await fetch(this.endpoint, {
875
+ method: "POST",
876
+ headers: { "content-type": "application/json" },
877
+ body: JSON.stringify(events),
878
+ signal: controller.signal
879
+ });
880
+ } catch (err) {
881
+ getLogger().debug("telemetry flush failed", err);
882
+ } finally {
883
+ clearTimeout(timer);
884
+ }
885
+ }
886
+ };
887
+
888
+ // src/telemetry/environment.ts
889
+ init_esm_shims();
890
+ import * as fs2 from "fs";
891
+ var env = process.env;
892
+ function invokedByAgent() {
893
+ if ("CLAUDECODE" in env || "CLAUDE_CODE" in env || "CLAUDE_CODE_ENTRYPOINT" in env)
894
+ return "claude";
895
+ if ("CURSOR_TRACE_ID" in env || "CURSOR_AGENT" in env) return "cursor";
896
+ if ("GITHUB_COPILOT_AGENT" in env || "COPILOT_AGENT_ID" in env) return "copilot";
897
+ if ("GEMINI_CLI" in env || "GEMINI_AGENT" in env) return "gemini";
898
+ if ("WINDSURF" in env || "WINDSURF_AGENT" in env) return "windsurf";
899
+ if ("AIDER" in env || "OPENAI_AGENT" in env) return "other";
900
+ return "none";
901
+ }
902
+ function inContainer() {
903
+ try {
904
+ if (fs2.existsSync("/.dockerenv")) return true;
905
+ } catch {
906
+ }
907
+ if (env.KUBERNETES_SERVICE_HOST) return true;
908
+ try {
909
+ const blob = fs2.readFileSync("/proc/1/cgroup", "utf8");
910
+ return blob.includes("docker") || blob.includes("containerd") || blob.includes("kubepods");
911
+ } catch {
912
+ return false;
913
+ }
914
+ }
915
+ function serverless() {
916
+ if (env.AWS_LAMBDA_FUNCTION_NAME) return "lambda";
917
+ if (env.K_SERVICE) return "cloud_run";
918
+ if (env.VERCEL) return "vercel";
919
+ if (env.AZURE_FUNCTIONS_ENVIRONMENT || env.FUNCTIONS_WORKER_RUNTIME) return "azure_functions";
920
+ return "none";
921
+ }
922
+ function cloud() {
923
+ if (env.AWS_REGION || env.AWS_EXECUTION_ENV || env.AWS_LAMBDA_FUNCTION_NAME) return "aws";
924
+ if (env.K_SERVICE || env.GOOGLE_CLOUD_PROJECT || env.GCP_PROJECT) return "gcp";
925
+ if (env.WEBSITE_INSTANCE_ID || env.AZURE_FUNCTIONS_ENVIRONMENT) return "azure";
926
+ if (env.FLY_APP_NAME) return "fly";
927
+ return "none";
928
+ }
929
+ function packageManager() {
930
+ const ua = env.npm_config_user_agent ?? "";
931
+ if (ua.startsWith("pnpm")) return "pnpm";
932
+ if (ua.startsWith("yarn")) return "yarn";
933
+ if (ua.startsWith("bun")) return "bun";
934
+ if (ua.startsWith("npm")) return "npm";
935
+ return "none";
936
+ }
937
+
327
938
  // src/_speech-events.ts
328
939
  init_esm_shims();
329
940
  var logger = getLogger();
@@ -628,6 +1239,79 @@ function closeParkedConnections(slot) {
628
1239
  }
629
1240
  }
630
1241
  }
1242
+ function carrierFamily(carrier) {
1243
+ if (carrier instanceof Carrier2) return "twilio";
1244
+ if (carrier instanceof Carrier3) return "telnyx";
1245
+ if (carrier instanceof Carrier) return "plivo";
1246
+ return "none";
1247
+ }
1248
+ function telemetryEngineFamily(opts) {
1249
+ if (opts.engine) {
1250
+ return opts.engine.constructor.name.toLowerCase().includes("convai") ? "convai" : "realtime";
1251
+ }
1252
+ if (opts.provider === "elevenlabs_convai") return "convai";
1253
+ if (opts.provider === "pipeline") return "pipeline";
1254
+ if (opts.provider === "openai_realtime") return "realtime";
1255
+ if (opts.stt || opts.tts) return "pipeline";
1256
+ return "realtime";
1257
+ }
1258
+ function telemetryProviderFamily(family) {
1259
+ if (family === "realtime") return "openai";
1260
+ if (family === "convai") return "elevenlabs";
1261
+ return "other";
1262
+ }
1263
+ function telemetryBucketCustomTools(n) {
1264
+ if (n <= 0) return "0";
1265
+ if (n === 1) return "1";
1266
+ if (n <= 3) return "2_3";
1267
+ if (n <= 6) return "4_6";
1268
+ if (n <= 12) return "7_12";
1269
+ return "13_plus";
1270
+ }
1271
+ function telemetryBucketMcp(n) {
1272
+ if (n <= 0) return "0";
1273
+ if (n === 1) return "1";
1274
+ if (n <= 3) return "2_3";
1275
+ return "4_plus";
1276
+ }
1277
+ function telemetryIntegration(opts) {
1278
+ const nMcp = opts.mcpServers?.length ?? 0;
1279
+ if (nMcp > 0) {
1280
+ return { integration: "mcp", integrationKind: "mcp", mcpBucket: telemetryBucketMcp(nMcp) };
1281
+ }
1282
+ if (opts.consult) {
1283
+ let isOpenclaw = false;
1284
+ const oc = opts.consult.openaiCompatible;
1285
+ if (oc) {
1286
+ const model = oc.model ?? "";
1287
+ const baseUrl = oc.baseUrl ?? "";
1288
+ isOpenclaw = model.startsWith("openclaw/") || baseUrl.includes(":18789");
1289
+ }
1290
+ return {
1291
+ integration: isOpenclaw ? "openclaw" : "other",
1292
+ integrationKind: "consult",
1293
+ mcpBucket: "0"
1294
+ };
1295
+ }
1296
+ return { integration: "none", integrationKind: "none", mcpBucket: "0" };
1297
+ }
1298
+ function telemetryEnvironmentDims() {
1299
+ try {
1300
+ const dims = {
1301
+ invoked_by_agent: invokedByAgent(),
1302
+ container: inContainer(),
1303
+ serverless: serverless(),
1304
+ cloud: cloud(),
1305
+ package_manager: packageManager(),
1306
+ days_since_install_bucket: daysSinceInstallBucket()
1307
+ };
1308
+ const prev = previousVersion(VERSION);
1309
+ if (prev) dims.previous_sdk_version = prev;
1310
+ return dims;
1311
+ } catch {
1312
+ return {};
1313
+ }
1314
+ }
631
1315
  var Patter = class {
632
1316
  localConfig;
633
1317
  embeddedServer = null;
@@ -648,6 +1332,14 @@ var Patter = class {
648
1332
  * ``Cannot use both tunnel: true and webhookUrl``.
649
1333
  */
650
1334
  tunnelOwnsWebhookUrl = false;
1335
+ /**
1336
+ * Anonymous usage telemetry (opt-out, default ON). Separate from
1337
+ * ``./observability`` (user-facing OTel). Fire-and-forget and fail-safe — it
1338
+ * can never block or break a call. See ``./telemetry``.
1339
+ */
1340
+ telemetry;
1341
+ telemetrySeenEngines = /* @__PURE__ */ new Set();
1342
+ telemetrySeenAgentShapes = /* @__PURE__ */ new Set();
651
1343
  /**
652
1344
  * Pre-rendered first-message TTS audio per outbound call_id. Populated
653
1345
  * by :meth:`call` when ``agent.prewarmFirstMessage`` is true; consumed
@@ -853,6 +1545,22 @@ var Patter = class {
853
1545
  openaiKey: options.openaiKey,
854
1546
  persistRoot: resolvePersistRoot(options.persist)
855
1547
  };
1548
+ this.telemetry = new TelemetryClient({
1549
+ sdkVersion: VERSION,
1550
+ flag: options.telemetry
1551
+ });
1552
+ const initDims = {
1553
+ carrier: carrierFamily(carrier),
1554
+ tunnel: tunnel instanceof Static ? "static" : options.tunnel ? "configured" : "none",
1555
+ ...telemetryEnvironmentDims()
1556
+ };
1557
+ if (this.telemetry.enabled) {
1558
+ try {
1559
+ if (isFirstRun()) this.telemetry.record("first_run", initDims);
1560
+ } catch {
1561
+ }
1562
+ }
1563
+ this.telemetry.record("sdk_initialized", initDims);
856
1564
  this._tunnelReady = new Promise((resolve, reject) => {
857
1565
  this._tunnelReadyResolve = resolve;
858
1566
  this._tunnelReadyReject = reject;
@@ -872,6 +1580,55 @@ var Patter = class {
872
1580
  // === Agent definition ===
873
1581
  /** Resolve user-supplied agent options against engine defaults and return the merged config. */
874
1582
  agent(opts) {
1583
+ const family = telemetryEngineFamily(opts);
1584
+ const stack = stackDimensions(opts.stt, opts.tts, opts.llm);
1585
+ const featureKey = family + "|" + Object.entries(stack).sort().map(([k, v]) => `${k}=${v}`).join(",");
1586
+ if (!this.telemetrySeenEngines.has(featureKey)) {
1587
+ this.telemetrySeenEngines.add(featureKey);
1588
+ this.telemetry.record("feature_used", {
1589
+ engine: family,
1590
+ provider: telemetryProviderFamily(family),
1591
+ ...stack
1592
+ });
1593
+ }
1594
+ const builtin = opts.consult ? 1 : 0;
1595
+ const customBucket = telemetryBucketCustomTools(opts.tools?.length ?? 0);
1596
+ const { integration, integrationKind, mcpBucket } = telemetryIntegration(opts);
1597
+ const engineObj = opts.engine;
1598
+ const nr = opts.openaiRealtimeNoiseReduction ?? engineObj?.noiseReduction;
1599
+ const noiseReduction = nr === "near_field" || nr === "far_field" ? nr : "none";
1600
+ const td = opts.realtimeTurnDetection ?? engineObj?.turnDetection;
1601
+ const turnDetection = td != null ? "custom" : "none";
1602
+ const preamblesUsed = Boolean(opts.toolCallPreambles);
1603
+ const perToolTimeoutsSet = Array.isArray(opts.tools) && opts.tools.some((t) => t.timeoutMs !== void 0);
1604
+ const llmFallbackConfigured = typeof opts.llm?.getAvailability === "function";
1605
+ const shapeKey = [
1606
+ builtin,
1607
+ customBucket,
1608
+ integration,
1609
+ integrationKind,
1610
+ mcpBucket,
1611
+ noiseReduction,
1612
+ turnDetection,
1613
+ preamblesUsed,
1614
+ perToolTimeoutsSet,
1615
+ llmFallbackConfigured
1616
+ ].join("|");
1617
+ if (!this.telemetrySeenAgentShapes.has(shapeKey)) {
1618
+ this.telemetrySeenAgentShapes.add(shapeKey);
1619
+ this.telemetry.record("agent_configured", {
1620
+ builtin_tool_count: builtin,
1621
+ custom_tool_count_bucket: customBucket,
1622
+ integration,
1623
+ integration_kind: integrationKind,
1624
+ mcp_server_count_bucket: mcpBucket,
1625
+ noise_reduction: noiseReduction,
1626
+ turn_detection: turnDetection,
1627
+ preambles_used: preamblesUsed,
1628
+ per_tool_timeouts_set: perToolTimeoutsSet,
1629
+ llm_fallback_configured: llmFallbackConfigured
1630
+ });
1631
+ }
875
1632
  let working = { ...opts };
876
1633
  if (opts.engine) {
877
1634
  if (opts.provider) {
@@ -1061,6 +1818,7 @@ var Patter = class {
1061
1818
  opts.dashboardToken ?? "",
1062
1819
  opts.allowInsecureDashboard ?? false
1063
1820
  );
1821
+ this.embeddedServer.telemetry = this.telemetry;
1064
1822
  this.embeddedServer.popPrewarmAudio = this.popPrewarmAudio;
1065
1823
  this.embeddedServer.popPrewarmedConnections = this.popPrewarmedConnections;
1066
1824
  this.embeddedServer.recordPrewarmWaste = this.recordPrewarmWaste;
@@ -1070,6 +1828,7 @@ var Patter = class {
1070
1828
  await waitForTunnelPubliclyReachable(webhookUrl);
1071
1829
  }
1072
1830
  this._readyResolve(webhookUrl);
1831
+ this.telemetry.flushPending();
1073
1832
  } catch (err) {
1074
1833
  const e = err instanceof Error ? err : new Error(String(err));
1075
1834
  this._readyReject(e);
@@ -1078,7 +1837,7 @@ var Patter = class {
1078
1837
  }
1079
1838
  /** Run the agent in interactive terminal-test mode (no real telephony). */
1080
1839
  async test(opts) {
1081
- const { TestSession: TestSession2 } = await import("./test-mode-HGHI2AUV.mjs");
1840
+ const { TestSession: TestSession2 } = await import("./test-mode-XFOADUNE.mjs");
1082
1841
  const session = new TestSession2();
1083
1842
  await session.run({
1084
1843
  agent: opts.agent,
@@ -1752,6 +2511,7 @@ var Patter = class {
1752
2511
  * entries leak across ``serve`` / ``disconnect`` cycles. See FIX #93.
1753
2512
  */
1754
2513
  async disconnect() {
2514
+ this.telemetry.flushPending();
1755
2515
  for (const handle of this.prewarmTtlTimers.values()) {
1756
2516
  clearTimeout(handle);
1757
2517
  }
@@ -2329,6 +3089,7 @@ var PatterTool = class {
2329
3089
  maxDurationSec;
2330
3090
  recording;
2331
3091
  started = false;
3092
+ hermesTelemetryEmitted = false;
2332
3093
  /** Cached in-progress (or completed) start promise so concurrent execute()
2333
3094
  * callers all await the same boot sequence instead of each racing into
2334
3095
  * phone.serve(). Reset to null on failure so callers can retry after a
@@ -2485,6 +3246,20 @@ var PatterTool = class {
2485
3246
  * the same wire contract.
2486
3247
  */
2487
3248
  hermesHandler() {
3249
+ if (!this.hermesTelemetryEmitted) {
3250
+ this.hermesTelemetryEmitted = true;
3251
+ try {
3252
+ const tel = this.phone.telemetry;
3253
+ tel?.record("agent_configured", {
3254
+ builtin_tool_count: 0,
3255
+ custom_tool_count_bucket: "0",
3256
+ integration: "hermes",
3257
+ integration_kind: "none",
3258
+ mcp_server_count_bucket: "0"
3259
+ });
3260
+ } catch {
3261
+ }
3262
+ }
2488
3263
  return async (args) => {
2489
3264
  try {
2490
3265
  const result = await this.execute(args);
@@ -7541,7 +8316,12 @@ var LLM5 = class extends GoogleLLMProvider {
7541
8316
 
7542
8317
  // src/llm/openai-compatible.ts
7543
8318
  init_esm_shims();
8319
+ import { createHash } from "crypto";
7544
8320
  var DEFAULT_TIMEOUT_S = 60;
8321
+ function hashCaller(caller) {
8322
+ if (!caller) return void 0;
8323
+ return createHash("sha256").update(caller, "utf8").digest("hex").slice(0, 16);
8324
+ }
7545
8325
  var OpenAICompatibleLLMProvider = class {
7546
8326
  /**
7547
8327
  * Stable pricing/dashboard key — read by stream-handler/metrics. Typed as
@@ -7560,6 +8340,7 @@ var OpenAICompatibleLLMProvider = class {
7560
8340
  sessionIdPrefix;
7561
8341
  sessionKeyHeader;
7562
8342
  sessionKey;
8343
+ sessionKeyFactory;
7563
8344
  temperature;
7564
8345
  maxTokens;
7565
8346
  responseFormat;
@@ -7589,6 +8370,17 @@ var OpenAICompatibleLLMProvider = class {
7589
8370
  this.sessionIdPrefix = options.sessionIdPrefix;
7590
8371
  this.sessionKeyHeader = options.sessionKeyHeader;
7591
8372
  this.sessionKey = options.sessionKey;
8373
+ let sessionKeyFactory = options.sessionKeyFactory;
8374
+ if (!sessionKeyFactory && options.sessionKeyFrom === "caller_hash") {
8375
+ sessionKeyFactory = (ctx) => ctx.callerHash ? `patter-caller-${ctx.callerHash}` : void 0;
8376
+ } else if (options.sessionKeyFrom !== void 0 && options.sessionKeyFrom !== "caller_hash") {
8377
+ throw new Error(
8378
+ `sessionKeyFrom must be 'caller_hash' or undefined, got ${JSON.stringify(
8379
+ options.sessionKeyFrom
8380
+ )}`
8381
+ );
8382
+ }
8383
+ this.sessionKeyFactory = sessionKeyFactory;
7592
8384
  this.temperature = options.temperature;
7593
8385
  this.maxTokens = options.maxTokens;
7594
8386
  this.responseFormat = options.responseFormat;
@@ -7612,7 +8404,7 @@ var OpenAICompatibleLLMProvider = class {
7612
8404
  * - ``sessionKeyHeader`` (+ ``sessionKey``) → the static ``sessionKey`` value.
7613
8405
  * ``sessionKey`` is a credential-grade memory scope and is never logged.
7614
8406
  */
7615
- buildHeaders(callId) {
8407
+ buildHeaders(callId, caller, callee) {
7616
8408
  const headers = {
7617
8409
  "Content-Type": "application/json",
7618
8410
  "User-Agent": `getpatter/${VERSION}`,
@@ -7624,11 +8416,33 @@ var OpenAICompatibleLLMProvider = class {
7624
8416
  if (this.sessionIdHeader && callId) {
7625
8417
  headers[this.sessionIdHeader] = `${this.sessionIdPrefix ?? ""}${callId}`;
7626
8418
  }
7627
- if (this.sessionKeyHeader && this.sessionKey) {
7628
- headers[this.sessionKeyHeader] = this.sessionKey;
8419
+ if (this.sessionKeyHeader) {
8420
+ const sessionKeyValue = this.resolveSessionKey(callId, caller, callee);
8421
+ if (sessionKeyValue) {
8422
+ headers[this.sessionKeyHeader] = sessionKeyValue;
8423
+ }
7629
8424
  }
7630
8425
  return headers;
7631
8426
  }
8427
+ /**
8428
+ * Resolve the ``sessionKeyHeader`` VALUE for this call. When a
8429
+ * ``sessionKeyFactory`` is configured it is called with a
8430
+ * {@link SessionContext} (the raw ``caller`` plus its non-reversible
8431
+ * {@link hashCaller}) and its return value wins — a falsy return omits the
8432
+ * header. Otherwise the static ``sessionKey`` is used. Never logged.
8433
+ */
8434
+ resolveSessionKey(callId, caller, callee) {
8435
+ if (this.sessionKeyFactory) {
8436
+ const ctx = {
8437
+ callId,
8438
+ caller,
8439
+ callee,
8440
+ callerHash: hashCaller(caller)
8441
+ };
8442
+ return this.sessionKeyFactory(ctx);
8443
+ }
8444
+ return this.sessionKey;
8445
+ }
7632
8446
  /**
7633
8447
  * Pre-call DNS / TLS warmup for the configured endpoint. Best-effort:
7634
8448
  * 5 s timeout, all exceptions swallowed at debug level. The ``Authorization``
@@ -7682,10 +8496,12 @@ var OpenAICompatibleLLMProvider = class {
7682
8496
  /** Stream Patter-format LLM chunks from the configured chat completions API. */
7683
8497
  async *stream(messages, tools, opts) {
7684
8498
  const callId = opts?.callId;
8499
+ const caller = opts?.caller;
8500
+ const callee = opts?.callee;
7685
8501
  const body = this.buildBody(messages, tools, callId);
7686
8502
  const response = await fetch(`${this.baseUrl}/chat/completions`, {
7687
8503
  method: "POST",
7688
- headers: this.buildHeaders(callId),
8504
+ headers: this.buildHeaders(callId, caller, callee),
7689
8505
  body: JSON.stringify(body),
7690
8506
  signal: mergeAbortSignals(opts?.signal, AbortSignal.timeout(this.timeoutMs))
7691
8507
  });
@@ -7705,6 +8521,13 @@ var LLM6 = class extends OpenAICompatibleLLMProvider {
7705
8521
  static providerKey = "openai_compatible";
7706
8522
  };
7707
8523
 
8524
+ // src/llm/custom.ts
8525
+ init_esm_shims();
8526
+ var LLM7 = class extends OpenAICompatibleLLMProvider {
8527
+ /** Stable pricing/dashboard key — read by stream-handler/metrics. */
8528
+ static providerKey = "custom";
8529
+ };
8530
+
7708
8531
  // src/llm/hermes.ts
7709
8532
  init_esm_shims();
7710
8533
  var BASE_URL = "http://127.0.0.1:8642/v1";
@@ -7716,7 +8539,7 @@ var SESSION_ID_HEADER = "X-Hermes-Session-Id";
7716
8539
  var SESSION_ID_PREFIX = "patter-call-";
7717
8540
  var SESSION_KEY_HEADER = "X-Hermes-Session-Key";
7718
8541
  var DEFAULT_TIMEOUT_S2 = 120;
7719
- var LLM7 = class extends OpenAICompatibleLLMProvider {
8542
+ var LLM8 = class extends OpenAICompatibleLLMProvider {
7720
8543
  static providerKey = "hermes";
7721
8544
  constructor(opts = {}) {
7722
8545
  const model = opts.model ?? process.env[MODEL_ENV] ?? DEFAULT_MODEL5;
@@ -7731,6 +8554,8 @@ var LLM7 = class extends OpenAICompatibleLLMProvider {
7731
8554
  sessionIdPrefix: SESSION_ID_PREFIX,
7732
8555
  sessionKeyHeader: SESSION_KEY_HEADER,
7733
8556
  sessionKey: opts.sessionKey,
8557
+ sessionKeyFrom: opts.sessionKeyFrom,
8558
+ sessionKeyFactory: opts.sessionKeyFactory,
7734
8559
  extraHeaders: opts.extraHeaders,
7735
8560
  temperature: opts.temperature,
7736
8561
  maxTokens: opts.maxTokens,
@@ -7755,7 +8580,7 @@ var SESSION_HEADER = "x-openclaw-session-key";
7755
8580
  var SESSION_USER_PREFIX2 = "patter-call-";
7756
8581
  var DEFAULT_TIMEOUT_S3 = 120;
7757
8582
  var OPENCLAW_AGENT_RE = /^[A-Za-z0-9._:/-]+$/;
7758
- var LLM8 = class extends OpenAICompatibleLLMProvider {
8583
+ var LLM9 = class extends OpenAICompatibleLLMProvider {
7759
8584
  static providerKey = "openclaw";
7760
8585
  constructor(opts) {
7761
8586
  const agent = opts?.agent;
@@ -8025,57 +8850,6 @@ var KrispVivaFilter = class {
8025
8850
  }
8026
8851
  };
8027
8852
 
8028
- // src/telephony/twilio.ts
8029
- init_esm_shims();
8030
- var Carrier2 = class {
8031
- kind = "twilio";
8032
- accountSid;
8033
- authToken;
8034
- constructor(opts = {}) {
8035
- const sid = opts.accountSid ?? process.env.TWILIO_ACCOUNT_SID;
8036
- const tok = opts.authToken ?? process.env.TWILIO_AUTH_TOKEN;
8037
- if (!sid) {
8038
- throw new Error(
8039
- "Twilio carrier requires accountSid. Pass { accountSid: 'AC...' } or set TWILIO_ACCOUNT_SID in the environment."
8040
- );
8041
- }
8042
- if (!tok) {
8043
- throw new Error(
8044
- "Twilio carrier requires authToken. Pass { authToken: '...' } or set TWILIO_AUTH_TOKEN in the environment."
8045
- );
8046
- }
8047
- this.accountSid = sid;
8048
- this.authToken = tok;
8049
- }
8050
- };
8051
-
8052
- // src/telephony/telnyx.ts
8053
- init_esm_shims();
8054
- var Carrier3 = class {
8055
- kind = "telnyx";
8056
- apiKey;
8057
- connectionId;
8058
- publicKey;
8059
- constructor(opts = {}) {
8060
- const key = opts.apiKey ?? process.env.TELNYX_API_KEY;
8061
- const conn = opts.connectionId ?? process.env.TELNYX_CONNECTION_ID;
8062
- const pub = opts.publicKey ?? process.env.TELNYX_PUBLIC_KEY;
8063
- if (!key) {
8064
- throw new Error(
8065
- "Telnyx carrier requires apiKey. Pass { apiKey: '...' } or set TELNYX_API_KEY in the environment."
8066
- );
8067
- }
8068
- if (!conn) {
8069
- throw new Error(
8070
- "Telnyx carrier requires connectionId. Pass { connectionId: '...' } or set TELNYX_CONNECTION_ID in the environment."
8071
- );
8072
- }
8073
- this.apiKey = key;
8074
- this.connectionId = conn;
8075
- this.publicKey = pub;
8076
- }
8077
- };
8078
-
8079
8853
  // src/public-api.ts
8080
8854
  init_esm_shims();
8081
8855
  var DEFAULT_GUARDRAIL_REPLACEMENT = "I'm sorry, I can't respond to that.";
@@ -8142,9 +8916,9 @@ function tool(opts) {
8142
8916
 
8143
8917
  // src/chat-context.ts
8144
8918
  init_esm_shims();
8145
- import { randomUUID } from "crypto";
8919
+ import { randomUUID as randomUUID2 } from "crypto";
8146
8920
  function generateId() {
8147
- return randomUUID().replace(/-/g, "").slice(0, 12);
8921
+ return randomUUID2().replace(/-/g, "").slice(0, 12);
8148
8922
  }
8149
8923
  function createMessage(role, content, options) {
8150
8924
  return Object.freeze({
@@ -8559,8 +9333,8 @@ var IVRActivity = class {
8559
9333
 
8560
9334
  // src/audio/background-audio.ts
8561
9335
  init_esm_shims();
8562
- import { promises as fs } from "fs";
8563
- import path from "path";
9336
+ import { promises as fs3 } from "fs";
9337
+ import path2 from "path";
8564
9338
  import { fileURLToPath } from "url";
8565
9339
  var BuiltinAudioClip = {
8566
9340
  CITY_AMBIENCE: "city-ambience.ogg",
@@ -8573,8 +9347,8 @@ var BuiltinAudioClip = {
8573
9347
  };
8574
9348
  function builtinClipPath(clip) {
8575
9349
  const meta = typeof import.meta !== "undefined" ? import.meta : void 0;
8576
- const here = meta?.url ? path.dirname(fileURLToPath(meta.url)) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
8577
- return path.resolve(here, "..", "resources", "audio", clip);
9350
+ const here = meta?.url ? path2.dirname(fileURLToPath(meta.url)) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
9351
+ return path2.resolve(here, "..", "resources", "audio", clip);
8578
9352
  }
8579
9353
  var INT16_MIN = -32768;
8580
9354
  var INT16_MAX = 32767;
@@ -8743,7 +9517,7 @@ var BackgroundAudioPlayer = class {
8743
9517
  return source.decode(source.path);
8744
9518
  case "builtin": {
8745
9519
  const p = builtinClipPath(source.clip);
8746
- const header = await fs.readFile(p, { flag: "r" }).then((buf) => buf.subarray(0, 4));
9520
+ const header = await fs3.readFile(p, { flag: "r" }).then((buf) => buf.subarray(0, 4));
8747
9521
  if (header.toString("ascii") !== "OggS") {
8748
9522
  throw new Error(`Bundled clip ${source.clip} is not a valid Ogg file`);
8749
9523
  }
@@ -8790,8 +9564,8 @@ var TwilioAdapter = class _TwilioAdapter {
8790
9564
  this.baseUrl = opts.region ? `https://api.${opts.region}.twilio.com/2010-04-01` : TWILIO_API_BASE;
8791
9565
  this.authHeader = `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`;
8792
9566
  }
8793
- async request(method, path2, body) {
8794
- const url = `${this.baseUrl}/Accounts/${encodeURIComponent(this.accountSid)}${path2}`;
9567
+ async request(method, path3, body) {
9568
+ const url = `${this.baseUrl}/Accounts/${encodeURIComponent(this.accountSid)}${path3}`;
8795
9569
  const headers = { Authorization: this.authHeader };
8796
9570
  if (body) headers["Content-Type"] = "application/x-www-form-urlencoded";
8797
9571
  const response = await fetch(url, {
@@ -8802,7 +9576,7 @@ var TwilioAdapter = class _TwilioAdapter {
8802
9576
  });
8803
9577
  const text = await response.text();
8804
9578
  if (!response.ok) {
8805
- throw new Error(`Twilio ${method} ${path2} failed: ${response.status} ${text}`);
9579
+ throw new Error(`Twilio ${method} ${path3} failed: ${response.status} ${text}`);
8806
9580
  }
8807
9581
  if (!text) return {};
8808
9582
  try {
@@ -8820,8 +9594,8 @@ var TwilioAdapter = class _TwilioAdapter {
8820
9594
  const country = encodeURIComponent(opts.countryCode);
8821
9595
  const queryParts = ["PageSize=1"];
8822
9596
  if (opts.areaCode) queryParts.push(`AreaCode=${encodeURIComponent(opts.areaCode)}`);
8823
- const path2 = `/AvailablePhoneNumbers/${country}/Local.json?${queryParts.join("&")}`;
8824
- const available = await this.request("GET", path2);
9597
+ const path3 = `/AvailablePhoneNumbers/${country}/Local.json?${queryParts.join("&")}`;
9598
+ const available = await this.request("GET", path3);
8825
9599
  const first = available.available_phone_numbers?.[0]?.phone_number;
8826
9600
  if (!first) {
8827
9601
  throw new Error(`TwilioAdapter: no numbers available for country ${opts.countryCode}`);
@@ -8921,7 +9695,7 @@ var TwilioAdapter = class _TwilioAdapter {
8921
9695
 
8922
9696
  // src/providers/telnyx-adapter.ts
8923
9697
  init_esm_shims();
8924
- import { randomUUID as randomUUID2 } from "crypto";
9698
+ import { randomUUID as randomUUID3 } from "crypto";
8925
9699
  var TELNYX_API_BASE = "https://api.telnyx.com/v2";
8926
9700
  var TelnyxAdapter = class {
8927
9701
  apiKey;
@@ -8932,8 +9706,8 @@ var TelnyxAdapter = class {
8932
9706
  this.apiKey = apiKey;
8933
9707
  this.connectionId = connectionId;
8934
9708
  }
8935
- async request(method, path2, body) {
8936
- const url = `${this.baseUrl}${path2}`;
9709
+ async request(method, path3, body) {
9710
+ const url = `${this.baseUrl}${path3}`;
8937
9711
  const headers = {
8938
9712
  Authorization: `Bearer ${this.apiKey}`
8939
9713
  };
@@ -8946,7 +9720,7 @@ var TelnyxAdapter = class {
8946
9720
  });
8947
9721
  const text = await response.text();
8948
9722
  if (!response.ok) {
8949
- throw new Error(`Telnyx ${method} ${path2} failed: ${response.status} ${text}`);
9723
+ throw new Error(`Telnyx ${method} ${path3} failed: ${response.status} ${text}`);
8950
9724
  }
8951
9725
  if (!text) return {};
8952
9726
  try {
@@ -9030,7 +9804,7 @@ var TelnyxAdapter = class {
9030
9804
  if (!callControlId) throw new Error("TelnyxAdapter: callControlId is required");
9031
9805
  const encoded = encodeURIComponent(callControlId);
9032
9806
  const body = {
9033
- command_id: opts.commandId ?? randomUUID2()
9807
+ command_id: opts.commandId ?? randomUUID3()
9034
9808
  };
9035
9809
  try {
9036
9810
  await this.request(
@@ -9304,6 +10078,12 @@ var TelnyxTTS = class {
9304
10078
 
9305
10079
  // src/observability/index.ts
9306
10080
  init_esm_shims();
10081
+
10082
+ // src/index.ts
10083
+ var hermes = Object.freeze({ LLM: LLM8 });
10084
+ var openclaw = Object.freeze({ LLM: LLM9 });
10085
+ var openaiCompatible = Object.freeze({ LLM: LLM6 });
10086
+ var custom = Object.freeze({ LLM: LLM7 });
9307
10087
  export {
9308
10088
  AllProvidersFailedError,
9309
10089
  LLM2 as AnthropicLLM,
@@ -9319,6 +10099,7 @@ export {
9319
10099
  LLM4 as CerebrasLLM,
9320
10100
  ChatContext,
9321
10101
  CloudflareTunnel,
10102
+ LLM7 as CustomLLM,
9322
10103
  DEFAULT_MIN_SENTENCE_LEN,
9323
10104
  DEFAULT_PRICING,
9324
10105
  DTMF_EVENTS,
@@ -9342,7 +10123,7 @@ export {
9342
10123
  LLM5 as GoogleLLM,
9343
10124
  LLM3 as GroqLLM,
9344
10125
  Guardrail,
9345
- LLM7 as HermesLLM,
10126
+ LLM8 as HermesLLM,
9346
10127
  IVRActivity,
9347
10128
  TTS7 as InworldTTS,
9348
10129
  KrispFrameDuration,
@@ -9368,7 +10149,7 @@ export {
9368
10149
  STT3 as OpenAITranscribeSTT,
9369
10150
  OpenAITranscriptionModel,
9370
10151
  OpenAIVoice,
9371
- LLM8 as OpenClawLLM,
10152
+ LLM9 as OpenClawLLM,
9372
10153
  PRICING_LAST_UPDATED,
9373
10154
  PRICING_VERSION,
9374
10155
  PartialStreamError,
@@ -9437,6 +10218,7 @@ export {
9437
10218
  createResampler24kTo16k,
9438
10219
  createResampler24kTo8k,
9439
10220
  createResampler8kTo16k,
10221
+ custom,
9440
10222
  deepgram,
9441
10223
  defineTool,
9442
10224
  elevenlabs,
@@ -9448,6 +10230,8 @@ export {
9448
10230
  geminiLive,
9449
10231
  getLogger,
9450
10232
  guardrail,
10233
+ hashCaller,
10234
+ hermes,
9451
10235
  initTracing,
9452
10236
  isRemoteUrl,
9453
10237
  isTracingEnabled,
@@ -9460,7 +10244,9 @@ export {
9460
10244
  mountDashboard,
9461
10245
  mulawToPcm16,
9462
10246
  notifyDashboard,
10247
+ openaiCompatible,
9463
10248
  openaiTts,
10249
+ openclaw,
9464
10250
  openclawConsult,
9465
10251
  openclawPostCallNotifier,
9466
10252
  pcm16ToMulaw,