@p-sw/brainbox 0.1.0 → 0.1.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/dist/index.js +5312 -0
- package/package.json +7 -4
- package/scripts/smoke_providers.ts +0 -176
- package/src/brain/index.ts +0 -936
- package/src/brain/manager.ts +0 -144
- package/src/brain/memory.ts +0 -99
- package/src/brain/messageHistory.ts +0 -26
- package/src/brain/schedule.ts +0 -11
- package/src/brain/types.ts +0 -26
- package/src/channel/base.ts +0 -527
- package/src/channel/discord.ts +0 -227
- package/src/channel/telegram.ts +0 -150
- package/src/commands/auth.tsx +0 -306
- package/src/commands/brain.ts +0 -119
- package/src/commands/daemon/commands.ts +0 -56
- package/src/commands/daemon/pairingCommand.ts +0 -15
- package/src/commands/daemon/restartCommand.ts +0 -17
- package/src/commands/daemon.ts +0 -168
- package/src/commands/index.ts +0 -16
- package/src/commands/model.tsx +0 -138
- package/src/commands/onboard.tsx +0 -473
- package/src/commands/pairing.ts +0 -32
- package/src/commands/restart.ts +0 -24
- package/src/config/file/auth.ts +0 -105
- package/src/config/file/root.ts +0 -37
- package/src/config/index.ts +0 -21
- package/src/config/loader.ts +0 -115
- package/src/index.ts +0 -61
- package/src/provider/index.ts +0 -122
- package/src/provider/llm.ts +0 -191
- package/src/provider/promptLoader.ts +0 -36
- package/src/provider/providers/302ai.ts +0 -17
- package/src/provider/providers/MiniMax.ts +0 -17
- package/src/provider/providers/anthropic.ts +0 -257
- package/src/provider/providers/azure_cognitive.ts +0 -40
- package/src/provider/providers/azure_openai.ts +0 -49
- package/src/provider/providers/baseten.ts +0 -17
- package/src/provider/providers/bedrock.ts +0 -312
- package/src/provider/providers/cerebras.ts +0 -17
- package/src/provider/providers/cloudflare_gateway.ts +0 -34
- package/src/provider/providers/cloudflare_workers.ts +0 -178
- package/src/provider/providers/copilot.ts +0 -22
- package/src/provider/providers/cortecs.ts +0 -17
- package/src/provider/providers/deepinfra.ts +0 -17
- package/src/provider/providers/deepseek.ts +0 -17
- package/src/provider/providers/digitalocean.ts +0 -17
- package/src/provider/providers/fireworks.ts +0 -17
- package/src/provider/providers/gitlab_duo.ts +0 -184
- package/src/provider/providers/gmi.ts +0 -17
- package/src/provider/providers/groq.ts +0 -17
- package/src/provider/providers/helicone.ts +0 -17
- package/src/provider/providers/huggingface.ts +0 -17
- package/src/provider/providers/ionet.ts +0 -17
- package/src/provider/providers/llamacpp.ts +0 -17
- package/src/provider/providers/llmgateway.ts +0 -17
- package/src/provider/providers/lmstudio.ts +0 -17
- package/src/provider/providers/mistral.ts +0 -17
- package/src/provider/providers/moonshot.ts +0 -17
- package/src/provider/providers/nebius.ts +0 -17
- package/src/provider/providers/nvidia.ts +0 -17
- package/src/provider/providers/ollama.ts +0 -17
- package/src/provider/providers/ollama_cloud.ts +0 -17
- package/src/provider/providers/openai.ts +0 -17
- package/src/provider/providers/openai_compatible.ts +0 -293
- package/src/provider/providers/openrouter.ts +0 -175
- package/src/provider/providers/ovhcloud.ts +0 -17
- package/src/provider/providers/sap_aicore.ts +0 -22
- package/src/provider/providers/scaleway.ts +0 -17
- package/src/provider/providers/snowflake_cortex.ts +0 -207
- package/src/provider/providers/stackit.ts +0 -17
- package/src/provider/providers/together.ts +0 -17
- package/src/provider/providers/venice.ts +0 -17
- package/src/provider/providers/vercel.ts +0 -17
- package/src/provider/providers/vertex.ts +0 -229
- package/src/provider/providers/xai.ts +0 -17
- package/src/provider/providers/zai.ts +0 -17
- package/src/provider/providers/zenmux.ts +0 -17
- package/src/provider/schema.ts +0 -143
- package/src/ui/TextInput.tsx +0 -114
- package/src/utils/daemonClient.ts +0 -85
- package/src/utils/logger.ts +0 -204
- package/tsconfig.json +0 -34
- /package/{prompts → dist/prompts}/daily_schedule.md +0 -0
- /package/{prompts → dist/prompts}/memoir.md +0 -0
- /package/{prompts → dist/prompts}/monthly_schedule.md +0 -0
- /package/{prompts → dist/prompts}/objectifier.md +0 -0
- /package/{prompts → dist/prompts}/persona_base_system_prompt.md +0 -0
- /package/{prompts → dist/prompts}/persona_base_system_prompt_fixed.md +0 -0
- /package/{prompts → dist/prompts}/persona_init.md +0 -0
- /package/{prompts → dist/prompts}/schedule_availability.md +0 -0
- /package/{prompts → dist/prompts}/send_message.md +0 -0
- /package/{prompts → dist/prompts}/start_conversation.md +0 -0
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class MoonshotExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "moonshot",
|
|
11
|
-
baseURL: "https://api.moonshot.ai/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class NebiusExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "nebius",
|
|
11
|
-
baseURL: "https://api.tokenfactory.nebius.com/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class NvidiaExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "nvidia",
|
|
11
|
-
baseURL: "https://integrate.api.nvidia.com/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class OllamaExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "ollama",
|
|
11
|
-
baseURL: "http://127.0.0.1:11434/v1",
|
|
12
|
-
apiKey: opts.apiKey || "ollama",
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class OllamaCloudExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "ollama-cloud",
|
|
11
|
-
baseURL: "https://api.ollama.cloud/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class OpenAIExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "openai",
|
|
11
|
-
baseURL: "https://api.openai.com/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import { logger } from "@/utils/logger";
|
|
2
|
-
import {
|
|
3
|
-
LLMExecutor,
|
|
4
|
-
defaultReasoningEffort,
|
|
5
|
-
type CallOptions,
|
|
6
|
-
type ChatChoice,
|
|
7
|
-
type ChatFunctionTool,
|
|
8
|
-
type ChatMessages,
|
|
9
|
-
type ChatWithToolsOptions,
|
|
10
|
-
type ReasoningEffort,
|
|
11
|
-
type ToolCall,
|
|
12
|
-
} from "../llm";
|
|
13
|
-
|
|
14
|
-
const log = logger.child("llm:openai-compatible");
|
|
15
|
-
|
|
16
|
-
// ponytail: Most LLM providers expose an OpenAI-compatible /v1/chat/completions endpoint.
|
|
17
|
-
// One executor, parameterized by baseURL + auth headers, covers the majority of the list.
|
|
18
|
-
// Per-provider tweaks live in subclass overrides; default behavior needs none.
|
|
19
|
-
|
|
20
|
-
export type OpenAICompatibleOptions = {
|
|
21
|
-
baseURL: string;
|
|
22
|
-
apiKey: string;
|
|
23
|
-
conversationModel: string;
|
|
24
|
-
identityModel: string;
|
|
25
|
-
defaultHeaders?: Record<string, string>;
|
|
26
|
-
chatPath?: string;
|
|
27
|
-
noBearerPrefix?: boolean;
|
|
28
|
-
reasoningEffortInQuery?: boolean;
|
|
29
|
-
providerName: string;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
type ChatMessageWire =
|
|
33
|
-
| { role: "system" | "user"; content: string }
|
|
34
|
-
| {
|
|
35
|
-
role: "assistant";
|
|
36
|
-
content: string | null;
|
|
37
|
-
tool_calls?: Array<{
|
|
38
|
-
id: string;
|
|
39
|
-
type: "function";
|
|
40
|
-
function: { name: string; arguments: string };
|
|
41
|
-
}>;
|
|
42
|
-
}
|
|
43
|
-
| { role: "tool"; content: string; tool_call_id: string };
|
|
44
|
-
|
|
45
|
-
type ChatResponse = {
|
|
46
|
-
choices?: Array<{
|
|
47
|
-
message?: {
|
|
48
|
-
content?: string | null;
|
|
49
|
-
tool_calls?: Array<{
|
|
50
|
-
id: string;
|
|
51
|
-
type: "function";
|
|
52
|
-
function: { name: string; arguments: string };
|
|
53
|
-
}>;
|
|
54
|
-
};
|
|
55
|
-
}>;
|
|
56
|
-
error?: { message?: string; type?: string };
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
function toWireMessage(m: ChatMessages): ChatMessageWire {
|
|
60
|
-
if (m.role === "assistant") {
|
|
61
|
-
return {
|
|
62
|
-
role: "assistant",
|
|
63
|
-
content: m.content ?? null,
|
|
64
|
-
tool_calls: m.toolCalls?.map((c) => ({
|
|
65
|
-
id: c.id,
|
|
66
|
-
type: "function",
|
|
67
|
-
function: { name: c.function.name, arguments: c.function.arguments },
|
|
68
|
-
})),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
if (m.role === "tool") {
|
|
72
|
-
return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
|
|
73
|
-
}
|
|
74
|
-
return { role: m.role, content: m.content };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function fromChoice(
|
|
78
|
-
choice: NonNullable<ChatResponse["choices"]>[number],
|
|
79
|
-
): ChatChoice {
|
|
80
|
-
const msg = choice.message ?? {};
|
|
81
|
-
const toolCalls: ToolCall[] | undefined = msg.tool_calls?.map((c) => ({
|
|
82
|
-
id: c.id,
|
|
83
|
-
function: { name: c.function.name, arguments: c.function.arguments },
|
|
84
|
-
}));
|
|
85
|
-
return {
|
|
86
|
-
message: {
|
|
87
|
-
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
88
|
-
toolCalls,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function toWireTool(t: ChatFunctionTool) {
|
|
94
|
-
return {
|
|
95
|
-
type: "function" as const,
|
|
96
|
-
function: {
|
|
97
|
-
name: t.name,
|
|
98
|
-
description: t.description ?? "",
|
|
99
|
-
parameters: t.parameters ?? {},
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function buildResponseFormat(
|
|
105
|
-
jsonSchemaName: string,
|
|
106
|
-
jsonSchema: Record<string, unknown> | undefined,
|
|
107
|
-
): Record<string, unknown> {
|
|
108
|
-
return {
|
|
109
|
-
type: "json_schema",
|
|
110
|
-
json_schema: {
|
|
111
|
-
name: jsonSchemaName,
|
|
112
|
-
schema: jsonSchema ?? {},
|
|
113
|
-
strict: true,
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export class OpenAICompatibleExecutor extends LLMExecutor {
|
|
119
|
-
readonly providerName: string;
|
|
120
|
-
readonly models: { conversation: string; identity: string };
|
|
121
|
-
|
|
122
|
-
protected readonly baseURL: string;
|
|
123
|
-
protected readonly apiKey: string;
|
|
124
|
-
protected readonly defaultHeaders: Record<string, string>;
|
|
125
|
-
protected readonly chatPath: string;
|
|
126
|
-
protected readonly noBearerPrefix: boolean;
|
|
127
|
-
protected readonly reasoningEffortInQuery: boolean;
|
|
128
|
-
|
|
129
|
-
constructor(opts: OpenAICompatibleOptions) {
|
|
130
|
-
super();
|
|
131
|
-
this.providerName = opts.providerName;
|
|
132
|
-
this.baseURL = opts.baseURL.replace(/\/+$/, "");
|
|
133
|
-
this.apiKey = opts.apiKey;
|
|
134
|
-
this.defaultHeaders = opts.defaultHeaders ?? {};
|
|
135
|
-
this.chatPath = opts.chatPath ?? "/chat/completions";
|
|
136
|
-
this.noBearerPrefix = opts.noBearerPrefix ?? false;
|
|
137
|
-
this.reasoningEffortInQuery = opts.reasoningEffortInQuery ?? false;
|
|
138
|
-
this.models = {
|
|
139
|
-
conversation: opts.conversationModel,
|
|
140
|
-
identity: opts.identityModel,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
protected buildBody(opts: {
|
|
145
|
-
model: string;
|
|
146
|
-
messages: ChatMessageWire[];
|
|
147
|
-
responseFormat?: Record<string, unknown>;
|
|
148
|
-
tools?: unknown[];
|
|
149
|
-
parallelToolCalls?: boolean;
|
|
150
|
-
reasoningEffort?: ReasoningEffort;
|
|
151
|
-
}): Record<string, unknown> {
|
|
152
|
-
const body: Record<string, unknown> = {
|
|
153
|
-
model: opts.model,
|
|
154
|
-
messages: opts.messages,
|
|
155
|
-
stream: false,
|
|
156
|
-
};
|
|
157
|
-
if (opts.responseFormat) body["response_format"] = opts.responseFormat;
|
|
158
|
-
if (opts.tools) body["tools"] = opts.tools;
|
|
159
|
-
if (opts.parallelToolCalls !== undefined) {
|
|
160
|
-
body["parallel_tool_calls"] = opts.parallelToolCalls;
|
|
161
|
-
}
|
|
162
|
-
if (opts.reasoningEffort && opts.reasoningEffort !== "none") {
|
|
163
|
-
body["reasoning_effort"] = opts.reasoningEffort;
|
|
164
|
-
}
|
|
165
|
-
return body;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
protected buildRequestUrl(
|
|
169
|
-
_model: string,
|
|
170
|
-
reasoningEffort: ReasoningEffort | undefined,
|
|
171
|
-
): string {
|
|
172
|
-
const url = new URL(this.baseURL + this.chatPath);
|
|
173
|
-
if (
|
|
174
|
-
this.reasoningEffortInQuery &&
|
|
175
|
-
reasoningEffort &&
|
|
176
|
-
reasoningEffort !== "none"
|
|
177
|
-
) {
|
|
178
|
-
url.searchParams.set("reasoning_effort", reasoningEffort);
|
|
179
|
-
}
|
|
180
|
-
return url.toString();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private async sendRequest(
|
|
184
|
-
body: Record<string, unknown>,
|
|
185
|
-
reasoningEffort: ReasoningEffort | undefined,
|
|
186
|
-
): Promise<ChatResponse> {
|
|
187
|
-
const modelName = body["model"];
|
|
188
|
-
const modelStr = typeof modelName === "string" ? modelName : "";
|
|
189
|
-
const url = this.buildRequestUrl(modelStr, reasoningEffort);
|
|
190
|
-
const authHeader: Record<string, string> = this.apiKey
|
|
191
|
-
? this.noBearerPrefix
|
|
192
|
-
? { Authorization: this.apiKey }
|
|
193
|
-
: { Authorization: `Bearer ${this.apiKey}` }
|
|
194
|
-
: {};
|
|
195
|
-
const headers: Record<string, string> = {
|
|
196
|
-
"Content-Type": "application/json",
|
|
197
|
-
...authHeader,
|
|
198
|
-
...this.defaultHeaders,
|
|
199
|
-
};
|
|
200
|
-
const res = await fetch(url, {
|
|
201
|
-
method: "POST",
|
|
202
|
-
headers,
|
|
203
|
-
body: JSON.stringify(body),
|
|
204
|
-
});
|
|
205
|
-
if (!res.ok) {
|
|
206
|
-
const text = await res.text().catch(() => "");
|
|
207
|
-
log.error(
|
|
208
|
-
`${this.providerName}: HTTP ${res.status} ${res.statusText} body=${text.slice(0, 500)}`,
|
|
209
|
-
);
|
|
210
|
-
throw new Error(
|
|
211
|
-
`${this.providerName} request failed: ${res.status} ${res.statusText}`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
const data = (await res.json()) as ChatResponse;
|
|
215
|
-
if (data.error) {
|
|
216
|
-
log.error(
|
|
217
|
-
`${this.providerName}: API error ${data.error.type ?? ""} ${data.error.message ?? ""}`,
|
|
218
|
-
);
|
|
219
|
-
throw new Error(
|
|
220
|
-
`${this.providerName} API error: ${data.error.message ?? "unknown"}`,
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
return data;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async call<T>(model: string, options: CallOptions): Promise<T> {
|
|
227
|
-
const jsonMode = "jsonSchemaName" in options;
|
|
228
|
-
const reasoning = defaultReasoningEffort(
|
|
229
|
-
options.reasoningEffort,
|
|
230
|
-
model,
|
|
231
|
-
this.models.identity,
|
|
232
|
-
);
|
|
233
|
-
log.debug(
|
|
234
|
-
`call: provider=${this.providerName} model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`,
|
|
235
|
-
);
|
|
236
|
-
const body = this.buildBody({
|
|
237
|
-
model,
|
|
238
|
-
messages: [
|
|
239
|
-
{ role: "system", content: options.instruction },
|
|
240
|
-
{ role: "user", content: options.message },
|
|
241
|
-
],
|
|
242
|
-
responseFormat: jsonMode
|
|
243
|
-
? buildResponseFormat(options.jsonSchemaName, options.jsonSchema)
|
|
244
|
-
: undefined,
|
|
245
|
-
reasoningEffort: reasoning,
|
|
246
|
-
});
|
|
247
|
-
const data = await this.sendRequest(body, options.reasoningEffort);
|
|
248
|
-
const content = data.choices?.[0]?.message?.content;
|
|
249
|
-
if (!content) {
|
|
250
|
-
log.debug(`call: empty content in choice 0`);
|
|
251
|
-
throw new Error("Empty response from model");
|
|
252
|
-
}
|
|
253
|
-
log.debug(`call: response ${content.length} chars`);
|
|
254
|
-
return (jsonMode ? JSON.parse(content) : content) as T;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async chatWithTools(
|
|
258
|
-
model: string,
|
|
259
|
-
options: ChatWithToolsOptions,
|
|
260
|
-
): Promise<ChatChoice> {
|
|
261
|
-
const reasoning = defaultReasoningEffort(
|
|
262
|
-
options.reasoningEffort,
|
|
263
|
-
model,
|
|
264
|
-
this.models.identity,
|
|
265
|
-
);
|
|
266
|
-
log.debug(
|
|
267
|
-
`chatWithTools: provider=${this.providerName} model=${model} msgs=${options.messages.length} tools=${options.tools.length}`,
|
|
268
|
-
);
|
|
269
|
-
const body = this.buildBody({
|
|
270
|
-
model,
|
|
271
|
-
messages: [
|
|
272
|
-
{ role: "system", content: options.instruction },
|
|
273
|
-
...options.messages.map(toWireMessage),
|
|
274
|
-
],
|
|
275
|
-
tools: options.tools.map(toWireTool),
|
|
276
|
-
parallelToolCalls: options.parallelToolCalls ?? false,
|
|
277
|
-
reasoningEffort: reasoning,
|
|
278
|
-
});
|
|
279
|
-
const data = await this.sendRequest(body, options.reasoningEffort);
|
|
280
|
-
const choice = data.choices?.[0];
|
|
281
|
-
if (!choice) {
|
|
282
|
-
log.debug(`chatWithTools: no choice in response`);
|
|
283
|
-
throw new Error("LLM returned no choice");
|
|
284
|
-
}
|
|
285
|
-
const calls = choice.message?.tool_calls?.length ?? 0;
|
|
286
|
-
const textLen =
|
|
287
|
-
typeof choice.message?.content === "string"
|
|
288
|
-
? choice.message.content.length
|
|
289
|
-
: 0;
|
|
290
|
-
log.debug(`chatWithTools: choice toolCalls=${calls} text=${textLen}`);
|
|
291
|
-
return fromChoice(choice);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { OpenRouter } from "@openrouter/sdk";
|
|
2
|
-
import { logger } from "@/utils/logger";
|
|
3
|
-
import type {
|
|
4
|
-
ChatChoice as OrChoice,
|
|
5
|
-
ChatFunctionTool as OrTool,
|
|
6
|
-
ChatMessages as OrMessage,
|
|
7
|
-
ChatRequestEffort,
|
|
8
|
-
} from "@openrouter/sdk/models";
|
|
9
|
-
import {
|
|
10
|
-
LLMExecutor,
|
|
11
|
-
type CallOptions,
|
|
12
|
-
type ChatChoice,
|
|
13
|
-
type ChatFunctionTool,
|
|
14
|
-
type ChatMessages,
|
|
15
|
-
type ChatWithToolsOptions,
|
|
16
|
-
type ReasoningEffort,
|
|
17
|
-
type ToolCall,
|
|
18
|
-
} from "../llm";
|
|
19
|
-
|
|
20
|
-
const log = logger.child("llm:openrouter");
|
|
21
|
-
|
|
22
|
-
const REASONING_EFFORT_MAP: Record<ReasoningEffort, ChatRequestEffort> = {
|
|
23
|
-
none: "none",
|
|
24
|
-
low: "low",
|
|
25
|
-
medium: "medium",
|
|
26
|
-
high: "high",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function toOrMessage(m: ChatMessages): OrMessage {
|
|
30
|
-
if (m.role === "assistant") {
|
|
31
|
-
return {
|
|
32
|
-
role: "assistant",
|
|
33
|
-
content: m.content ?? "",
|
|
34
|
-
toolCalls: m.toolCalls?.map((c) => ({
|
|
35
|
-
id: c.id,
|
|
36
|
-
type: "function" as const,
|
|
37
|
-
function: { name: c.function.name, arguments: c.function.arguments },
|
|
38
|
-
})),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
if (m.role === "tool") {
|
|
42
|
-
return { role: "tool", content: m.content, toolCallId: m.toolCallId };
|
|
43
|
-
}
|
|
44
|
-
return { role: m.role, content: m.content };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function fromOrChoice(choice: OrChoice): ChatChoice {
|
|
48
|
-
const msg = choice.message;
|
|
49
|
-
const toolCalls: ToolCall[] | undefined = msg.toolCalls?.map((c) => ({
|
|
50
|
-
id: c.id,
|
|
51
|
-
function: { name: c.function.name, arguments: c.function.arguments },
|
|
52
|
-
}));
|
|
53
|
-
return {
|
|
54
|
-
message: {
|
|
55
|
-
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
56
|
-
toolCalls,
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function toOrTool(t: ChatFunctionTool): OrTool {
|
|
62
|
-
return {
|
|
63
|
-
type: "function",
|
|
64
|
-
function: {
|
|
65
|
-
name: t.name,
|
|
66
|
-
description: t.description ?? "",
|
|
67
|
-
parameters: t.parameters ?? {},
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export class OpenRouterExecutor extends LLMExecutor {
|
|
73
|
-
readonly providerName = "openrouter";
|
|
74
|
-
readonly models: { conversation: string; identity: string };
|
|
75
|
-
|
|
76
|
-
private client: OpenRouter;
|
|
77
|
-
|
|
78
|
-
constructor(opts: {
|
|
79
|
-
apiKey: string;
|
|
80
|
-
conversationModel: string;
|
|
81
|
-
identityModel: string;
|
|
82
|
-
}) {
|
|
83
|
-
super();
|
|
84
|
-
this.client = new OpenRouter({ apiKey: opts.apiKey, appTitle: "boxbrain" });
|
|
85
|
-
this.models = {
|
|
86
|
-
conversation: opts.conversationModel,
|
|
87
|
-
identity: opts.identityModel,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async call<T>(model: string, options: CallOptions): Promise<T> {
|
|
92
|
-
const jsonMode = "jsonSchemaName" in options;
|
|
93
|
-
log.debug(
|
|
94
|
-
`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`,
|
|
95
|
-
);
|
|
96
|
-
const result = await this.client.chat.send({
|
|
97
|
-
chatRequest: {
|
|
98
|
-
model,
|
|
99
|
-
messages: [
|
|
100
|
-
{ role: "system", content: options.instruction },
|
|
101
|
-
{ role: "user", content: options.message },
|
|
102
|
-
],
|
|
103
|
-
reasoning: {
|
|
104
|
-
effort:
|
|
105
|
-
options.reasoningEffort ??
|
|
106
|
-
(model === this.models.identity
|
|
107
|
-
? REASONING_EFFORT_MAP.medium
|
|
108
|
-
: REASONING_EFFORT_MAP.none),
|
|
109
|
-
},
|
|
110
|
-
responseFormat: jsonMode
|
|
111
|
-
? {
|
|
112
|
-
type: "json_schema",
|
|
113
|
-
jsonSchema: {
|
|
114
|
-
name: options.jsonSchemaName,
|
|
115
|
-
schema: options.jsonSchema,
|
|
116
|
-
strict: true,
|
|
117
|
-
},
|
|
118
|
-
}
|
|
119
|
-
: { type: "text" },
|
|
120
|
-
stream: false,
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const content = result.choices[0]?.message?.content;
|
|
125
|
-
if (!content) {
|
|
126
|
-
log.debug(`call: empty content in choice 0`);
|
|
127
|
-
throw new Error("Empty response from model");
|
|
128
|
-
}
|
|
129
|
-
log.debug(`call: response ${content.length} chars`);
|
|
130
|
-
|
|
131
|
-
return (jsonMode ? JSON.parse(content) : content) as T;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async chatWithTools(
|
|
135
|
-
model: string,
|
|
136
|
-
options: ChatWithToolsOptions,
|
|
137
|
-
): Promise<ChatChoice> {
|
|
138
|
-
log.debug(
|
|
139
|
-
`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`,
|
|
140
|
-
);
|
|
141
|
-
const result = await this.client.chat.send({
|
|
142
|
-
chatRequest: {
|
|
143
|
-
model,
|
|
144
|
-
messages: [
|
|
145
|
-
{ role: "system", content: options.instruction },
|
|
146
|
-
...options.messages.map(toOrMessage),
|
|
147
|
-
],
|
|
148
|
-
reasoning: {
|
|
149
|
-
effort:
|
|
150
|
-
options.reasoningEffort ??
|
|
151
|
-
(model === this.models.identity
|
|
152
|
-
? REASONING_EFFORT_MAP.medium
|
|
153
|
-
: REASONING_EFFORT_MAP.none),
|
|
154
|
-
},
|
|
155
|
-
responseFormat: { type: "text" },
|
|
156
|
-
tools: options.tools.map(toOrTool),
|
|
157
|
-
parallelToolCalls: options.parallelToolCalls ?? false,
|
|
158
|
-
stream: false,
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const choice = result.choices[0];
|
|
163
|
-
if (!choice) {
|
|
164
|
-
log.debug(`chatWithTools: no choice in response`);
|
|
165
|
-
throw new Error("LLM returned no choice");
|
|
166
|
-
}
|
|
167
|
-
const calls = choice.message.toolCalls?.length ?? 0;
|
|
168
|
-
const textLen =
|
|
169
|
-
typeof choice.message.content === "string"
|
|
170
|
-
? choice.message.content.length
|
|
171
|
-
: 0;
|
|
172
|
-
log.debug(`chatWithTools: choice toolCalls=${calls} text=${textLen}`);
|
|
173
|
-
return fromOrChoice(choice);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class OvhCloudExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "ovhcloud",
|
|
11
|
-
baseURL: "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
import { readAuthString } from "../llm";
|
|
3
|
-
|
|
4
|
-
export class SapAiCoreExecutor extends OpenAICompatibleExecutor {
|
|
5
|
-
constructor(opts: {
|
|
6
|
-
apiKey: string;
|
|
7
|
-
conversationModel: string;
|
|
8
|
-
identityModel: string;
|
|
9
|
-
auth?: Record<string, unknown>;
|
|
10
|
-
}) {
|
|
11
|
-
const baseURL =
|
|
12
|
-
readAuthString(opts.auth, "baseURL", "SAP_AI_CORE_BASE_URL") ||
|
|
13
|
-
"https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com/v2";
|
|
14
|
-
super({
|
|
15
|
-
providerName: "sap-aicore",
|
|
16
|
-
baseURL,
|
|
17
|
-
apiKey: opts.apiKey,
|
|
18
|
-
conversationModel: opts.conversationModel,
|
|
19
|
-
identityModel: opts.identityModel,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class ScalewayExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "scaleway",
|
|
11
|
-
baseURL: "https://api.scaleway.ai/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|