getpatter 0.5.0 → 0.5.1
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/README.md +25 -1
- package/dist/{chunk-JO5C35FM.mjs → chunk-AKQFOFLG.mjs} +1 -1
- package/dist/{chunk-757NVN4L.mjs → chunk-B6C3KIBG.mjs} +48 -23
- package/dist/index.d.mts +363 -56
- package/dist/index.d.ts +363 -56
- package/dist/index.js +690 -850
- package/dist/index.mjs +634 -4
- package/dist/{test-mode-YFOL2HYH.mjs → test-mode-JZMYE5HY.mjs} +1 -1
- package/dist/{tunnel-BL7A7GXW.mjs → tunnel-O7ICMSTP.mjs} +1 -1
- package/package.json +1 -1
- package/dist/lib-4WCAS54J.mjs +0 -830
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-AFUYSNDH.mjs";
|
|
4
4
|
import {
|
|
5
5
|
startTunnel
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-AKQFOFLG.mjs";
|
|
7
7
|
import {
|
|
8
8
|
CallMetricsAccumulator,
|
|
9
9
|
DEFAULT_MIN_SENTENCE_LEN,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
resample16kTo8k,
|
|
37
37
|
resample24kTo16k,
|
|
38
38
|
resample8kTo16k
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-B6C3KIBG.mjs";
|
|
40
40
|
import {
|
|
41
41
|
getLogger,
|
|
42
42
|
setLogger
|
|
@@ -351,6 +351,8 @@ var Patter = class {
|
|
|
351
351
|
"Unknown engine. Expected OpenAIRealtime or ElevenLabsConvAI instance."
|
|
352
352
|
);
|
|
353
353
|
}
|
|
354
|
+
} else if (!working.provider && (working.stt !== void 0 || working.tts !== void 0 || working.llm !== void 0)) {
|
|
355
|
+
working = { ...working, provider: "pipeline" };
|
|
354
356
|
}
|
|
355
357
|
if (working.provider) {
|
|
356
358
|
const valid = ["openai_realtime", "elevenlabs_convai", "pipeline"];
|
|
@@ -358,6 +360,19 @@ var Patter = class {
|
|
|
358
360
|
throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${working.provider}'`);
|
|
359
361
|
}
|
|
360
362
|
}
|
|
363
|
+
if (working.llm !== void 0) {
|
|
364
|
+
const llm = working.llm;
|
|
365
|
+
if (!llm || typeof llm.stream !== "function") {
|
|
366
|
+
throw new Error(
|
|
367
|
+
"`llm` must be an LLMProvider instance (e.g. new AnthropicLLM()). Got a value without a `.stream` method."
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (working.engine) {
|
|
371
|
+
getLogger().warn(
|
|
372
|
+
"agent({ engine, llm }): `llm` is ignored when `engine` is set \u2014 realtime/ConvAI engines run their own model. Remove `llm` or switch to pipeline mode (stt + tts + llm) to silence this warning."
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
361
376
|
if (working.tools) {
|
|
362
377
|
if (!Array.isArray(working.tools)) {
|
|
363
378
|
throw new TypeError("tools must be an array");
|
|
@@ -401,7 +416,7 @@ var Patter = class {
|
|
|
401
416
|
throw new Error("Cannot use both tunnel: true and webhookUrl. Pick one.");
|
|
402
417
|
}
|
|
403
418
|
if (wantsCloudflared) {
|
|
404
|
-
const { startTunnel: startTunnel2 } = await import("./tunnel-
|
|
419
|
+
const { startTunnel: startTunnel2 } = await import("./tunnel-O7ICMSTP.mjs");
|
|
405
420
|
this.tunnelHandle = await startTunnel2(port);
|
|
406
421
|
webhookUrl = this.tunnelHandle.hostname;
|
|
407
422
|
}
|
|
@@ -452,7 +467,7 @@ var Patter = class {
|
|
|
452
467
|
if (this.mode !== "local") {
|
|
453
468
|
throw new Error("test() is only available in local mode");
|
|
454
469
|
}
|
|
455
|
-
const { TestSession: TestSession2 } = await import("./test-mode-
|
|
470
|
+
const { TestSession: TestSession2 } = await import("./test-mode-JZMYE5HY.mjs");
|
|
456
471
|
const session = new TestSession2();
|
|
457
472
|
await session.run({
|
|
458
473
|
agent: opts.agent,
|
|
@@ -2850,6 +2865,616 @@ var TTS5 = class extends LMNTTTS {
|
|
|
2850
2865
|
}
|
|
2851
2866
|
};
|
|
2852
2867
|
|
|
2868
|
+
// src/llm/openai.ts
|
|
2869
|
+
var LLM = class extends OpenAILLMProvider {
|
|
2870
|
+
constructor(opts = {}) {
|
|
2871
|
+
const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
|
|
2872
|
+
if (!key) {
|
|
2873
|
+
throw new Error(
|
|
2874
|
+
"OpenAI LLM requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY."
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
super(key, opts.model ?? "gpt-4o-mini");
|
|
2878
|
+
}
|
|
2879
|
+
};
|
|
2880
|
+
|
|
2881
|
+
// src/providers/anthropic-llm.ts
|
|
2882
|
+
var DEFAULT_ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
|
|
2883
|
+
var DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
2884
|
+
var DEFAULT_MODEL = "claude-3-5-sonnet-20241022";
|
|
2885
|
+
var DEFAULT_MAX_TOKENS = 1024;
|
|
2886
|
+
var AnthropicLLMProvider = class {
|
|
2887
|
+
apiKey;
|
|
2888
|
+
model;
|
|
2889
|
+
maxTokens;
|
|
2890
|
+
temperature;
|
|
2891
|
+
url;
|
|
2892
|
+
anthropicVersion;
|
|
2893
|
+
constructor(options) {
|
|
2894
|
+
if (!options.apiKey) {
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
"Anthropic API key is required. Pass it via { apiKey } or set the ANTHROPIC_API_KEY environment variable before constructing the provider."
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
this.apiKey = options.apiKey;
|
|
2900
|
+
this.model = options.model ?? DEFAULT_MODEL;
|
|
2901
|
+
this.maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
2902
|
+
this.temperature = options.temperature;
|
|
2903
|
+
this.url = options.baseUrl ?? DEFAULT_ANTHROPIC_URL;
|
|
2904
|
+
this.anthropicVersion = options.anthropicVersion ?? DEFAULT_ANTHROPIC_VERSION;
|
|
2905
|
+
}
|
|
2906
|
+
async *stream(messages, tools) {
|
|
2907
|
+
const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
|
|
2908
|
+
const anthropicTools = tools ? toAnthropicTools(tools) : null;
|
|
2909
|
+
const body = {
|
|
2910
|
+
model: this.model,
|
|
2911
|
+
messages: anthropicMessages,
|
|
2912
|
+
max_tokens: this.maxTokens,
|
|
2913
|
+
stream: true
|
|
2914
|
+
};
|
|
2915
|
+
if (system) body.system = system;
|
|
2916
|
+
if (anthropicTools && anthropicTools.length > 0) body.tools = anthropicTools;
|
|
2917
|
+
if (this.temperature !== void 0) body.temperature = this.temperature;
|
|
2918
|
+
const response = await fetch(this.url, {
|
|
2919
|
+
method: "POST",
|
|
2920
|
+
headers: {
|
|
2921
|
+
"Content-Type": "application/json",
|
|
2922
|
+
"x-api-key": this.apiKey,
|
|
2923
|
+
"anthropic-version": this.anthropicVersion
|
|
2924
|
+
},
|
|
2925
|
+
body: JSON.stringify(body),
|
|
2926
|
+
signal: AbortSignal.timeout(3e4)
|
|
2927
|
+
});
|
|
2928
|
+
if (!response.ok) {
|
|
2929
|
+
const errText = await response.text();
|
|
2930
|
+
getLogger().error(`Anthropic API error: ${response.status} ${errText}`);
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
const reader = response.body?.getReader();
|
|
2934
|
+
if (!reader) return;
|
|
2935
|
+
const decoder = new TextDecoder();
|
|
2936
|
+
let buffer = "";
|
|
2937
|
+
const toolIndexByBlock = /* @__PURE__ */ new Map();
|
|
2938
|
+
const toolIdByBlock = /* @__PURE__ */ new Map();
|
|
2939
|
+
let nextIndex = 0;
|
|
2940
|
+
while (true) {
|
|
2941
|
+
const { done, value } = await reader.read();
|
|
2942
|
+
if (done) break;
|
|
2943
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2944
|
+
const lines = buffer.split("\n");
|
|
2945
|
+
buffer = lines.pop() || "";
|
|
2946
|
+
for (const line of lines) {
|
|
2947
|
+
const trimmed = line.trim();
|
|
2948
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
2949
|
+
const data = trimmed.slice(6);
|
|
2950
|
+
if (!data || data === "[DONE]") continue;
|
|
2951
|
+
let event;
|
|
2952
|
+
try {
|
|
2953
|
+
event = JSON.parse(data);
|
|
2954
|
+
} catch {
|
|
2955
|
+
continue;
|
|
2956
|
+
}
|
|
2957
|
+
if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
2958
|
+
const blockIdx = event.index ?? 0;
|
|
2959
|
+
const toolId = event.content_block.id ?? "";
|
|
2960
|
+
const toolName = event.content_block.name ?? "";
|
|
2961
|
+
const patterIndex = nextIndex++;
|
|
2962
|
+
toolIndexByBlock.set(blockIdx, patterIndex);
|
|
2963
|
+
toolIdByBlock.set(blockIdx, toolId);
|
|
2964
|
+
yield {
|
|
2965
|
+
type: "tool_call",
|
|
2966
|
+
index: patterIndex,
|
|
2967
|
+
id: toolId,
|
|
2968
|
+
name: toolName,
|
|
2969
|
+
arguments: ""
|
|
2970
|
+
};
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
if (event.type === "content_block_delta") {
|
|
2974
|
+
if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
2975
|
+
yield { type: "text", content: event.delta.text };
|
|
2976
|
+
continue;
|
|
2977
|
+
}
|
|
2978
|
+
if (event.delta?.type === "input_json_delta" && event.delta.partial_json) {
|
|
2979
|
+
const blockIdx = event.index ?? 0;
|
|
2980
|
+
const patterIndex = toolIndexByBlock.get(blockIdx);
|
|
2981
|
+
if (patterIndex !== void 0) {
|
|
2982
|
+
yield {
|
|
2983
|
+
type: "tool_call",
|
|
2984
|
+
index: patterIndex,
|
|
2985
|
+
id: toolIdByBlock.get(blockIdx),
|
|
2986
|
+
arguments: event.delta.partial_json
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
yield { type: "done" };
|
|
2994
|
+
}
|
|
2995
|
+
};
|
|
2996
|
+
function toAnthropicTools(tools) {
|
|
2997
|
+
return tools.map((t) => {
|
|
2998
|
+
const fn = t.function ?? t;
|
|
2999
|
+
return {
|
|
3000
|
+
name: String(fn.name ?? ""),
|
|
3001
|
+
description: String(fn.description ?? ""),
|
|
3002
|
+
input_schema: fn.parameters ?? { type: "object", properties: {} }
|
|
3003
|
+
};
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
function toAnthropicMessages(messages) {
|
|
3007
|
+
const systemParts = [];
|
|
3008
|
+
const out = [];
|
|
3009
|
+
for (const rawMsg of messages) {
|
|
3010
|
+
const role = rawMsg.role;
|
|
3011
|
+
if (role === "system") {
|
|
3012
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
3013
|
+
systemParts.push(rawMsg.content);
|
|
3014
|
+
}
|
|
3015
|
+
continue;
|
|
3016
|
+
}
|
|
3017
|
+
if (role === "user") {
|
|
3018
|
+
if (typeof rawMsg.content === "string") {
|
|
3019
|
+
out.push({ role: "user", content: rawMsg.content });
|
|
3020
|
+
} else if (rawMsg.content) {
|
|
3021
|
+
out.push({ role: "user", content: rawMsg.content });
|
|
3022
|
+
}
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
if (role === "assistant") {
|
|
3026
|
+
const blocks = [];
|
|
3027
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
3028
|
+
blocks.push({ type: "text", text: rawMsg.content });
|
|
3029
|
+
}
|
|
3030
|
+
for (const tc of rawMsg.tool_calls ?? []) {
|
|
3031
|
+
let args = {};
|
|
3032
|
+
try {
|
|
3033
|
+
args = JSON.parse(tc.function?.arguments ?? "{}");
|
|
3034
|
+
} catch {
|
|
3035
|
+
args = {};
|
|
3036
|
+
}
|
|
3037
|
+
blocks.push({
|
|
3038
|
+
type: "tool_use",
|
|
3039
|
+
id: tc.id ?? "",
|
|
3040
|
+
name: tc.function?.name ?? "",
|
|
3041
|
+
input: args
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
if (blocks.length > 0) {
|
|
3045
|
+
out.push({ role: "assistant", content: blocks });
|
|
3046
|
+
}
|
|
3047
|
+
continue;
|
|
3048
|
+
}
|
|
3049
|
+
if (role === "tool") {
|
|
3050
|
+
const contentStr = typeof rawMsg.content === "string" ? rawMsg.content : JSON.stringify(rawMsg.content);
|
|
3051
|
+
out.push({
|
|
3052
|
+
role: "user",
|
|
3053
|
+
content: [
|
|
3054
|
+
{
|
|
3055
|
+
type: "tool_result",
|
|
3056
|
+
tool_use_id: rawMsg.tool_call_id ?? "",
|
|
3057
|
+
content: contentStr
|
|
3058
|
+
}
|
|
3059
|
+
]
|
|
3060
|
+
});
|
|
3061
|
+
continue;
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
return { system: systemParts.join("\n\n"), messages: out };
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
// src/llm/anthropic.ts
|
|
3068
|
+
var LLM2 = class extends AnthropicLLMProvider {
|
|
3069
|
+
constructor(opts = {}) {
|
|
3070
|
+
const key = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
3071
|
+
if (!key) {
|
|
3072
|
+
throw new Error(
|
|
3073
|
+
"Anthropic LLM requires an apiKey. Pass { apiKey: 'sk-ant-...' } or set ANTHROPIC_API_KEY."
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
super({
|
|
3077
|
+
apiKey: key,
|
|
3078
|
+
model: opts.model,
|
|
3079
|
+
maxTokens: opts.maxTokens,
|
|
3080
|
+
temperature: opts.temperature,
|
|
3081
|
+
baseUrl: opts.baseUrl,
|
|
3082
|
+
anthropicVersion: opts.anthropicVersion
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
3085
|
+
};
|
|
3086
|
+
|
|
3087
|
+
// src/providers/groq-llm.ts
|
|
3088
|
+
var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
|
|
3089
|
+
var DEFAULT_MODEL2 = "llama-3.3-70b-versatile";
|
|
3090
|
+
var GroqLLMProvider = class {
|
|
3091
|
+
apiKey;
|
|
3092
|
+
model;
|
|
3093
|
+
baseUrl;
|
|
3094
|
+
constructor(options) {
|
|
3095
|
+
if (!options.apiKey) {
|
|
3096
|
+
throw new Error(
|
|
3097
|
+
"Groq API key is required. Pass it via { apiKey } or read GROQ_API_KEY from the environment."
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
this.apiKey = options.apiKey;
|
|
3101
|
+
this.model = options.model ?? DEFAULT_MODEL2;
|
|
3102
|
+
this.baseUrl = options.baseUrl ?? GROQ_BASE_URL;
|
|
3103
|
+
}
|
|
3104
|
+
async *stream(messages, tools) {
|
|
3105
|
+
const body = {
|
|
3106
|
+
model: this.model,
|
|
3107
|
+
messages,
|
|
3108
|
+
stream: true
|
|
3109
|
+
};
|
|
3110
|
+
if (tools) body.tools = tools;
|
|
3111
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
3112
|
+
method: "POST",
|
|
3113
|
+
headers: {
|
|
3114
|
+
"Content-Type": "application/json",
|
|
3115
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
3116
|
+
},
|
|
3117
|
+
body: JSON.stringify(body),
|
|
3118
|
+
signal: AbortSignal.timeout(3e4)
|
|
3119
|
+
});
|
|
3120
|
+
if (!response.ok) {
|
|
3121
|
+
const errText = await response.text();
|
|
3122
|
+
getLogger().error(`Groq API error: ${response.status} ${errText}`);
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
yield* parseOpenAISseStream(response);
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
async function* parseOpenAISseStream(response) {
|
|
3129
|
+
const reader = response.body?.getReader();
|
|
3130
|
+
if (!reader) return;
|
|
3131
|
+
const decoder = new TextDecoder();
|
|
3132
|
+
let buffer = "";
|
|
3133
|
+
while (true) {
|
|
3134
|
+
const { done, value } = await reader.read();
|
|
3135
|
+
if (done) break;
|
|
3136
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3137
|
+
const lines = buffer.split("\n");
|
|
3138
|
+
buffer = lines.pop() || "";
|
|
3139
|
+
for (const line of lines) {
|
|
3140
|
+
const trimmed = line.trim();
|
|
3141
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
3142
|
+
const data = trimmed.slice(6);
|
|
3143
|
+
if (data === "[DONE]") continue;
|
|
3144
|
+
let chunk;
|
|
3145
|
+
try {
|
|
3146
|
+
chunk = JSON.parse(data);
|
|
3147
|
+
} catch {
|
|
3148
|
+
continue;
|
|
3149
|
+
}
|
|
3150
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
3151
|
+
if (!delta) continue;
|
|
3152
|
+
if (delta.content) {
|
|
3153
|
+
yield { type: "text", content: delta.content };
|
|
3154
|
+
}
|
|
3155
|
+
if (delta.tool_calls) {
|
|
3156
|
+
for (const tc of delta.tool_calls) {
|
|
3157
|
+
yield {
|
|
3158
|
+
type: "tool_call",
|
|
3159
|
+
index: tc.index,
|
|
3160
|
+
id: tc.id,
|
|
3161
|
+
name: tc.function?.name,
|
|
3162
|
+
arguments: tc.function?.arguments
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
// src/llm/groq.ts
|
|
3171
|
+
var LLM3 = class extends GroqLLMProvider {
|
|
3172
|
+
constructor(opts = {}) {
|
|
3173
|
+
const key = opts.apiKey ?? process.env.GROQ_API_KEY;
|
|
3174
|
+
if (!key) {
|
|
3175
|
+
throw new Error(
|
|
3176
|
+
"Groq LLM requires an apiKey. Pass { apiKey: 'gsk_...' } or set GROQ_API_KEY."
|
|
3177
|
+
);
|
|
3178
|
+
}
|
|
3179
|
+
super({
|
|
3180
|
+
apiKey: key,
|
|
3181
|
+
model: opts.model,
|
|
3182
|
+
baseUrl: opts.baseUrl
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
};
|
|
3186
|
+
|
|
3187
|
+
// src/providers/cerebras-llm.ts
|
|
3188
|
+
var CEREBRAS_BASE_URL = "https://api.cerebras.ai/v1";
|
|
3189
|
+
var DEFAULT_MODEL3 = "llama3.1-8b";
|
|
3190
|
+
var CerebrasLLMProvider = class {
|
|
3191
|
+
apiKey;
|
|
3192
|
+
model;
|
|
3193
|
+
baseUrl;
|
|
3194
|
+
gzipCompression;
|
|
3195
|
+
constructor(options) {
|
|
3196
|
+
if (!options.apiKey) {
|
|
3197
|
+
throw new Error(
|
|
3198
|
+
"Cerebras API key is required. Pass it via { apiKey } or read CEREBRAS_API_KEY from the environment."
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
this.apiKey = options.apiKey;
|
|
3202
|
+
this.model = options.model ?? DEFAULT_MODEL3;
|
|
3203
|
+
this.baseUrl = options.baseUrl ?? CEREBRAS_BASE_URL;
|
|
3204
|
+
this.gzipCompression = options.gzipCompression ?? false;
|
|
3205
|
+
}
|
|
3206
|
+
async *stream(messages, tools) {
|
|
3207
|
+
const body = {
|
|
3208
|
+
model: this.model,
|
|
3209
|
+
messages,
|
|
3210
|
+
stream: true
|
|
3211
|
+
};
|
|
3212
|
+
if (tools) body.tools = tools;
|
|
3213
|
+
const headers = {
|
|
3214
|
+
"Content-Type": "application/json",
|
|
3215
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
3216
|
+
};
|
|
3217
|
+
let payload = JSON.stringify(body);
|
|
3218
|
+
if (this.gzipCompression) {
|
|
3219
|
+
const compressed = await gzipEncode(payload);
|
|
3220
|
+
if (compressed) {
|
|
3221
|
+
payload = compressed;
|
|
3222
|
+
headers["Content-Encoding"] = "gzip";
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
3226
|
+
method: "POST",
|
|
3227
|
+
headers,
|
|
3228
|
+
body: payload,
|
|
3229
|
+
signal: AbortSignal.timeout(3e4)
|
|
3230
|
+
});
|
|
3231
|
+
if (!response.ok) {
|
|
3232
|
+
const errText = await response.text();
|
|
3233
|
+
getLogger().error(`Cerebras API error: ${response.status} ${errText}`);
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
yield* parseOpenAISseStream(response);
|
|
3237
|
+
}
|
|
3238
|
+
};
|
|
3239
|
+
async function gzipEncode(data) {
|
|
3240
|
+
const CompressionCtor = globalThis.CompressionStream;
|
|
3241
|
+
if (!CompressionCtor) return null;
|
|
3242
|
+
const stream = new CompressionCtor("gzip");
|
|
3243
|
+
const writer = stream.writable.getWriter();
|
|
3244
|
+
const encoder = new TextEncoder();
|
|
3245
|
+
await writer.write(encoder.encode(data));
|
|
3246
|
+
await writer.close();
|
|
3247
|
+
const chunks = [];
|
|
3248
|
+
const reader = stream.readable.getReader();
|
|
3249
|
+
while (true) {
|
|
3250
|
+
const { done, value } = await reader.read();
|
|
3251
|
+
if (done) break;
|
|
3252
|
+
if (value) chunks.push(value);
|
|
3253
|
+
}
|
|
3254
|
+
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
3255
|
+
const out = new Uint8Array(total);
|
|
3256
|
+
let offset = 0;
|
|
3257
|
+
for (const c of chunks) {
|
|
3258
|
+
out.set(c, offset);
|
|
3259
|
+
offset += c.length;
|
|
3260
|
+
}
|
|
3261
|
+
return out;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
// src/llm/cerebras.ts
|
|
3265
|
+
var LLM4 = class extends CerebrasLLMProvider {
|
|
3266
|
+
constructor(opts = {}) {
|
|
3267
|
+
const key = opts.apiKey ?? process.env.CEREBRAS_API_KEY;
|
|
3268
|
+
if (!key) {
|
|
3269
|
+
throw new Error(
|
|
3270
|
+
"Cerebras LLM requires an apiKey. Pass { apiKey: 'csk-...' } or set CEREBRAS_API_KEY."
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
super({
|
|
3274
|
+
apiKey: key,
|
|
3275
|
+
model: opts.model,
|
|
3276
|
+
baseUrl: opts.baseUrl,
|
|
3277
|
+
gzipCompression: opts.gzipCompression
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
};
|
|
3281
|
+
|
|
3282
|
+
// src/providers/google-llm.ts
|
|
3283
|
+
var DEFAULT_MODEL4 = "gemini-2.5-flash";
|
|
3284
|
+
var DEFAULT_BASE_URL3 = "https://generativelanguage.googleapis.com/v1beta";
|
|
3285
|
+
var GoogleLLMProvider = class {
|
|
3286
|
+
apiKey;
|
|
3287
|
+
model;
|
|
3288
|
+
baseUrl;
|
|
3289
|
+
temperature;
|
|
3290
|
+
maxOutputTokens;
|
|
3291
|
+
constructor(options) {
|
|
3292
|
+
if (!options.apiKey) {
|
|
3293
|
+
throw new Error(
|
|
3294
|
+
"Google API key is required. Pass it via { apiKey } or read GOOGLE_API_KEY from the environment."
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
this.apiKey = options.apiKey;
|
|
3298
|
+
this.model = options.model ?? DEFAULT_MODEL4;
|
|
3299
|
+
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL3;
|
|
3300
|
+
this.temperature = options.temperature;
|
|
3301
|
+
this.maxOutputTokens = options.maxOutputTokens;
|
|
3302
|
+
}
|
|
3303
|
+
async *stream(messages, tools) {
|
|
3304
|
+
const { systemInstruction, contents } = toGeminiContents(messages);
|
|
3305
|
+
const geminiTools = tools ? toGeminiTools(tools) : null;
|
|
3306
|
+
const body = { contents };
|
|
3307
|
+
if (systemInstruction) {
|
|
3308
|
+
body.systemInstruction = { role: "system", parts: [{ text: systemInstruction }] };
|
|
3309
|
+
}
|
|
3310
|
+
if (geminiTools) body.tools = geminiTools;
|
|
3311
|
+
const generationConfig = {};
|
|
3312
|
+
if (this.temperature !== void 0) generationConfig.temperature = this.temperature;
|
|
3313
|
+
if (this.maxOutputTokens !== void 0)
|
|
3314
|
+
generationConfig.maxOutputTokens = this.maxOutputTokens;
|
|
3315
|
+
if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
|
|
3316
|
+
const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:streamGenerateContent?alt=sse&key=${encodeURIComponent(this.apiKey)}`;
|
|
3317
|
+
const response = await fetch(url, {
|
|
3318
|
+
method: "POST",
|
|
3319
|
+
headers: { "Content-Type": "application/json" },
|
|
3320
|
+
body: JSON.stringify(body),
|
|
3321
|
+
signal: AbortSignal.timeout(3e4)
|
|
3322
|
+
});
|
|
3323
|
+
if (!response.ok) {
|
|
3324
|
+
const errText = await response.text();
|
|
3325
|
+
getLogger().error(`Gemini API error: ${response.status} ${errText}`);
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
const reader = response.body?.getReader();
|
|
3329
|
+
if (!reader) return;
|
|
3330
|
+
const decoder = new TextDecoder();
|
|
3331
|
+
let buffer = "";
|
|
3332
|
+
let nextIndex = 0;
|
|
3333
|
+
while (true) {
|
|
3334
|
+
const { done, value } = await reader.read();
|
|
3335
|
+
if (done) break;
|
|
3336
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3337
|
+
const lines = buffer.split("\n");
|
|
3338
|
+
buffer = lines.pop() || "";
|
|
3339
|
+
for (const line of lines) {
|
|
3340
|
+
const trimmed = line.trim();
|
|
3341
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
3342
|
+
const data = trimmed.slice(6);
|
|
3343
|
+
if (!data) continue;
|
|
3344
|
+
let payload;
|
|
3345
|
+
try {
|
|
3346
|
+
payload = JSON.parse(data);
|
|
3347
|
+
} catch {
|
|
3348
|
+
continue;
|
|
3349
|
+
}
|
|
3350
|
+
const candidate = payload.candidates?.[0];
|
|
3351
|
+
const parts = candidate?.content?.parts ?? [];
|
|
3352
|
+
for (const part of parts) {
|
|
3353
|
+
if (part.functionCall) {
|
|
3354
|
+
const args = part.functionCall.args ?? {};
|
|
3355
|
+
const callId = part.functionCall.id ?? `gemini_call_${nextIndex}`;
|
|
3356
|
+
yield {
|
|
3357
|
+
type: "tool_call",
|
|
3358
|
+
index: nextIndex,
|
|
3359
|
+
id: callId,
|
|
3360
|
+
name: part.functionCall.name ?? "",
|
|
3361
|
+
arguments: JSON.stringify(args)
|
|
3362
|
+
};
|
|
3363
|
+
nextIndex++;
|
|
3364
|
+
continue;
|
|
3365
|
+
}
|
|
3366
|
+
if (part.text) {
|
|
3367
|
+
yield { type: "text", content: part.text };
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
yield { type: "done" };
|
|
3373
|
+
}
|
|
3374
|
+
};
|
|
3375
|
+
function toGeminiTools(tools) {
|
|
3376
|
+
const functionDeclarations = tools.map((t) => {
|
|
3377
|
+
const fn = t.function ?? t;
|
|
3378
|
+
return {
|
|
3379
|
+
name: String(fn.name ?? ""),
|
|
3380
|
+
description: String(fn.description ?? ""),
|
|
3381
|
+
parameters: fn.parameters ?? { type: "object", properties: {} }
|
|
3382
|
+
};
|
|
3383
|
+
});
|
|
3384
|
+
if (functionDeclarations.length === 0) return [];
|
|
3385
|
+
return [{ functionDeclarations }];
|
|
3386
|
+
}
|
|
3387
|
+
function toGeminiContents(messages) {
|
|
3388
|
+
const systemParts = [];
|
|
3389
|
+
const contents = [];
|
|
3390
|
+
for (const rawMsg of messages) {
|
|
3391
|
+
const role = rawMsg.role;
|
|
3392
|
+
if (role === "system") {
|
|
3393
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
3394
|
+
systemParts.push(rawMsg.content);
|
|
3395
|
+
}
|
|
3396
|
+
continue;
|
|
3397
|
+
}
|
|
3398
|
+
if (role === "user") {
|
|
3399
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
3400
|
+
contents.push({ role: "user", parts: [{ text: rawMsg.content }] });
|
|
3401
|
+
}
|
|
3402
|
+
continue;
|
|
3403
|
+
}
|
|
3404
|
+
if (role === "assistant") {
|
|
3405
|
+
const parts = [];
|
|
3406
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
3407
|
+
parts.push({ text: rawMsg.content });
|
|
3408
|
+
}
|
|
3409
|
+
for (const tc of rawMsg.tool_calls ?? []) {
|
|
3410
|
+
let args = {};
|
|
3411
|
+
try {
|
|
3412
|
+
const parsed = JSON.parse(tc.function?.arguments ?? "{}");
|
|
3413
|
+
if (parsed && typeof parsed === "object") args = parsed;
|
|
3414
|
+
} catch {
|
|
3415
|
+
args = {};
|
|
3416
|
+
}
|
|
3417
|
+
parts.push({
|
|
3418
|
+
functionCall: {
|
|
3419
|
+
name: tc.function?.name ?? "",
|
|
3420
|
+
args,
|
|
3421
|
+
id: tc.id
|
|
3422
|
+
}
|
|
3423
|
+
});
|
|
3424
|
+
}
|
|
3425
|
+
if (parts.length > 0) contents.push({ role: "model", parts });
|
|
3426
|
+
continue;
|
|
3427
|
+
}
|
|
3428
|
+
if (role === "tool") {
|
|
3429
|
+
const raw = rawMsg.content;
|
|
3430
|
+
let response;
|
|
3431
|
+
if (typeof raw === "string") {
|
|
3432
|
+
try {
|
|
3433
|
+
const parsed = JSON.parse(raw);
|
|
3434
|
+
response = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: parsed };
|
|
3435
|
+
} catch {
|
|
3436
|
+
response = { result: raw };
|
|
3437
|
+
}
|
|
3438
|
+
} else {
|
|
3439
|
+
response = raw ?? {};
|
|
3440
|
+
}
|
|
3441
|
+
contents.push({
|
|
3442
|
+
role: "user",
|
|
3443
|
+
parts: [
|
|
3444
|
+
{
|
|
3445
|
+
functionResponse: {
|
|
3446
|
+
name: rawMsg.name ?? rawMsg.tool_call_id ?? "",
|
|
3447
|
+
response,
|
|
3448
|
+
id: rawMsg.tool_call_id
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
]
|
|
3452
|
+
});
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
return { systemInstruction: systemParts.join("\n\n"), contents };
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
// src/llm/google.ts
|
|
3460
|
+
var LLM5 = class extends GoogleLLMProvider {
|
|
3461
|
+
constructor(opts = {}) {
|
|
3462
|
+
const key = opts.apiKey ?? process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
|
|
3463
|
+
if (!key) {
|
|
3464
|
+
throw new Error(
|
|
3465
|
+
"Google LLM requires an apiKey. Pass { apiKey: 'AIza...' } or set GEMINI_API_KEY (or GOOGLE_API_KEY)."
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
super({
|
|
3469
|
+
apiKey: key,
|
|
3470
|
+
model: opts.model,
|
|
3471
|
+
baseUrl: opts.baseUrl,
|
|
3472
|
+
temperature: opts.temperature,
|
|
3473
|
+
maxOutputTokens: opts.maxOutputTokens
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
};
|
|
3477
|
+
|
|
2853
3478
|
// src/carriers/twilio.ts
|
|
2854
3479
|
var Carrier = class {
|
|
2855
3480
|
kind = "twilio";
|
|
@@ -3558,6 +4183,7 @@ function isAudioConfig(value) {
|
|
|
3558
4183
|
}
|
|
3559
4184
|
export {
|
|
3560
4185
|
AllProvidersFailedError,
|
|
4186
|
+
LLM2 as AnthropicLLM,
|
|
3561
4187
|
STT5 as AssemblyAISTT,
|
|
3562
4188
|
AuthenticationError,
|
|
3563
4189
|
BackgroundAudioPlayer,
|
|
@@ -3565,6 +4191,7 @@ export {
|
|
|
3565
4191
|
CallMetricsAccumulator,
|
|
3566
4192
|
STT3 as CartesiaSTT,
|
|
3567
4193
|
TTS3 as CartesiaTTS,
|
|
4194
|
+
LLM4 as CerebrasLLM,
|
|
3568
4195
|
ChatContext,
|
|
3569
4196
|
CloudflareTunnel,
|
|
3570
4197
|
DEFAULT_MIN_SENTENCE_LEN,
|
|
@@ -3578,11 +4205,14 @@ export {
|
|
|
3578
4205
|
GEMINI_DEFAULT_INPUT_SR,
|
|
3579
4206
|
GEMINI_DEFAULT_OUTPUT_SR,
|
|
3580
4207
|
GeminiLiveAdapter,
|
|
4208
|
+
LLM5 as GoogleLLM,
|
|
4209
|
+
LLM3 as GroqLLM,
|
|
3581
4210
|
Guardrail,
|
|
3582
4211
|
IVRActivity,
|
|
3583
4212
|
LLMLoop,
|
|
3584
4213
|
TTS5 as LMNTTTS,
|
|
3585
4214
|
MetricsStore,
|
|
4215
|
+
LLM as OpenAILLM,
|
|
3586
4216
|
OpenAILLMProvider,
|
|
3587
4217
|
Realtime as OpenAIRealtime,
|
|
3588
4218
|
OpenAIRealtimeAdapter,
|