@mnemom/mnemom 0.7.2 → 0.8.0
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/commands/agents.d.ts +0 -14
- package/dist/commands/agents.js +35 -295
- package/dist/commands/auth.js +2 -7
- package/dist/commands/card.d.ts +21 -8
- package/dist/commands/card.js +328 -275
- package/dist/commands/integrity.js +6 -6
- package/dist/commands/license.js +14 -29
- package/dist/commands/logs.js +8 -8
- package/dist/commands/policy.d.ts +10 -23
- package/dist/commands/policy.js +20 -533
- package/dist/commands/protection.d.ts +14 -0
- package/dist/commands/protection.js +308 -0
- package/dist/commands/status.js +42 -50
- package/dist/index.js +114 -160
- package/dist/lib/api.d.ts +41 -0
- package/dist/lib/api.js +131 -1
- package/dist/lib/auth.d.ts +30 -21
- package/dist/lib/auth.js +95 -49
- package/dist/lib/config.d.ts +11 -98
- package/dist/lib/config.js +12 -220
- package/dist/lib/model-cache.js +5 -6
- package/dist/smoltbot-shim.js +1 -1
- package/package.json +2 -2
- package/dist/commands/init.d.ts +0 -7
- package/dist/commands/init.js +0 -763
- package/dist/commands/migrate-config.d.ts +0 -2
- package/dist/commands/migrate-config.js +0 -72
- package/dist/commands/register.d.ts +0 -6
- package/dist/commands/register.js +0 -362
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
const OPENCLAW_CONFIG_PATH = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
6
|
-
export function makeMigrateConfigCommand() {
|
|
7
|
-
const cmd = new Command("migrate-config");
|
|
8
|
-
cmd
|
|
9
|
-
.description("Migrate ~/.openclaw/openclaw.json provider keys from smoltbot* to mnemom*")
|
|
10
|
-
.action(async () => {
|
|
11
|
-
// 1. Check if OpenClaw config exists
|
|
12
|
-
if (!fs.existsSync(OPENCLAW_CONFIG_PATH)) {
|
|
13
|
-
console.log("OpenClaw not detected at ~/.openclaw/openclaw.json. Nothing to migrate.");
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
// 2. Read and parse the config
|
|
17
|
-
let config = null;
|
|
18
|
-
try {
|
|
19
|
-
const raw = fs.readFileSync(OPENCLAW_CONFIG_PATH, "utf-8");
|
|
20
|
-
config = JSON.parse(raw);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
console.error("Error: ~/.openclaw/openclaw.json is malformed JSON.");
|
|
24
|
-
process.exit(1);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (!config)
|
|
28
|
-
return;
|
|
29
|
-
// 3. Get models.providers — must be an object
|
|
30
|
-
const models = config.models;
|
|
31
|
-
const providers = models?.providers;
|
|
32
|
-
if (!providers || typeof providers !== "object" || Object.keys(providers).length === 0) {
|
|
33
|
-
console.log("No providers found.");
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
// 4. Find all keys matching /^smoltbot/
|
|
37
|
-
const keysToMigrate = Object.keys(providers).filter((k) => /^smoltbot/.test(k));
|
|
38
|
-
if (keysToMigrate.length === 0) {
|
|
39
|
-
console.log("No smoltbot* provider keys found. Nothing to migrate.");
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
// 5. Migrate each key
|
|
43
|
-
const migrations = [];
|
|
44
|
-
for (const oldKey of keysToMigrate) {
|
|
45
|
-
const newKey = oldKey.replace(/^smoltbot/, "mnemom");
|
|
46
|
-
const entry = providers[oldKey];
|
|
47
|
-
// Deep-copy the provider entry
|
|
48
|
-
const newEntry = { ...entry };
|
|
49
|
-
// Rename x-smoltbot-agent header if present
|
|
50
|
-
const defaultHeaders = newEntry.defaultHeaders;
|
|
51
|
-
if (defaultHeaders && "x-smoltbot-agent" in defaultHeaders) {
|
|
52
|
-
const headersCopy = { ...defaultHeaders };
|
|
53
|
-
headersCopy["x-mnemom-agent"] = headersCopy["x-smoltbot-agent"];
|
|
54
|
-
delete headersCopy["x-smoltbot-agent"];
|
|
55
|
-
newEntry.defaultHeaders = headersCopy;
|
|
56
|
-
}
|
|
57
|
-
// Remove old key, add new key
|
|
58
|
-
delete providers[oldKey];
|
|
59
|
-
providers[newKey] = newEntry;
|
|
60
|
-
migrations.push({ from: oldKey, to: newKey });
|
|
61
|
-
}
|
|
62
|
-
// 6. Write back
|
|
63
|
-
fs.writeFileSync(OPENCLAW_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
64
|
-
// 7. Print summary
|
|
65
|
-
console.log(`✓ Migrated ${migrations.length} provider${migrations.length !== 1 ? "s" : ""}:`);
|
|
66
|
-
for (const { from, to } of migrations) {
|
|
67
|
-
console.log(` ${from} → ${to}`);
|
|
68
|
-
}
|
|
69
|
-
console.log("\nYour agent config at ~/.smoltbot/ is unchanged.\n");
|
|
70
|
-
});
|
|
71
|
-
return cmd;
|
|
72
|
-
}
|
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
2
|
-
import * as crypto from "node:crypto";
|
|
3
|
-
import { loadConfig, saveConfig, deriveAgentIdWithName, } from "../lib/config.js";
|
|
4
|
-
import { detectProviders, configureNamedAgentProviders, } from "../lib/openclaw.js";
|
|
5
|
-
import { getLatestModels, } from "../lib/models.js";
|
|
6
|
-
import { askYesNo, askInput, askSelect, isInteractive } from "../lib/prompt.js";
|
|
7
|
-
import { fmt } from "../lib/format.js";
|
|
8
|
-
const GATEWAY_URL = "https://gateway.mnemom.ai";
|
|
9
|
-
const DASHBOARD_URL = "https://mnemom.ai";
|
|
10
|
-
/** Validate agent name: alphanumeric + hyphens, 1-32 chars, not "default" */
|
|
11
|
-
function validateAgentName(name) {
|
|
12
|
-
if (name.toLowerCase() === "default") {
|
|
13
|
-
return '"default" is reserved. Choose a different name.';
|
|
14
|
-
}
|
|
15
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9-]{0,30}[a-zA-Z0-9]$/.test(name) && !/^[a-zA-Z0-9]{1,2}$/.test(name)) {
|
|
16
|
-
return "Name must be 1-32 alphanumeric characters or hyphens, cannot start/end with hyphen.";
|
|
17
|
-
}
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
export async function registerCommand(name, options = {}) {
|
|
21
|
-
console.log(fmt.header("smoltbot register - Add a named agent"));
|
|
22
|
-
console.log();
|
|
23
|
-
// Validate name
|
|
24
|
-
const nameError = validateAgentName(name);
|
|
25
|
-
if (nameError) {
|
|
26
|
-
console.log(fmt.error(nameError) + "\n");
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
// Load config
|
|
30
|
-
const config = loadConfig();
|
|
31
|
-
if (!config) {
|
|
32
|
-
console.log(fmt.error("smoltbot is not initialized") + "\n");
|
|
33
|
-
console.log("Run `smoltbot init` first to set up the default agent.\n");
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
// Check for duplicate
|
|
37
|
-
if (config.agents[name]) {
|
|
38
|
-
console.log(fmt.warn(`Agent "${name}" already exists`) + "\n");
|
|
39
|
-
console.log(fmt.label(" Agent ID:", ` ${config.agents[name].agentId}`));
|
|
40
|
-
console.log();
|
|
41
|
-
if (!isInteractive()) {
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
const overwrite = await askYesNo(`Overwrite agent "${name}"?`, false);
|
|
45
|
-
if (!overwrite) {
|
|
46
|
-
console.log("\nNo changes made.\n");
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
console.log();
|
|
50
|
-
}
|
|
51
|
-
// Detect providers
|
|
52
|
-
const detection = detectProviders();
|
|
53
|
-
if (!detection.installed && !options.standalone) {
|
|
54
|
-
console.log(fmt.error("OpenClaw not found. Use --standalone for standalone mode.") + "\n");
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
// Find API key for agent ID derivation
|
|
58
|
-
let apiKey;
|
|
59
|
-
let selectedProvider = "anthropic";
|
|
60
|
-
const PROVIDER_LABELS = {
|
|
61
|
-
anthropic: "Anthropic",
|
|
62
|
-
openai: "OpenAI",
|
|
63
|
-
gemini: "Gemini",
|
|
64
|
-
};
|
|
65
|
-
const KEY_PREFIXES = {
|
|
66
|
-
anthropic: "sk-ant-",
|
|
67
|
-
openai: "sk-",
|
|
68
|
-
gemini: "AIza",
|
|
69
|
-
};
|
|
70
|
-
const ENV_VARS = {
|
|
71
|
-
anthropic: "ANTHROPIC_API_KEY",
|
|
72
|
-
openai: "OPENAI_API_KEY",
|
|
73
|
-
gemini: "GEMINI_API_KEY",
|
|
74
|
-
};
|
|
75
|
-
if (options.standalone || !detection.installed) {
|
|
76
|
-
// Standalone: ask which provider, then prompt for key
|
|
77
|
-
if (isInteractive()) {
|
|
78
|
-
const choice = await askSelect("Which provider will this agent use?", ["Anthropic", "OpenAI", "Gemini"]);
|
|
79
|
-
const providerMap = {
|
|
80
|
-
Anthropic: "anthropic",
|
|
81
|
-
OpenAI: "openai",
|
|
82
|
-
Gemini: "gemini",
|
|
83
|
-
};
|
|
84
|
-
selectedProvider = (choice ? providerMap[choice] : "anthropic") || "anthropic";
|
|
85
|
-
apiKey = await askInput(`${PROVIDER_LABELS[selectedProvider]} API key (${KEY_PREFIXES[selectedProvider]}...):`, true);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
apiKey = process.env.ANTHROPIC_API_KEY
|
|
89
|
-
|| process.env.OPENAI_API_KEY
|
|
90
|
-
|| process.env.GEMINI_API_KEY;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
// OpenClaw: use first available key
|
|
95
|
-
for (const provider of ["anthropic", "openai", "gemini"]) {
|
|
96
|
-
const info = detection.providers[provider];
|
|
97
|
-
if (info.hasApiKey && info.apiKey) {
|
|
98
|
-
apiKey = info.apiKey;
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (!apiKey) {
|
|
104
|
-
console.log(fmt.error("No API key available for agent identity") + "\n");
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
// Derive agent ID with name
|
|
108
|
-
const agentId = deriveAgentIdWithName(apiKey, name);
|
|
109
|
-
console.log(fmt.label("Agent ID:", ` ${agentId}`));
|
|
110
|
-
console.log(fmt.label("Name: ", ` ${name}`));
|
|
111
|
-
console.log(fmt.label("Gateway: ", ` ${config.gateway} (x-smoltbot-agent: ${name})`));
|
|
112
|
-
console.log();
|
|
113
|
-
// Configure OpenClaw providers for named agent
|
|
114
|
-
if (detection.installed && !options.standalone) {
|
|
115
|
-
console.log("Configuring OpenClaw providers for named agent...\n");
|
|
116
|
-
const verifiedProviders = [];
|
|
117
|
-
for (const provider of ["anthropic", "openai", "gemini"]) {
|
|
118
|
-
const info = detection.providers[provider];
|
|
119
|
-
if (info.hasApiKey && info.apiKey) {
|
|
120
|
-
verifiedProviders.push({ provider, apiKey: info.apiKey });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (verifiedProviders.length > 0) {
|
|
124
|
-
const latestModels = getLatestModels();
|
|
125
|
-
const providerData = {};
|
|
126
|
-
for (const { provider, apiKey: key } of verifiedProviders) {
|
|
127
|
-
providerData[provider] = {
|
|
128
|
-
apiKey: key,
|
|
129
|
-
models: latestModels[provider],
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const configured = configureNamedAgentProviders(name, providerData);
|
|
133
|
-
for (const provider of configured) {
|
|
134
|
-
const configKey = `smoltbot-${name}` + (provider === "anthropic" ? "" : `-${provider}`);
|
|
135
|
-
console.log(fmt.success(`${provider} provider configured (${configKey})`));
|
|
136
|
-
}
|
|
137
|
-
console.log();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Save agent to config
|
|
141
|
-
const agentConfig = {
|
|
142
|
-
agentId,
|
|
143
|
-
openclawConfigured: detection.installed && !options.standalone,
|
|
144
|
-
providers: options.standalone ? [selectedProvider] : undefined,
|
|
145
|
-
configuredAt: new Date().toISOString(),
|
|
146
|
-
};
|
|
147
|
-
config.agents[name] = agentConfig;
|
|
148
|
-
if (options.setDefault) {
|
|
149
|
-
config.defaultAgent = name;
|
|
150
|
-
console.log(fmt.success(`Set "${name}" as default agent`) + "\n");
|
|
151
|
-
}
|
|
152
|
-
saveConfig(config);
|
|
153
|
-
console.log(fmt.success(`Agent "${name}" registered locally`) + "\n");
|
|
154
|
-
// Make a real API call through the gateway to verify key AND create agent on server
|
|
155
|
-
// This MUST succeed — the agent doesn't exist on the server until this call goes through
|
|
156
|
-
const gatewayProvider = options.standalone ? selectedProvider : "anthropic";
|
|
157
|
-
let verified = false;
|
|
158
|
-
while (!verified) {
|
|
159
|
-
console.log(`Connecting to gateway...`);
|
|
160
|
-
const testResult = await testGatewayCall(config.gateway, name, gatewayProvider, apiKey, config.mnemomApiKey);
|
|
161
|
-
if (testResult.ok && testResult.response) {
|
|
162
|
-
// Update stored agent ID to match the server-assigned UUID (scale/step-25b).
|
|
163
|
-
// The gateway now generates mnm-{uuid} IDs — the locally-derived ID is a placeholder.
|
|
164
|
-
if (testResult.agentId && config.agents[name]) {
|
|
165
|
-
config.agents[name].agentId = testResult.agentId;
|
|
166
|
-
saveConfig(config);
|
|
167
|
-
}
|
|
168
|
-
console.log(fmt.success("Connected! First response from " + name + ":") + "\n");
|
|
169
|
-
console.log(` "${testResult.response}"\n`);
|
|
170
|
-
console.log(fmt.success("Agent created on mnemom.ai") + "\n");
|
|
171
|
-
verified = true;
|
|
172
|
-
}
|
|
173
|
-
else if (testResult.authError) {
|
|
174
|
-
console.log(fmt.error(`API key invalid: ${testResult.error}`) + "\n");
|
|
175
|
-
console.log(" Check your API key and try again with:");
|
|
176
|
-
console.log(` smoltbot register ${name}\n`);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
console.log(fmt.error(`Connection failed: ${testResult.error}`) + "\n");
|
|
181
|
-
if (!isInteractive()) {
|
|
182
|
-
console.log(" Run `smoltbot register " + name + "` to retry.\n");
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const retry = await askYesNo("Retry?", true);
|
|
186
|
-
if (!retry) {
|
|
187
|
-
console.log("\n Agent is saved locally but NOT created on the server.");
|
|
188
|
-
console.log(` Run \`smoltbot register ${name}\` to try again.\n`);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
console.log();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Only show claim + next steps after successful gateway verification
|
|
195
|
-
// Include hash proof in URL so the website can auto-fill it
|
|
196
|
-
// Hash proof must match how the gateway computed agent_hash:
|
|
197
|
-
// named agents use hash(apiKey + '|' + name), default uses hash(apiKey)
|
|
198
|
-
// eslint-disable-next-line -- not password hashing: hash proof must match website's SHA-256 verification
|
|
199
|
-
const hashInput = apiKey + "|" + name;
|
|
200
|
-
const hashProof = crypto.createHash("sha256").update(hashInput).digest("hex");
|
|
201
|
-
const claimUrl = `${DASHBOARD_URL}/claim/${agentId}?hash=${hashProof}`;
|
|
202
|
-
console.log(fmt.section("Link to your Mnemom account"));
|
|
203
|
-
console.log();
|
|
204
|
-
console.log(` Sign in (or create a free account) to see your agent's`);
|
|
205
|
-
console.log(` traces and manage its alignment card.\n`);
|
|
206
|
-
console.log(` ${claimUrl}\n`);
|
|
207
|
-
if (isInteractive()) {
|
|
208
|
-
const openBrowser = await askYesNo("Open in browser?", true);
|
|
209
|
-
if (openBrowser) {
|
|
210
|
-
openUrl(claimUrl);
|
|
211
|
-
console.log();
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
console.log();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
// Show usage instructions
|
|
218
|
-
console.log(fmt.section("Start using your agent"));
|
|
219
|
-
console.log();
|
|
220
|
-
if (detection.installed && !options.standalone) {
|
|
221
|
-
console.log(` openclaw models set smoltbot-${name}/<model-id>`);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
console.log(` Add the x-smoltbot-agent header to your API calls:\n`);
|
|
225
|
-
console.log(` x-smoltbot-agent: ${name}\n`);
|
|
226
|
-
if (selectedProvider === "anthropic") {
|
|
227
|
-
console.log(` Python: client = Anthropic(base_url="${config.gateway}/anthropic",`);
|
|
228
|
-
console.log(` default_headers={"x-smoltbot-agent": "${name}"})`);
|
|
229
|
-
console.log(` TypeScript: new Anthropic({ baseURL: "${config.gateway}/anthropic",`);
|
|
230
|
-
console.log(` defaultHeaders: { "x-smoltbot-agent": "${name}" } })`);
|
|
231
|
-
}
|
|
232
|
-
else if (selectedProvider === "openai") {
|
|
233
|
-
console.log(` Python: client = OpenAI(base_url="${config.gateway}/openai/v1",`);
|
|
234
|
-
console.log(` default_headers={"x-smoltbot-agent": "${name}"})`);
|
|
235
|
-
console.log(` TypeScript: new OpenAI({ baseURL: "${config.gateway}/openai/v1",`);
|
|
236
|
-
console.log(` defaultHeaders: { "x-smoltbot-agent": "${name}" } })`);
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
console.log(` Add header: x-smoltbot-agent: ${name}`);
|
|
240
|
-
console.log(` Endpoint: ${config.gateway}/gemini/v1beta/models/{model}:generateContent`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
console.log();
|
|
244
|
-
console.log(` smoltbot status --agent ${name} Check status`);
|
|
245
|
-
console.log(` smoltbot agents List all agents`);
|
|
246
|
-
console.log();
|
|
247
|
-
console.log(` Dashboard: ${DASHBOARD_URL}/agents/${agentId}\n`);
|
|
248
|
-
}
|
|
249
|
-
const HELLO_PROMPT = "Say hello in one short sentence. Keep it under 15 words.";
|
|
250
|
-
/**
|
|
251
|
-
* Make a real API call through the gateway to verify the key,
|
|
252
|
-
* create the agent on the server, and return the first response.
|
|
253
|
-
*/
|
|
254
|
-
async function testGatewayCall(gateway, agentName, provider, apiKey, mnemomApiKey) {
|
|
255
|
-
const agentHeader = { "x-smoltbot-agent": agentName };
|
|
256
|
-
try {
|
|
257
|
-
let url;
|
|
258
|
-
let headers;
|
|
259
|
-
let body;
|
|
260
|
-
if (provider === "anthropic") {
|
|
261
|
-
url = new URL(`${gateway}/anthropic/v1/messages`).href;
|
|
262
|
-
headers = {
|
|
263
|
-
"Content-Type": "application/json",
|
|
264
|
-
"x-api-key": apiKey,
|
|
265
|
-
"anthropic-version": "2023-06-01",
|
|
266
|
-
...agentHeader,
|
|
267
|
-
...(mnemomApiKey ? { "x-mnemom-api-key": mnemomApiKey } : {}),
|
|
268
|
-
};
|
|
269
|
-
body = JSON.stringify({
|
|
270
|
-
model: "claude-haiku-4-5-20251001",
|
|
271
|
-
max_tokens: 16000,
|
|
272
|
-
messages: [{ role: "user", content: HELLO_PROMPT }],
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
else if (provider === "openai") {
|
|
276
|
-
url = new URL(`${gateway}/openai/v1/chat/completions`).href;
|
|
277
|
-
headers = {
|
|
278
|
-
"Content-Type": "application/json",
|
|
279
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
280
|
-
...agentHeader,
|
|
281
|
-
...(mnemomApiKey ? { "x-mnemom-api-key": mnemomApiKey } : {}),
|
|
282
|
-
};
|
|
283
|
-
body = JSON.stringify({
|
|
284
|
-
model: "gpt-4o-mini",
|
|
285
|
-
max_tokens: 150,
|
|
286
|
-
messages: [{ role: "user", content: HELLO_PROMPT }],
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
// Gemini
|
|
291
|
-
url = new URL(`${gateway}/gemini/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`).href;
|
|
292
|
-
headers = {
|
|
293
|
-
"Content-Type": "application/json",
|
|
294
|
-
...agentHeader,
|
|
295
|
-
...(mnemomApiKey ? { "x-mnemom-api-key": mnemomApiKey } : {}),
|
|
296
|
-
};
|
|
297
|
-
body = JSON.stringify({
|
|
298
|
-
contents: [{ parts: [{ text: HELLO_PROMPT }] }],
|
|
299
|
-
generationConfig: { maxOutputTokens: 150 },
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
const response = await fetch(url, {
|
|
303
|
-
method: "POST",
|
|
304
|
-
headers,
|
|
305
|
-
body,
|
|
306
|
-
signal: AbortSignal.timeout(30000),
|
|
307
|
-
});
|
|
308
|
-
if (response.status === 401 || response.status === 403) {
|
|
309
|
-
return { ok: false, authError: true, error: "API key is invalid or revoked" };
|
|
310
|
-
}
|
|
311
|
-
if (response.status === 429) {
|
|
312
|
-
const agentId = response.headers.get("x-mnemom-agent") ?? undefined;
|
|
313
|
-
return { ok: true, response: "(rate limited — but key is valid)", agentId };
|
|
314
|
-
}
|
|
315
|
-
if (!response.ok) {
|
|
316
|
-
const errorBody = await response.text().catch(() => "");
|
|
317
|
-
return { ok: false, error: `HTTP ${response.status}: ${errorBody.slice(0, 200)}` };
|
|
318
|
-
}
|
|
319
|
-
// Extract server-assigned agent ID from response header (scale/step-25b)
|
|
320
|
-
const agentId = response.headers.get("x-mnemom-agent") ?? undefined;
|
|
321
|
-
// Extract response text from provider-specific format
|
|
322
|
-
const data = await response.json();
|
|
323
|
-
const text = extractResponseText(data, provider);
|
|
324
|
-
return { ok: true, response: text || "(empty response)", agentId };
|
|
325
|
-
}
|
|
326
|
-
catch (err) {
|
|
327
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
328
|
-
if (message.includes("abort") || message.includes("timeout")) {
|
|
329
|
-
return { ok: false, error: "request timed out (30s)" };
|
|
330
|
-
}
|
|
331
|
-
return { ok: false, error: message };
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
function extractResponseText(data, provider) {
|
|
335
|
-
if (provider === "anthropic") {
|
|
336
|
-
// { content: [{ type: "text", text: "..." }] }
|
|
337
|
-
const blocks = data?.content;
|
|
338
|
-
if (Array.isArray(blocks)) {
|
|
339
|
-
for (const block of blocks) {
|
|
340
|
-
if (block.type === "text" && block.text)
|
|
341
|
-
return block.text.trim();
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
else if (provider === "openai") {
|
|
346
|
-
// { choices: [{ message: { content: "..." } }] }
|
|
347
|
-
return data?.choices?.[0]?.message?.content?.trim() || null;
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
// Gemini: { candidates: [{ content: { parts: [{ text: "..." }] } }] }
|
|
351
|
-
return data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() || null;
|
|
352
|
-
}
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
function openUrl(url) {
|
|
356
|
-
const cmd = process.platform === "darwin"
|
|
357
|
-
? `open "${url}"`
|
|
358
|
-
: process.platform === "win32"
|
|
359
|
-
? `start "${url}"`
|
|
360
|
-
: `xdg-open "${url}"`;
|
|
361
|
-
exec(cmd, () => { });
|
|
362
|
-
}
|