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/{chunk-7IIV3BY4.mjs → chunk-CRPJLVHB.mjs} +11 -1
- package/dist/index.d.mts +384 -6
- package/dist/index.d.ts +384 -6
- package/dist/index.js +277 -1
- package/dist/index.mjs +260 -2
- package/dist/{test-mode-4QLLWYVV.mjs → test-mode-HGHI2AUV.mjs} +1 -1
- package/package.json +1 -1
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,
|
|
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-
|
|
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-
|
|
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,
|