getpatter 0.6.4 → 0.6.5

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.js CHANGED
@@ -6131,12 +6131,14 @@ ${systemPrompt}` : DEFAULT_PHONE_PREAMBLE;
6131
6131
  const hasAfterLlmResponse = Boolean(hookExecutor?.hasAfterLlmResponse() && hookCtx);
6132
6132
  const hasAfterLlmChunk = Boolean(hookExecutor?.hasAfterLlmChunk());
6133
6133
  const allEmittedText = [];
6134
+ const callId = callContext.call_id;
6135
+ const streamOpts = typeof callId === "string" && callId.length > 0 ? { ...opts, callId } : opts;
6134
6136
  for (let iter = 0; iter < maxIterations; iter++) {
6135
6137
  const toolCallsAccumulated = /* @__PURE__ */ new Map();
6136
6138
  const textParts = [];
6137
6139
  let hasToolCalls = false;
6138
6140
  let usageChunkReceived = false;
6139
- for await (const chunk of this.provider.stream(messages, this.openaiTools, opts)) {
6141
+ for await (const chunk of this.provider.stream(messages, this.openaiTools, streamOpts)) {
6140
6142
  if (chunk.type === "text" && chunk.content) {
6141
6143
  const content = hasAfterLlmChunk && hookExecutor ? hookExecutor.runAfterLlmChunk(chunk.content) : chunk.content;
6142
6144
  textParts.push(content);
@@ -29655,6 +29657,14 @@ Avoid:
29655
29657
  chunker.reset();
29656
29658
  getLogger().error(`LLM loop error (${label}):`, e);
29657
29659
  this.metricsAcc.recordTurnInterrupted();
29660
+ const fallback = this.deps.agent.llmErrorMessage;
29661
+ if (fallback && !ttsFirstByteSent.value && this.isSpeaking) {
29662
+ try {
29663
+ await this.synthesizeSentence(fallback, hookExecutor, hookCtx, ttsFirstByteSent);
29664
+ } catch (err) {
29665
+ getLogger().error(`llmErrorMessage fallback synthesis failed (${label}):`, err);
29666
+ }
29667
+ }
29658
29668
  }
29659
29669
  }
29660
29670
  this.metricsAcc.recordLlmComplete();
@@ -34205,6 +34215,7 @@ __export(index_exports, {
34205
34215
  GoogleLLM: () => LLM5,
34206
34216
  GroqLLM: () => LLM3,
34207
34217
  Guardrail: () => Guardrail,
34218
+ HermesLLM: () => LLM7,
34208
34219
  IVRActivity: () => IVRActivity,
34209
34220
  InworldTTS: () => TTS7,
34210
34221
  KrispFrameDuration: () => KrispFrameDuration,
@@ -34215,6 +34226,8 @@ __export(index_exports, {
34215
34226
  MetricsStore: () => MetricsStore,
34216
34227
  MinWordsStrategy: () => MinWordsStrategy,
34217
34228
  Ngrok: () => Ngrok,
34229
+ OpenAICompatibleLLM: () => LLM6,
34230
+ OpenAICompatibleLLMProvider: () => OpenAICompatibleLLMProvider,
34218
34231
  OpenAILLM: () => LLM,
34219
34232
  OpenAILLMProvider: () => OpenAILLMProvider,
34220
34233
  OpenAIRealtime: () => Realtime,
@@ -34228,6 +34241,7 @@ __export(index_exports, {
34228
34241
  OpenAITranscribeSTT: () => STT3,
34229
34242
  OpenAITranscriptionModel: () => OpenAITranscriptionModel,
34230
34243
  OpenAIVoice: () => OpenAIVoice,
34244
+ OpenClawLLM: () => LLM8,
34231
34245
  PRICING_LAST_UPDATED: () => PRICING_LAST_UPDATED,
34232
34246
  PRICING_VERSION: () => PRICING_VERSION,
34233
34247
  PartialStreamError: () => PartialStreamError,
@@ -41852,6 +41866,264 @@ var LLM5 = class extends GoogleLLMProvider {
41852
41866
  }
41853
41867
  };
41854
41868
 
41869
+ // src/llm/openai-compatible.ts
41870
+ init_cjs_shims();
41871
+ init_llm_loop();
41872
+ init_errors();
41873
+ init_logger();
41874
+ init_version();
41875
+ var DEFAULT_TIMEOUT_S = 60;
41876
+ var OpenAICompatibleLLMProvider = class {
41877
+ /**
41878
+ * Stable pricing/dashboard key — read by stream-handler/metrics. Typed as
41879
+ * ``string`` (not the narrowed literal) so the Hermes / OpenClaw presets can
41880
+ * override it with their own key while still extending this class.
41881
+ */
41882
+ static providerKey = "openai_compatible";
41883
+ /** Resolved bearer; undefined for keyless gateways. */
41884
+ apiKey;
41885
+ model;
41886
+ baseUrl;
41887
+ timeoutMs;
41888
+ extraHeaders;
41889
+ sessionUserPrefix;
41890
+ sessionIdHeader;
41891
+ sessionIdPrefix;
41892
+ sessionKeyHeader;
41893
+ sessionKey;
41894
+ temperature;
41895
+ maxTokens;
41896
+ responseFormat;
41897
+ parallelToolCalls;
41898
+ toolChoice;
41899
+ seed;
41900
+ topP;
41901
+ frequencyPenalty;
41902
+ presencePenalty;
41903
+ stop;
41904
+ constructor(options) {
41905
+ if (!options.baseUrl) {
41906
+ throw new Error(
41907
+ 'OpenAICompatibleLLMProvider requires a baseUrl (e.g. "http://127.0.0.1:11434/v1").'
41908
+ );
41909
+ }
41910
+ if (!options.model) {
41911
+ throw new Error("OpenAICompatibleLLMProvider requires a model.");
41912
+ }
41913
+ this.apiKey = options.apiKey ?? (options.apiKeyEnv ? process.env[options.apiKeyEnv] : void 0);
41914
+ this.model = options.model;
41915
+ this.baseUrl = options.baseUrl;
41916
+ this.timeoutMs = (options.timeout ?? DEFAULT_TIMEOUT_S) * 1e3;
41917
+ this.extraHeaders = options.extraHeaders;
41918
+ this.sessionUserPrefix = options.sessionUserPrefix;
41919
+ this.sessionIdHeader = options.sessionIdHeader;
41920
+ this.sessionIdPrefix = options.sessionIdPrefix;
41921
+ this.sessionKeyHeader = options.sessionKeyHeader;
41922
+ this.sessionKey = options.sessionKey;
41923
+ this.temperature = options.temperature;
41924
+ this.maxTokens = options.maxTokens;
41925
+ this.responseFormat = options.responseFormat;
41926
+ this.parallelToolCalls = options.parallelToolCalls;
41927
+ this.toolChoice = options.toolChoice;
41928
+ this.seed = options.seed;
41929
+ this.topP = options.topP;
41930
+ this.frequencyPenalty = options.frequencyPenalty;
41931
+ this.presencePenalty = options.presencePenalty;
41932
+ this.stop = options.stop;
41933
+ }
41934
+ /**
41935
+ * Assemble the request headers. ``User-Agent`` is set first so any
41936
+ * ``extraHeaders`` (and the per-call session headers) layer on top without
41937
+ * silently dropping the SDK attribution, and the ``Authorization`` header is
41938
+ * only added when a key is present (keyless gateways omit it).
41939
+ *
41940
+ * The two session headers are emitted INDEPENDENTLY, each gated on its own
41941
+ * config (decoupled from ``sessionUserPrefix`` and from each other):
41942
+ * - ``sessionIdHeader`` (+ ``callId``) → ``` `${sessionIdPrefix}${callId}` ```
41943
+ * - ``sessionKeyHeader`` (+ ``sessionKey``) → the static ``sessionKey`` value.
41944
+ * ``sessionKey`` is a credential-grade memory scope and is never logged.
41945
+ */
41946
+ buildHeaders(callId) {
41947
+ const headers = {
41948
+ "Content-Type": "application/json",
41949
+ "User-Agent": `getpatter/${VERSION}`,
41950
+ ...this.extraHeaders ?? {}
41951
+ };
41952
+ if (this.apiKey) {
41953
+ headers.Authorization = `Bearer ${this.apiKey}`;
41954
+ }
41955
+ if (this.sessionIdHeader && callId) {
41956
+ headers[this.sessionIdHeader] = `${this.sessionIdPrefix ?? ""}${callId}`;
41957
+ }
41958
+ if (this.sessionKeyHeader && this.sessionKey) {
41959
+ headers[this.sessionKeyHeader] = this.sessionKey;
41960
+ }
41961
+ return headers;
41962
+ }
41963
+ /**
41964
+ * Pre-call DNS / TLS warmup for the configured endpoint. Best-effort:
41965
+ * 5 s timeout, all exceptions swallowed at debug level. The ``Authorization``
41966
+ * header is only sent when a key is present so the operator-grade bearer is
41967
+ * never echoed for keyless gateways (and the key is never logged).
41968
+ */
41969
+ async warmup() {
41970
+ try {
41971
+ const headers = {};
41972
+ if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
41973
+ await fetch(`${this.baseUrl}/models`, {
41974
+ method: "GET",
41975
+ headers,
41976
+ signal: AbortSignal.timeout(5e3)
41977
+ });
41978
+ } catch (err) {
41979
+ getLogger().debug(
41980
+ `OpenAI-compatible LLM warmup failed (best-effort): ${String(err)}`
41981
+ );
41982
+ }
41983
+ }
41984
+ /**
41985
+ * Build the request body. Mirrors the base OpenAI provider's sampling-kwarg
41986
+ * assembly and additionally sets ``user`` for session continuity when
41987
+ * ``sessionUserPrefix`` is set AND a ``callId`` is available — so the default
41988
+ * (prefix unset) behaviour is byte-identical to the base provider.
41989
+ */
41990
+ buildBody(messages, tools, callId) {
41991
+ const body = {
41992
+ model: this.model,
41993
+ messages,
41994
+ stream: true,
41995
+ stream_options: { include_usage: true }
41996
+ };
41997
+ if (this.temperature !== void 0) body.temperature = this.temperature;
41998
+ if (this.maxTokens !== void 0) body.max_completion_tokens = this.maxTokens;
41999
+ if (this.responseFormat !== void 0) body.response_format = this.responseFormat;
42000
+ if (this.parallelToolCalls !== void 0) body.parallel_tool_calls = this.parallelToolCalls;
42001
+ if (this.toolChoice !== void 0) body.tool_choice = this.toolChoice;
42002
+ if (this.seed !== void 0) body.seed = this.seed;
42003
+ if (this.topP !== void 0) body.top_p = this.topP;
42004
+ if (this.frequencyPenalty !== void 0) body.frequency_penalty = this.frequencyPenalty;
42005
+ if (this.presencePenalty !== void 0) body.presence_penalty = this.presencePenalty;
42006
+ if (this.stop !== void 0) body.stop = this.stop;
42007
+ if (tools) body.tools = tools;
42008
+ if (this.sessionUserPrefix !== void 0 && callId) {
42009
+ body.user = `${this.sessionUserPrefix}${callId}`;
42010
+ }
42011
+ return body;
42012
+ }
42013
+ /** Stream Patter-format LLM chunks from the configured chat completions API. */
42014
+ async *stream(messages, tools, opts) {
42015
+ const callId = opts?.callId;
42016
+ const body = this.buildBody(messages, tools, callId);
42017
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
42018
+ method: "POST",
42019
+ headers: this.buildHeaders(callId),
42020
+ body: JSON.stringify(body),
42021
+ signal: mergeAbortSignals(opts?.signal, AbortSignal.timeout(this.timeoutMs))
42022
+ });
42023
+ if (!response.ok) {
42024
+ const errText = await response.text();
42025
+ getLogger().error(
42026
+ `OpenAI-compatible API error: ${response.status} ${errText}`
42027
+ );
42028
+ throw new PatterConnectionError(
42029
+ `LLM API returned ${response.status}: ${errText.slice(0, 200)}`
42030
+ );
42031
+ }
42032
+ yield* parseOpenAISseStream(response);
42033
+ }
42034
+ };
42035
+ var LLM6 = class extends OpenAICompatibleLLMProvider {
42036
+ static providerKey = "openai_compatible";
42037
+ };
42038
+
42039
+ // src/llm/hermes.ts
42040
+ init_cjs_shims();
42041
+ var BASE_URL = "http://127.0.0.1:8642/v1";
42042
+ var DEFAULT_MODEL5 = "hermes-agent";
42043
+ var API_KEY_ENV = "API_SERVER_KEY";
42044
+ var MODEL_ENV = "API_SERVER_MODEL_NAME";
42045
+ var SESSION_USER_PREFIX = "patter-call-";
42046
+ var SESSION_ID_HEADER = "X-Hermes-Session-Id";
42047
+ var SESSION_ID_PREFIX = "patter-call-";
42048
+ var SESSION_KEY_HEADER = "X-Hermes-Session-Key";
42049
+ var DEFAULT_TIMEOUT_S2 = 120;
42050
+ var LLM7 = class extends OpenAICompatibleLLMProvider {
42051
+ static providerKey = "hermes";
42052
+ constructor(opts = {}) {
42053
+ const model = opts.model ?? process.env[MODEL_ENV] ?? DEFAULT_MODEL5;
42054
+ const options = {
42055
+ apiKey: opts.apiKey,
42056
+ apiKeyEnv: API_KEY_ENV,
42057
+ baseUrl: opts.baseUrl ?? BASE_URL,
42058
+ model,
42059
+ timeout: opts.timeout ?? DEFAULT_TIMEOUT_S2,
42060
+ sessionUserPrefix: SESSION_USER_PREFIX,
42061
+ sessionIdHeader: SESSION_ID_HEADER,
42062
+ sessionIdPrefix: SESSION_ID_PREFIX,
42063
+ sessionKeyHeader: SESSION_KEY_HEADER,
42064
+ sessionKey: opts.sessionKey,
42065
+ extraHeaders: opts.extraHeaders,
42066
+ temperature: opts.temperature,
42067
+ maxTokens: opts.maxTokens,
42068
+ responseFormat: opts.responseFormat,
42069
+ parallelToolCalls: opts.parallelToolCalls,
42070
+ toolChoice: opts.toolChoice,
42071
+ seed: opts.seed,
42072
+ topP: opts.topP,
42073
+ frequencyPenalty: opts.frequencyPenalty,
42074
+ presencePenalty: opts.presencePenalty,
42075
+ stop: opts.stop
42076
+ };
42077
+ super(options);
42078
+ }
42079
+ };
42080
+
42081
+ // src/llm/openclaw.ts
42082
+ init_cjs_shims();
42083
+ var BASE_URL2 = "http://127.0.0.1:18789/v1";
42084
+ var API_KEY_ENV2 = "OPENCLAW_API_KEY";
42085
+ var SESSION_HEADER = "x-openclaw-session-key";
42086
+ var SESSION_USER_PREFIX2 = "patter-call-";
42087
+ var DEFAULT_TIMEOUT_S3 = 120;
42088
+ var OPENCLAW_AGENT_RE2 = /^[A-Za-z0-9._:/-]+$/;
42089
+ var LLM8 = class extends OpenAICompatibleLLMProvider {
42090
+ static providerKey = "openclaw";
42091
+ constructor(opts) {
42092
+ const agent = opts?.agent;
42093
+ if (!agent || !OPENCLAW_AGENT_RE2.test(agent)) {
42094
+ throw new Error(
42095
+ `Invalid OpenClaw agent id: ${JSON.stringify(agent)}. Allowed characters: letters, digits, dot, underscore, colon, slash, dash.`
42096
+ );
42097
+ }
42098
+ const model = agent.includes("/") || agent.includes(":") ? agent : `openclaw/${agent}`;
42099
+ const options = {
42100
+ apiKey: opts.apiKey,
42101
+ apiKeyEnv: API_KEY_ENV2,
42102
+ baseUrl: opts.baseUrl ?? BASE_URL2,
42103
+ model,
42104
+ timeout: opts.timeout ?? DEFAULT_TIMEOUT_S3,
42105
+ sessionUserPrefix: SESSION_USER_PREFIX2,
42106
+ // Wire-identical to the prior behaviour: header value is the raw call id
42107
+ // (empty prefix), and OpenClaw's gateway also derives the session from
42108
+ // the ``user`` field above. No separate memory-scope header.
42109
+ sessionIdHeader: SESSION_HEADER,
42110
+ sessionIdPrefix: "",
42111
+ extraHeaders: opts.extraHeaders,
42112
+ temperature: opts.temperature,
42113
+ maxTokens: opts.maxTokens,
42114
+ responseFormat: opts.responseFormat,
42115
+ parallelToolCalls: opts.parallelToolCalls,
42116
+ toolChoice: opts.toolChoice,
42117
+ seed: opts.seed,
42118
+ topP: opts.topP,
42119
+ frequencyPenalty: opts.frequencyPenalty,
42120
+ presencePenalty: opts.presencePenalty,
42121
+ stop: opts.stop
42122
+ };
42123
+ super(options);
42124
+ }
42125
+ };
42126
+
41855
42127
  // src/index.ts
41856
42128
  init_silero_vad();
41857
42129
 
@@ -43425,6 +43697,7 @@ init_event_bus();
43425
43697
  GoogleLLM,
43426
43698
  GroqLLM,
43427
43699
  Guardrail,
43700
+ HermesLLM,
43428
43701
  IVRActivity,
43429
43702
  InworldTTS,
43430
43703
  KrispFrameDuration,
@@ -43435,6 +43708,8 @@ init_event_bus();
43435
43708
  MetricsStore,
43436
43709
  MinWordsStrategy,
43437
43710
  Ngrok,
43711
+ OpenAICompatibleLLM,
43712
+ OpenAICompatibleLLMProvider,
43438
43713
  OpenAILLM,
43439
43714
  OpenAILLMProvider,
43440
43715
  OpenAIRealtime,
@@ -43448,6 +43723,7 @@ init_event_bus();
43448
43723
  OpenAITranscribeSTT,
43449
43724
  OpenAITranscriptionModel,
43450
43725
  OpenAIVoice,
43726
+ OpenClawLLM,
43451
43727
  PRICING_LAST_UPDATED,
43452
43728
  PRICING_VERSION,
43453
43729
  PartialStreamError,
package/dist/index.mjs CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  openclawPostCallNotifier,
58
58
  resolveLogRoot,
59
59
  startSpan
60
- } from "./chunk-7IIV3BY4.mjs";
60
+ } from "./chunk-CRPJLVHB.mjs";
61
61
  import {
62
62
  OpenAIRealtime2Adapter,
63
63
  OpenAIRealtimeAdapter,
@@ -1078,7 +1078,7 @@ var Patter = class {
1078
1078
  }
1079
1079
  /** Run the agent in interactive terminal-test mode (no real telephony). */
1080
1080
  async test(opts) {
1081
- const { TestSession: TestSession2 } = await import("./test-mode-4QLLWYVV.mjs");
1081
+ const { TestSession: TestSession2 } = await import("./test-mode-HGHI2AUV.mjs");
1082
1082
  const session = new TestSession2();
1083
1083
  await session.run({
1084
1084
  agent: opts.agent,
@@ -7539,6 +7539,260 @@ var LLM5 = class extends GoogleLLMProvider {
7539
7539
  }
7540
7540
  };
7541
7541
 
7542
+ // src/llm/openai-compatible.ts
7543
+ init_esm_shims();
7544
+ var DEFAULT_TIMEOUT_S = 60;
7545
+ var OpenAICompatibleLLMProvider = class {
7546
+ /**
7547
+ * Stable pricing/dashboard key — read by stream-handler/metrics. Typed as
7548
+ * ``string`` (not the narrowed literal) so the Hermes / OpenClaw presets can
7549
+ * override it with their own key while still extending this class.
7550
+ */
7551
+ static providerKey = "openai_compatible";
7552
+ /** Resolved bearer; undefined for keyless gateways. */
7553
+ apiKey;
7554
+ model;
7555
+ baseUrl;
7556
+ timeoutMs;
7557
+ extraHeaders;
7558
+ sessionUserPrefix;
7559
+ sessionIdHeader;
7560
+ sessionIdPrefix;
7561
+ sessionKeyHeader;
7562
+ sessionKey;
7563
+ temperature;
7564
+ maxTokens;
7565
+ responseFormat;
7566
+ parallelToolCalls;
7567
+ toolChoice;
7568
+ seed;
7569
+ topP;
7570
+ frequencyPenalty;
7571
+ presencePenalty;
7572
+ stop;
7573
+ constructor(options) {
7574
+ if (!options.baseUrl) {
7575
+ throw new Error(
7576
+ 'OpenAICompatibleLLMProvider requires a baseUrl (e.g. "http://127.0.0.1:11434/v1").'
7577
+ );
7578
+ }
7579
+ if (!options.model) {
7580
+ throw new Error("OpenAICompatibleLLMProvider requires a model.");
7581
+ }
7582
+ this.apiKey = options.apiKey ?? (options.apiKeyEnv ? process.env[options.apiKeyEnv] : void 0);
7583
+ this.model = options.model;
7584
+ this.baseUrl = options.baseUrl;
7585
+ this.timeoutMs = (options.timeout ?? DEFAULT_TIMEOUT_S) * 1e3;
7586
+ this.extraHeaders = options.extraHeaders;
7587
+ this.sessionUserPrefix = options.sessionUserPrefix;
7588
+ this.sessionIdHeader = options.sessionIdHeader;
7589
+ this.sessionIdPrefix = options.sessionIdPrefix;
7590
+ this.sessionKeyHeader = options.sessionKeyHeader;
7591
+ this.sessionKey = options.sessionKey;
7592
+ this.temperature = options.temperature;
7593
+ this.maxTokens = options.maxTokens;
7594
+ this.responseFormat = options.responseFormat;
7595
+ this.parallelToolCalls = options.parallelToolCalls;
7596
+ this.toolChoice = options.toolChoice;
7597
+ this.seed = options.seed;
7598
+ this.topP = options.topP;
7599
+ this.frequencyPenalty = options.frequencyPenalty;
7600
+ this.presencePenalty = options.presencePenalty;
7601
+ this.stop = options.stop;
7602
+ }
7603
+ /**
7604
+ * Assemble the request headers. ``User-Agent`` is set first so any
7605
+ * ``extraHeaders`` (and the per-call session headers) layer on top without
7606
+ * silently dropping the SDK attribution, and the ``Authorization`` header is
7607
+ * only added when a key is present (keyless gateways omit it).
7608
+ *
7609
+ * The two session headers are emitted INDEPENDENTLY, each gated on its own
7610
+ * config (decoupled from ``sessionUserPrefix`` and from each other):
7611
+ * - ``sessionIdHeader`` (+ ``callId``) → ``` `${sessionIdPrefix}${callId}` ```
7612
+ * - ``sessionKeyHeader`` (+ ``sessionKey``) → the static ``sessionKey`` value.
7613
+ * ``sessionKey`` is a credential-grade memory scope and is never logged.
7614
+ */
7615
+ buildHeaders(callId) {
7616
+ const headers = {
7617
+ "Content-Type": "application/json",
7618
+ "User-Agent": `getpatter/${VERSION}`,
7619
+ ...this.extraHeaders ?? {}
7620
+ };
7621
+ if (this.apiKey) {
7622
+ headers.Authorization = `Bearer ${this.apiKey}`;
7623
+ }
7624
+ if (this.sessionIdHeader && callId) {
7625
+ headers[this.sessionIdHeader] = `${this.sessionIdPrefix ?? ""}${callId}`;
7626
+ }
7627
+ if (this.sessionKeyHeader && this.sessionKey) {
7628
+ headers[this.sessionKeyHeader] = this.sessionKey;
7629
+ }
7630
+ return headers;
7631
+ }
7632
+ /**
7633
+ * Pre-call DNS / TLS warmup for the configured endpoint. Best-effort:
7634
+ * 5 s timeout, all exceptions swallowed at debug level. The ``Authorization``
7635
+ * header is only sent when a key is present so the operator-grade bearer is
7636
+ * never echoed for keyless gateways (and the key is never logged).
7637
+ */
7638
+ async warmup() {
7639
+ try {
7640
+ const headers = {};
7641
+ if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
7642
+ await fetch(`${this.baseUrl}/models`, {
7643
+ method: "GET",
7644
+ headers,
7645
+ signal: AbortSignal.timeout(5e3)
7646
+ });
7647
+ } catch (err) {
7648
+ getLogger().debug(
7649
+ `OpenAI-compatible LLM warmup failed (best-effort): ${String(err)}`
7650
+ );
7651
+ }
7652
+ }
7653
+ /**
7654
+ * Build the request body. Mirrors the base OpenAI provider's sampling-kwarg
7655
+ * assembly and additionally sets ``user`` for session continuity when
7656
+ * ``sessionUserPrefix`` is set AND a ``callId`` is available — so the default
7657
+ * (prefix unset) behaviour is byte-identical to the base provider.
7658
+ */
7659
+ buildBody(messages, tools, callId) {
7660
+ const body = {
7661
+ model: this.model,
7662
+ messages,
7663
+ stream: true,
7664
+ stream_options: { include_usage: true }
7665
+ };
7666
+ if (this.temperature !== void 0) body.temperature = this.temperature;
7667
+ if (this.maxTokens !== void 0) body.max_completion_tokens = this.maxTokens;
7668
+ if (this.responseFormat !== void 0) body.response_format = this.responseFormat;
7669
+ if (this.parallelToolCalls !== void 0) body.parallel_tool_calls = this.parallelToolCalls;
7670
+ if (this.toolChoice !== void 0) body.tool_choice = this.toolChoice;
7671
+ if (this.seed !== void 0) body.seed = this.seed;
7672
+ if (this.topP !== void 0) body.top_p = this.topP;
7673
+ if (this.frequencyPenalty !== void 0) body.frequency_penalty = this.frequencyPenalty;
7674
+ if (this.presencePenalty !== void 0) body.presence_penalty = this.presencePenalty;
7675
+ if (this.stop !== void 0) body.stop = this.stop;
7676
+ if (tools) body.tools = tools;
7677
+ if (this.sessionUserPrefix !== void 0 && callId) {
7678
+ body.user = `${this.sessionUserPrefix}${callId}`;
7679
+ }
7680
+ return body;
7681
+ }
7682
+ /** Stream Patter-format LLM chunks from the configured chat completions API. */
7683
+ async *stream(messages, tools, opts) {
7684
+ const callId = opts?.callId;
7685
+ const body = this.buildBody(messages, tools, callId);
7686
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
7687
+ method: "POST",
7688
+ headers: this.buildHeaders(callId),
7689
+ body: JSON.stringify(body),
7690
+ signal: mergeAbortSignals(opts?.signal, AbortSignal.timeout(this.timeoutMs))
7691
+ });
7692
+ if (!response.ok) {
7693
+ const errText = await response.text();
7694
+ getLogger().error(
7695
+ `OpenAI-compatible API error: ${response.status} ${errText}`
7696
+ );
7697
+ throw new PatterConnectionError(
7698
+ `LLM API returned ${response.status}: ${errText.slice(0, 200)}`
7699
+ );
7700
+ }
7701
+ yield* parseOpenAISseStream(response);
7702
+ }
7703
+ };
7704
+ var LLM6 = class extends OpenAICompatibleLLMProvider {
7705
+ static providerKey = "openai_compatible";
7706
+ };
7707
+
7708
+ // src/llm/hermes.ts
7709
+ init_esm_shims();
7710
+ var BASE_URL = "http://127.0.0.1:8642/v1";
7711
+ var DEFAULT_MODEL5 = "hermes-agent";
7712
+ var API_KEY_ENV = "API_SERVER_KEY";
7713
+ var MODEL_ENV = "API_SERVER_MODEL_NAME";
7714
+ var SESSION_USER_PREFIX = "patter-call-";
7715
+ var SESSION_ID_HEADER = "X-Hermes-Session-Id";
7716
+ var SESSION_ID_PREFIX = "patter-call-";
7717
+ var SESSION_KEY_HEADER = "X-Hermes-Session-Key";
7718
+ var DEFAULT_TIMEOUT_S2 = 120;
7719
+ var LLM7 = class extends OpenAICompatibleLLMProvider {
7720
+ static providerKey = "hermes";
7721
+ constructor(opts = {}) {
7722
+ const model = opts.model ?? process.env[MODEL_ENV] ?? DEFAULT_MODEL5;
7723
+ const options = {
7724
+ apiKey: opts.apiKey,
7725
+ apiKeyEnv: API_KEY_ENV,
7726
+ baseUrl: opts.baseUrl ?? BASE_URL,
7727
+ model,
7728
+ timeout: opts.timeout ?? DEFAULT_TIMEOUT_S2,
7729
+ sessionUserPrefix: SESSION_USER_PREFIX,
7730
+ sessionIdHeader: SESSION_ID_HEADER,
7731
+ sessionIdPrefix: SESSION_ID_PREFIX,
7732
+ sessionKeyHeader: SESSION_KEY_HEADER,
7733
+ sessionKey: opts.sessionKey,
7734
+ extraHeaders: opts.extraHeaders,
7735
+ temperature: opts.temperature,
7736
+ maxTokens: opts.maxTokens,
7737
+ responseFormat: opts.responseFormat,
7738
+ parallelToolCalls: opts.parallelToolCalls,
7739
+ toolChoice: opts.toolChoice,
7740
+ seed: opts.seed,
7741
+ topP: opts.topP,
7742
+ frequencyPenalty: opts.frequencyPenalty,
7743
+ presencePenalty: opts.presencePenalty,
7744
+ stop: opts.stop
7745
+ };
7746
+ super(options);
7747
+ }
7748
+ };
7749
+
7750
+ // src/llm/openclaw.ts
7751
+ init_esm_shims();
7752
+ var BASE_URL2 = "http://127.0.0.1:18789/v1";
7753
+ var API_KEY_ENV2 = "OPENCLAW_API_KEY";
7754
+ var SESSION_HEADER = "x-openclaw-session-key";
7755
+ var SESSION_USER_PREFIX2 = "patter-call-";
7756
+ var DEFAULT_TIMEOUT_S3 = 120;
7757
+ var OPENCLAW_AGENT_RE = /^[A-Za-z0-9._:/-]+$/;
7758
+ var LLM8 = class extends OpenAICompatibleLLMProvider {
7759
+ static providerKey = "openclaw";
7760
+ constructor(opts) {
7761
+ const agent = opts?.agent;
7762
+ if (!agent || !OPENCLAW_AGENT_RE.test(agent)) {
7763
+ throw new Error(
7764
+ `Invalid OpenClaw agent id: ${JSON.stringify(agent)}. Allowed characters: letters, digits, dot, underscore, colon, slash, dash.`
7765
+ );
7766
+ }
7767
+ const model = agent.includes("/") || agent.includes(":") ? agent : `openclaw/${agent}`;
7768
+ const options = {
7769
+ apiKey: opts.apiKey,
7770
+ apiKeyEnv: API_KEY_ENV2,
7771
+ baseUrl: opts.baseUrl ?? BASE_URL2,
7772
+ model,
7773
+ timeout: opts.timeout ?? DEFAULT_TIMEOUT_S3,
7774
+ sessionUserPrefix: SESSION_USER_PREFIX2,
7775
+ // Wire-identical to the prior behaviour: header value is the raw call id
7776
+ // (empty prefix), and OpenClaw's gateway also derives the session from
7777
+ // the ``user`` field above. No separate memory-scope header.
7778
+ sessionIdHeader: SESSION_HEADER,
7779
+ sessionIdPrefix: "",
7780
+ extraHeaders: opts.extraHeaders,
7781
+ temperature: opts.temperature,
7782
+ maxTokens: opts.maxTokens,
7783
+ responseFormat: opts.responseFormat,
7784
+ parallelToolCalls: opts.parallelToolCalls,
7785
+ toolChoice: opts.toolChoice,
7786
+ seed: opts.seed,
7787
+ topP: opts.topP,
7788
+ frequencyPenalty: opts.frequencyPenalty,
7789
+ presencePenalty: opts.presencePenalty,
7790
+ stop: opts.stop
7791
+ };
7792
+ super(options);
7793
+ }
7794
+ };
7795
+
7542
7796
  // src/providers/deepfilternet-filter.ts
7543
7797
  init_esm_shims();
7544
7798
  function log() {
@@ -9088,6 +9342,7 @@ export {
9088
9342
  LLM5 as GoogleLLM,
9089
9343
  LLM3 as GroqLLM,
9090
9344
  Guardrail,
9345
+ LLM7 as HermesLLM,
9091
9346
  IVRActivity,
9092
9347
  TTS7 as InworldTTS,
9093
9348
  KrispFrameDuration,
@@ -9098,6 +9353,8 @@ export {
9098
9353
  MetricsStore,
9099
9354
  MinWordsStrategy,
9100
9355
  Ngrok,
9356
+ LLM6 as OpenAICompatibleLLM,
9357
+ OpenAICompatibleLLMProvider,
9101
9358
  LLM as OpenAILLM,
9102
9359
  OpenAILLMProvider,
9103
9360
  Realtime as OpenAIRealtime,
@@ -9111,6 +9368,7 @@ export {
9111
9368
  STT3 as OpenAITranscribeSTT,
9112
9369
  OpenAITranscriptionModel,
9113
9370
  OpenAIVoice,
9371
+ LLM8 as OpenClawLLM,
9114
9372
  PRICING_LAST_UPDATED,
9115
9373
  PRICING_VERSION,
9116
9374
  PartialStreamError,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TestSession
3
- } from "./chunk-7IIV3BY4.mjs";
3
+ } from "./chunk-CRPJLVHB.mjs";
4
4
  import "./chunk-BO227NTF.mjs";
5
5
  import "./chunk-MVOQFAEO.mjs";
6
6
  import "./chunk-N565J3CF.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getpatter",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "Open-source voice AI SDK — connect any AI agent to real phone calls in 4 lines of code",
5
5
  "license": "MIT",
6
6
  "author": {