@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,257 +0,0 @@
|
|
|
1
|
-
import { logger } from "@/utils/logger";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import {
|
|
4
|
-
LLMExecutor,
|
|
5
|
-
defaultReasoningEffort,
|
|
6
|
-
readAuthString,
|
|
7
|
-
type CallOptions,
|
|
8
|
-
type ChatChoice,
|
|
9
|
-
type ChatFunctionTool,
|
|
10
|
-
type ChatMessages,
|
|
11
|
-
type ChatWithToolsOptions,
|
|
12
|
-
type ReasoningEffort,
|
|
13
|
-
type ToolCall,
|
|
14
|
-
} from "../llm";
|
|
15
|
-
|
|
16
|
-
type AnthropicTool = {
|
|
17
|
-
name: string;
|
|
18
|
-
description?: string;
|
|
19
|
-
input_schema: Record<string, unknown>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type AnthropicContentBlock =
|
|
23
|
-
| { type: "text"; text: string }
|
|
24
|
-
| { type: "tool_use"; id: string; name: string; input: unknown };
|
|
25
|
-
|
|
26
|
-
type AnthropicResponse = {
|
|
27
|
-
content?: AnthropicContentBlock[];
|
|
28
|
-
stop_reason?: string;
|
|
29
|
-
error?: { message?: string; type?: string };
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const REASONING_BUDGET: Record<Exclude<ReasoningEffort, "none">, number> = {
|
|
33
|
-
low: 1024,
|
|
34
|
-
medium: 4096,
|
|
35
|
-
high: 16384,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const AnthropicAuthSchema = z
|
|
39
|
-
.object({
|
|
40
|
-
baseURL: z.string().optional(),
|
|
41
|
-
apiVersion: z.string().optional(),
|
|
42
|
-
})
|
|
43
|
-
.loose();
|
|
44
|
-
|
|
45
|
-
function toAnthropicMessages(messages: ChatMessages[]): {
|
|
46
|
-
system?: string;
|
|
47
|
-
msgs: Array<Record<string, unknown>>;
|
|
48
|
-
} {
|
|
49
|
-
let system: string | undefined;
|
|
50
|
-
const msgs: Array<Record<string, unknown>> = [];
|
|
51
|
-
for (const m of messages) {
|
|
52
|
-
if (m.role === "system") {
|
|
53
|
-
system = (system ? system + "\n\n" : "") + m.content;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (m.role === "user") {
|
|
57
|
-
msgs.push({ role: "user", content: m.content });
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (m.role === "assistant") {
|
|
61
|
-
const blocks: AnthropicContentBlock[] = [];
|
|
62
|
-
if (m.content) blocks.push({ type: "text", text: m.content });
|
|
63
|
-
if (m.toolCalls) {
|
|
64
|
-
for (const c of m.toolCalls) {
|
|
65
|
-
let input: unknown = {};
|
|
66
|
-
try {
|
|
67
|
-
input = c.function.arguments
|
|
68
|
-
? JSON.parse(c.function.arguments)
|
|
69
|
-
: {};
|
|
70
|
-
} catch {
|
|
71
|
-
input = {};
|
|
72
|
-
}
|
|
73
|
-
blocks.push({
|
|
74
|
-
type: "tool_use",
|
|
75
|
-
id: c.id,
|
|
76
|
-
name: c.function.name,
|
|
77
|
-
input,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
msgs.push({ role: "assistant", content: blocks });
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (m.role === "tool") {
|
|
85
|
-
msgs.push({
|
|
86
|
-
role: "user",
|
|
87
|
-
content: [
|
|
88
|
-
{
|
|
89
|
-
type: "tool_result",
|
|
90
|
-
tool_use_id: m.toolCallId,
|
|
91
|
-
content: m.content,
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return { system, msgs };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function toAnthropicTool(t: ChatFunctionTool): AnthropicTool {
|
|
101
|
-
return {
|
|
102
|
-
name: t.name,
|
|
103
|
-
description: t.description,
|
|
104
|
-
input_schema: t.parameters ?? { type: "object", properties: {} },
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export class AnthropicExecutor extends LLMExecutor {
|
|
109
|
-
readonly providerName = "anthropic";
|
|
110
|
-
readonly models: { conversation: string; identity: string };
|
|
111
|
-
|
|
112
|
-
private readonly apiKey: string;
|
|
113
|
-
private readonly baseURL: string;
|
|
114
|
-
private readonly apiVersion: string;
|
|
115
|
-
|
|
116
|
-
constructor(opts: {
|
|
117
|
-
apiKey: string;
|
|
118
|
-
conversationModel: string;
|
|
119
|
-
identityModel: string;
|
|
120
|
-
auth?: Record<string, unknown>;
|
|
121
|
-
}) {
|
|
122
|
-
super();
|
|
123
|
-
this.models = {
|
|
124
|
-
conversation: opts.conversationModel,
|
|
125
|
-
identity: opts.identityModel,
|
|
126
|
-
};
|
|
127
|
-
this.apiKey = opts.apiKey;
|
|
128
|
-
const parsed = AnthropicAuthSchema.safeParse(opts.auth ?? {});
|
|
129
|
-
const extra = parsed.success ? parsed.data : {};
|
|
130
|
-
this.baseURL = (
|
|
131
|
-
extra.baseURL ??
|
|
132
|
-
readAuthString(opts.auth, "baseURL", "ANTHROPIC_BASE_URL") ??
|
|
133
|
-
"https://api.anthropic.com"
|
|
134
|
-
).replace(/\/v1\/?$/, "");
|
|
135
|
-
this.apiVersion = extra.apiVersion ?? "2023-06-01";
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private async send(
|
|
139
|
-
body: Record<string, unknown>,
|
|
140
|
-
): Promise<AnthropicResponse> {
|
|
141
|
-
const res = await fetch(`${this.baseURL}/v1/messages`, {
|
|
142
|
-
method: "POST",
|
|
143
|
-
headers: {
|
|
144
|
-
"x-api-key": this.apiKey,
|
|
145
|
-
"anthropic-version": this.apiVersion,
|
|
146
|
-
"Content-Type": "application/json",
|
|
147
|
-
},
|
|
148
|
-
body: JSON.stringify(body),
|
|
149
|
-
});
|
|
150
|
-
if (!res.ok) {
|
|
151
|
-
const text = await res.text().catch(() => "");
|
|
152
|
-
throw new Error(
|
|
153
|
-
`anthropic request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
return (await res.json()) as AnthropicResponse;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async call<T>(model: string, options: CallOptions): Promise<T> {
|
|
160
|
-
const jsonMode = "jsonSchemaName" in options;
|
|
161
|
-
const reasoning = defaultReasoningEffort(
|
|
162
|
-
options.reasoningEffort,
|
|
163
|
-
model,
|
|
164
|
-
this.models.identity,
|
|
165
|
-
);
|
|
166
|
-
const log = logger.child("llm:anthropic");
|
|
167
|
-
log.debug(
|
|
168
|
-
`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`,
|
|
169
|
-
);
|
|
170
|
-
const body: Record<string, unknown> = {
|
|
171
|
-
model,
|
|
172
|
-
max_tokens: 4096,
|
|
173
|
-
system: options.instruction,
|
|
174
|
-
messages: [{ role: "user", content: options.message }],
|
|
175
|
-
};
|
|
176
|
-
if (reasoning !== "none") {
|
|
177
|
-
body["thinking"] = {
|
|
178
|
-
type: "enabled",
|
|
179
|
-
budget_tokens: REASONING_BUDGET[reasoning],
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
const data = await this.send(body);
|
|
183
|
-
if (data.error) {
|
|
184
|
-
throw new Error(
|
|
185
|
-
`anthropic API error: ${data.error.message ?? "unknown"}`,
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
const text = (data.content ?? [])
|
|
189
|
-
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
190
|
-
.map((b) => b.text)
|
|
191
|
-
.join("");
|
|
192
|
-
if (!text) {
|
|
193
|
-
throw new Error("Empty response from model");
|
|
194
|
-
}
|
|
195
|
-
return (jsonMode ? JSON.parse(text) : text) as T;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async chatWithTools(
|
|
199
|
-
model: string,
|
|
200
|
-
options: ChatWithToolsOptions,
|
|
201
|
-
): Promise<ChatChoice> {
|
|
202
|
-
const reasoning = defaultReasoningEffort(
|
|
203
|
-
options.reasoningEffort,
|
|
204
|
-
model,
|
|
205
|
-
this.models.identity,
|
|
206
|
-
);
|
|
207
|
-
const log = logger.child("llm:anthropic");
|
|
208
|
-
log.debug(
|
|
209
|
-
`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`,
|
|
210
|
-
);
|
|
211
|
-
const { system, msgs } = toAnthropicMessages(options.messages);
|
|
212
|
-
const body: Record<string, unknown> = {
|
|
213
|
-
model,
|
|
214
|
-
max_tokens: 4096,
|
|
215
|
-
system: system ?? options.instruction,
|
|
216
|
-
messages: msgs,
|
|
217
|
-
tools: options.tools.map(toAnthropicTool),
|
|
218
|
-
};
|
|
219
|
-
if (reasoning !== "none") {
|
|
220
|
-
body["thinking"] = {
|
|
221
|
-
type: "enabled",
|
|
222
|
-
budget_tokens: REASONING_BUDGET[reasoning],
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
const data = await this.send(body);
|
|
226
|
-
if (data.error) {
|
|
227
|
-
throw new Error(
|
|
228
|
-
`anthropic API error: ${data.error.message ?? "unknown"}`,
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
const blocks = data.content ?? [];
|
|
232
|
-
const text = blocks
|
|
233
|
-
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
234
|
-
.map((b) => b.text)
|
|
235
|
-
.join("");
|
|
236
|
-
const toolCalls: ToolCall[] | undefined = blocks
|
|
237
|
-
.filter(
|
|
238
|
-
(
|
|
239
|
-
b,
|
|
240
|
-
): b is {
|
|
241
|
-
type: "tool_use";
|
|
242
|
-
id: string;
|
|
243
|
-
name: string;
|
|
244
|
-
input: unknown;
|
|
245
|
-
} => b.type === "tool_use",
|
|
246
|
-
)
|
|
247
|
-
.map((b) => ({
|
|
248
|
-
id: b.id,
|
|
249
|
-
function: {
|
|
250
|
-
name: b.name,
|
|
251
|
-
arguments:
|
|
252
|
-
typeof b.input === "string" ? b.input : JSON.stringify(b.input),
|
|
253
|
-
},
|
|
254
|
-
}));
|
|
255
|
-
return { message: { content: text || undefined, toolCalls } };
|
|
256
|
-
}
|
|
257
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
import { readAuthString, type ReasoningEffort } from "../llm";
|
|
3
|
-
|
|
4
|
-
export class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
|
|
5
|
-
constructor(opts: {
|
|
6
|
-
apiKey: string;
|
|
7
|
-
conversationModel: string;
|
|
8
|
-
identityModel: string;
|
|
9
|
-
auth?: Record<string, unknown>;
|
|
10
|
-
}) {
|
|
11
|
-
const resource = readAuthString(
|
|
12
|
-
opts.auth,
|
|
13
|
-
"resource",
|
|
14
|
-
"AZURE_COGNITIVE_RESOURCE",
|
|
15
|
-
);
|
|
16
|
-
super({
|
|
17
|
-
providerName: "azure-cognitive",
|
|
18
|
-
baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
|
|
19
|
-
apiKey: opts.apiKey,
|
|
20
|
-
conversationModel: opts.conversationModel,
|
|
21
|
-
identityModel: opts.identityModel,
|
|
22
|
-
});
|
|
23
|
-
this.apiVersion =
|
|
24
|
-
readAuthString(opts.auth, "apiVersion", "AZURE_COGNITIVE_API_VERSION") ||
|
|
25
|
-
"2024-08-01-preview";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
private readonly apiVersion: string;
|
|
29
|
-
|
|
30
|
-
protected override buildRequestUrl(
|
|
31
|
-
model: string,
|
|
32
|
-
_reasoningEffort: ReasoningEffort | undefined,
|
|
33
|
-
): string {
|
|
34
|
-
const url = new URL(
|
|
35
|
-
`${this.baseURL}/${encodeURIComponent(model)}/chat/completions`,
|
|
36
|
-
);
|
|
37
|
-
url.searchParams.set("api-version", this.apiVersion);
|
|
38
|
-
return url.toString();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { logger } from "@/utils/logger";
|
|
2
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
3
|
-
import { readAuthString, type ReasoningEffort } from "../llm";
|
|
4
|
-
|
|
5
|
-
export class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
|
|
6
|
-
private readonly apiVersion: string;
|
|
7
|
-
|
|
8
|
-
constructor(opts: {
|
|
9
|
-
apiKey: string;
|
|
10
|
-
conversationModel: string;
|
|
11
|
-
identityModel: string;
|
|
12
|
-
auth?: Record<string, unknown>;
|
|
13
|
-
}) {
|
|
14
|
-
const resource = readAuthString(
|
|
15
|
-
opts.auth,
|
|
16
|
-
"resource",
|
|
17
|
-
"AZURE_OPENAI_RESOURCE",
|
|
18
|
-
);
|
|
19
|
-
const apiVersion =
|
|
20
|
-
readAuthString(opts.auth, "apiVersion", "AZURE_OPENAI_API_VERSION") ||
|
|
21
|
-
"2024-08-01-preview";
|
|
22
|
-
if (!resource) {
|
|
23
|
-
logger
|
|
24
|
-
.child("llm:azure-openai")
|
|
25
|
-
.warn(
|
|
26
|
-
"azure-openai: no resource configured; set auth.azure-openai.resource or AZURE_OPENAI_RESOURCE",
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
super({
|
|
30
|
-
providerName: "azure-openai",
|
|
31
|
-
baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
|
|
32
|
-
apiKey: opts.apiKey,
|
|
33
|
-
conversationModel: opts.conversationModel,
|
|
34
|
-
identityModel: opts.identityModel,
|
|
35
|
-
});
|
|
36
|
-
this.apiVersion = apiVersion;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
protected override buildRequestUrl(
|
|
40
|
-
model: string,
|
|
41
|
-
_reasoningEffort: ReasoningEffort | undefined,
|
|
42
|
-
): string {
|
|
43
|
-
const url = new URL(
|
|
44
|
-
`${this.baseURL}/${encodeURIComponent(model)}/chat/completions`,
|
|
45
|
-
);
|
|
46
|
-
url.searchParams.set("api-version", this.apiVersion);
|
|
47
|
-
return url.toString();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class BasetenExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "baseten",
|
|
11
|
-
baseURL: "https://api.baseten.co/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import { logger } from "@/utils/logger";
|
|
2
|
-
import { createHmac, createHash } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
-
LLMExecutor,
|
|
5
|
-
readAuthString,
|
|
6
|
-
type CallOptions,
|
|
7
|
-
type ChatChoice,
|
|
8
|
-
type ChatFunctionTool,
|
|
9
|
-
type ChatMessages,
|
|
10
|
-
type ChatWithToolsOptions,
|
|
11
|
-
type ToolCall,
|
|
12
|
-
} from "../llm";
|
|
13
|
-
|
|
14
|
-
type BedrockBody = {
|
|
15
|
-
anthropic_version: string;
|
|
16
|
-
max_tokens: number;
|
|
17
|
-
system?: string;
|
|
18
|
-
messages: Array<Record<string, unknown>>;
|
|
19
|
-
tools?: Array<Record<string, unknown>>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type BedrockResponse = Record<string, unknown>;
|
|
23
|
-
|
|
24
|
-
const pad2 = (n: number): string => n.toString().padStart(2, "0");
|
|
25
|
-
|
|
26
|
-
function amzDate(now: Date): { date: string; datetime: string } {
|
|
27
|
-
return {
|
|
28
|
-
date: `${now.getUTCFullYear()}${pad2(now.getUTCMonth() + 1)}${pad2(now.getUTCDate())}`,
|
|
29
|
-
datetime:
|
|
30
|
-
`${now.getUTCFullYear()}${pad2(now.getUTCMonth() + 1)}${pad2(now.getUTCDate())}T` +
|
|
31
|
-
`${pad2(now.getUTCHours())}${pad2(now.getUTCMinutes())}${pad2(now.getUTCSeconds())}Z`,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function signRequest(opts: {
|
|
36
|
-
method: string;
|
|
37
|
-
host: string;
|
|
38
|
-
path: string;
|
|
39
|
-
body: string;
|
|
40
|
-
region: string;
|
|
41
|
-
service: string;
|
|
42
|
-
accessKey: string;
|
|
43
|
-
secretKey: string;
|
|
44
|
-
sessionToken?: string;
|
|
45
|
-
}): Record<string, string> {
|
|
46
|
-
const now = new Date();
|
|
47
|
-
const { date, datetime } = amzDate(now);
|
|
48
|
-
const payloadHash = createHash("sha256")
|
|
49
|
-
.update(opts.body, "utf8")
|
|
50
|
-
.digest("hex");
|
|
51
|
-
|
|
52
|
-
const canonicalHeaders =
|
|
53
|
-
`content-type:application/json\n` +
|
|
54
|
-
`host:${opts.host}\n` +
|
|
55
|
-
`x-amz-content-sha256:${payloadHash}\n` +
|
|
56
|
-
`x-amz-date:${datetime}\n` +
|
|
57
|
-
(opts.sessionToken ? `x-amz-security-token:${opts.sessionToken}\n` : "");
|
|
58
|
-
const signedHeaders =
|
|
59
|
-
`content-type;host;x-amz-content-sha256;x-amz-date` +
|
|
60
|
-
(opts.sessionToken ? `;x-amz-security-token` : "");
|
|
61
|
-
|
|
62
|
-
const canonicalRequest = [
|
|
63
|
-
opts.method,
|
|
64
|
-
opts.path,
|
|
65
|
-
"",
|
|
66
|
-
canonicalHeaders,
|
|
67
|
-
signedHeaders,
|
|
68
|
-
payloadHash,
|
|
69
|
-
].join("\n");
|
|
70
|
-
|
|
71
|
-
const credentialScope = `${date}/${opts.region}/${opts.service}/aws4_request`;
|
|
72
|
-
const stringToSign = [
|
|
73
|
-
"AWS4-HMAC-SHA256",
|
|
74
|
-
datetime,
|
|
75
|
-
credentialScope,
|
|
76
|
-
createHash("sha256").update(canonicalRequest, "utf8").digest("hex"),
|
|
77
|
-
].join("\n");
|
|
78
|
-
|
|
79
|
-
// AWS SigV4 key derivation: four rounds of HMAC-SHA256 with a layered key.
|
|
80
|
-
const kDate = createHmac("sha256", `AWS4${opts.secretKey}`)
|
|
81
|
-
.update(date, "utf8")
|
|
82
|
-
.digest();
|
|
83
|
-
const kRegion = createHmac("sha256", kDate)
|
|
84
|
-
.update(opts.region, "utf8")
|
|
85
|
-
.digest();
|
|
86
|
-
const kService = createHmac("sha256", kRegion)
|
|
87
|
-
.update(opts.service, "utf8")
|
|
88
|
-
.digest();
|
|
89
|
-
const kSigning = createHmac("sha256", kService)
|
|
90
|
-
.update("aws4_request", "utf8")
|
|
91
|
-
.digest();
|
|
92
|
-
const signature = createHmac("sha256", kSigning)
|
|
93
|
-
.update(stringToSign, "utf8")
|
|
94
|
-
.digest("hex");
|
|
95
|
-
|
|
96
|
-
const auth = `AWS4-HMAC-SHA256 Credential=${opts.accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
97
|
-
const headers: Record<string, string> = {
|
|
98
|
-
Authorization: auth,
|
|
99
|
-
"Content-Type": "application/json",
|
|
100
|
-
"X-Amz-Date": datetime,
|
|
101
|
-
"X-Amz-Content-Sha256": payloadHash,
|
|
102
|
-
};
|
|
103
|
-
if (opts.sessionToken) headers["X-Amz-Security-Token"] = opts.sessionToken;
|
|
104
|
-
return headers;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function toAnthropicMessages(messages: ChatMessages[]): {
|
|
108
|
-
system?: string;
|
|
109
|
-
msgs: Array<Record<string, unknown>>;
|
|
110
|
-
} {
|
|
111
|
-
let system: string | undefined;
|
|
112
|
-
const msgs: Array<Record<string, unknown>> = [];
|
|
113
|
-
for (const m of messages) {
|
|
114
|
-
if (m.role === "system") {
|
|
115
|
-
system = (system ? system + "\n\n" : "") + m.content;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (m.role === "user") {
|
|
119
|
-
msgs.push({ role: "user", content: [{ type: "text", text: m.content }] });
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
if (m.role === "assistant") {
|
|
123
|
-
const blocks: Array<Record<string, unknown>> = [];
|
|
124
|
-
if (m.content) blocks.push({ type: "text", text: m.content });
|
|
125
|
-
if (m.toolCalls) {
|
|
126
|
-
for (const c of m.toolCalls) {
|
|
127
|
-
let input: unknown = {};
|
|
128
|
-
try {
|
|
129
|
-
input = c.function.arguments
|
|
130
|
-
? JSON.parse(c.function.arguments)
|
|
131
|
-
: {};
|
|
132
|
-
} catch {
|
|
133
|
-
input = {};
|
|
134
|
-
}
|
|
135
|
-
blocks.push({
|
|
136
|
-
type: "tool_use",
|
|
137
|
-
id: c.id,
|
|
138
|
-
name: c.function.name,
|
|
139
|
-
input,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
msgs.push({ role: "assistant", content: blocks });
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (m.role === "tool") {
|
|
147
|
-
msgs.push({
|
|
148
|
-
role: "user",
|
|
149
|
-
content: [
|
|
150
|
-
{
|
|
151
|
-
type: "tool_result",
|
|
152
|
-
tool_use_id: m.toolCallId,
|
|
153
|
-
content: [{ type: "text", text: m.content }],
|
|
154
|
-
},
|
|
155
|
-
],
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return { system, msgs };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function toAnthropicTool(t: ChatFunctionTool) {
|
|
163
|
-
return {
|
|
164
|
-
name: t.name,
|
|
165
|
-
description: t.description,
|
|
166
|
-
input_schema: t.parameters ?? { type: "object", properties: {} },
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function extractAnthropicContent(data: BedrockResponse): {
|
|
171
|
-
text: string;
|
|
172
|
-
toolCalls: ToolCall[] | undefined;
|
|
173
|
-
} {
|
|
174
|
-
const content = (data["content"] as Array<Record<string, unknown>>) ?? [];
|
|
175
|
-
const text = content
|
|
176
|
-
.filter((b) => b["type"] === "text")
|
|
177
|
-
.map((b) => b["text"] as string)
|
|
178
|
-
.join("");
|
|
179
|
-
const toolCalls: ToolCall[] | undefined = content
|
|
180
|
-
.filter((b) => b["type"] === "tool_use")
|
|
181
|
-
.map((b) => ({
|
|
182
|
-
id: b["id"] as string,
|
|
183
|
-
function: {
|
|
184
|
-
name: b["name"] as string,
|
|
185
|
-
arguments:
|
|
186
|
-
typeof b["input"] === "string"
|
|
187
|
-
? b["input"]
|
|
188
|
-
: JSON.stringify(b["input"] ?? {}),
|
|
189
|
-
},
|
|
190
|
-
}));
|
|
191
|
-
return { text, toolCalls };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export class BedrockExecutor extends LLMExecutor {
|
|
195
|
-
readonly providerName = "bedrock";
|
|
196
|
-
readonly models: { conversation: string; identity: string };
|
|
197
|
-
|
|
198
|
-
private readonly accessKeyId: string;
|
|
199
|
-
private readonly secretAccessKey: string;
|
|
200
|
-
private readonly region: string;
|
|
201
|
-
private readonly baseURL: string;
|
|
202
|
-
private readonly sessionToken: string;
|
|
203
|
-
|
|
204
|
-
constructor(opts: {
|
|
205
|
-
apiKey: string;
|
|
206
|
-
conversationModel: string;
|
|
207
|
-
identityModel: string;
|
|
208
|
-
auth?: Record<string, unknown>;
|
|
209
|
-
}) {
|
|
210
|
-
super();
|
|
211
|
-
this.models = {
|
|
212
|
-
conversation: opts.conversationModel,
|
|
213
|
-
identity: opts.identityModel,
|
|
214
|
-
};
|
|
215
|
-
const colon = opts.apiKey.indexOf(":");
|
|
216
|
-
this.accessKeyId = colon >= 0 ? opts.apiKey.slice(0, colon) : opts.apiKey;
|
|
217
|
-
this.secretAccessKey =
|
|
218
|
-
colon >= 0
|
|
219
|
-
? opts.apiKey.slice(colon + 1)
|
|
220
|
-
: readAuthString(opts.auth, "secretAccessKey", "AWS_SECRET_ACCESS_KEY");
|
|
221
|
-
this.region =
|
|
222
|
-
readAuthString(opts.auth, "region", "AWS_REGION") || "us-east-1";
|
|
223
|
-
this.sessionToken = readAuthString(
|
|
224
|
-
opts.auth,
|
|
225
|
-
"sessionToken",
|
|
226
|
-
"AWS_SESSION_TOKEN",
|
|
227
|
-
);
|
|
228
|
-
this.baseURL = `https://bedrock-runtime.${this.region}.amazonaws.com`;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private async invoke(
|
|
232
|
-
model: string,
|
|
233
|
-
body: BedrockBody,
|
|
234
|
-
): Promise<BedrockResponse> {
|
|
235
|
-
const bodyStr = JSON.stringify(body);
|
|
236
|
-
const path = `/model/${encodeURIComponent(model)}/invoke`;
|
|
237
|
-
const headers = signRequest({
|
|
238
|
-
method: "POST",
|
|
239
|
-
host: `bedrock-runtime.${this.region}.amazonaws.com`,
|
|
240
|
-
path,
|
|
241
|
-
body: bodyStr,
|
|
242
|
-
region: this.region,
|
|
243
|
-
service: "bedrock",
|
|
244
|
-
accessKey: this.accessKeyId,
|
|
245
|
-
secretKey: this.secretAccessKey,
|
|
246
|
-
sessionToken: this.sessionToken || undefined,
|
|
247
|
-
});
|
|
248
|
-
const res = await fetch(this.baseURL + path, {
|
|
249
|
-
method: "POST",
|
|
250
|
-
headers,
|
|
251
|
-
body: bodyStr,
|
|
252
|
-
});
|
|
253
|
-
if (!res.ok) {
|
|
254
|
-
const text = await res.text().catch(() => "");
|
|
255
|
-
throw new Error(
|
|
256
|
-
`bedrock request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
return (await res.json()) as BedrockResponse;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
async call<T>(model: string, options: CallOptions): Promise<T> {
|
|
263
|
-
const jsonMode = "jsonSchemaName" in options;
|
|
264
|
-
const log = logger.child("llm:bedrock");
|
|
265
|
-
log.debug(
|
|
266
|
-
`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`,
|
|
267
|
-
);
|
|
268
|
-
if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
|
|
269
|
-
throw new Error(
|
|
270
|
-
`bedrock provider currently only supports Anthropic models (got ${model})`,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
const body: BedrockBody = {
|
|
274
|
-
anthropic_version: "bedrock-2023-05-31",
|
|
275
|
-
max_tokens: 4096,
|
|
276
|
-
system: options.instruction,
|
|
277
|
-
messages: [{ role: "user", content: options.message }],
|
|
278
|
-
};
|
|
279
|
-
const data = await this.invoke(model, body);
|
|
280
|
-
const { text } = extractAnthropicContent(data);
|
|
281
|
-
if (!text) {
|
|
282
|
-
throw new Error("Empty response from model");
|
|
283
|
-
}
|
|
284
|
-
return (jsonMode ? JSON.parse(text) : text) as T;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async chatWithTools(
|
|
288
|
-
model: string,
|
|
289
|
-
options: ChatWithToolsOptions,
|
|
290
|
-
): Promise<ChatChoice> {
|
|
291
|
-
const log = logger.child("llm:bedrock");
|
|
292
|
-
log.debug(
|
|
293
|
-
`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`,
|
|
294
|
-
);
|
|
295
|
-
if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
|
|
296
|
-
throw new Error(
|
|
297
|
-
`bedrock provider currently only supports Anthropic models (got ${model})`,
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
const { system, msgs } = toAnthropicMessages(options.messages);
|
|
301
|
-
const body: BedrockBody = {
|
|
302
|
-
anthropic_version: "bedrock-2023-05-31",
|
|
303
|
-
max_tokens: 4096,
|
|
304
|
-
system: system ?? options.instruction,
|
|
305
|
-
messages: msgs,
|
|
306
|
-
tools: options.tools.map(toAnthropicTool),
|
|
307
|
-
};
|
|
308
|
-
const data = await this.invoke(model, body);
|
|
309
|
-
const { text, toolCalls } = extractAnthropicContent(data);
|
|
310
|
-
return { message: { content: text || undefined, toolCalls } };
|
|
311
|
-
}
|
|
312
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleExecutor } from "./openai_compatible";
|
|
2
|
-
|
|
3
|
-
export class CerebrasExecutor extends OpenAICompatibleExecutor {
|
|
4
|
-
constructor(opts: {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
conversationModel: string;
|
|
7
|
-
identityModel: string;
|
|
8
|
-
}) {
|
|
9
|
-
super({
|
|
10
|
-
providerName: "cerebras",
|
|
11
|
-
baseURL: "https://api.cerebras.ai/v1",
|
|
12
|
-
apiKey: opts.apiKey,
|
|
13
|
-
conversationModel: opts.conversationModel,
|
|
14
|
-
identityModel: opts.identityModel,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|