df5bcd87b5
Memind CI / Test, build, and release guards (pull_request) Failing after 18s
DeepSeek V4 tool rounds fail with reasoning_content errors when stable goosed hits api.deepseek.com directly. Default stable portal startup to the 18036 compat proxy and add a standalone recovery script for 103 ops. Co-authored-by: Cursor <cursoragent@cursor.com>
141 lines
4.4 KiB
JavaScript
141 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Standalone recovery: point every stable goosed target at the 18036 no-think proxy.
|
|
* Works on bundled 103 runtime (no db.mjs / llm-providers.mjs required).
|
|
*/
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { Agent, fetch as undiciFetch } from 'undici';
|
|
|
|
const GOOSED_PROVIDER_ID = 'custom_memind_deepseek_no_think';
|
|
const DEFAULT_MODEL = 'deepseek-v4-pro';
|
|
const MODELS = ['deepseek-v4-pro', 'deepseek-v4-flash'];
|
|
|
|
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const insecureDispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
|
|
function loadEnvFile(filePath) {
|
|
const env = {};
|
|
if (!fs.existsSync(filePath)) return env;
|
|
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const eq = trimmed.indexOf('=');
|
|
if (eq < 0) continue;
|
|
const key = trimmed.slice(0, eq).trim();
|
|
let value = trimmed.slice(eq + 1).trim();
|
|
if (
|
|
(value.startsWith('"') && value.endsWith('"'))
|
|
|| (value.startsWith("'") && value.endsWith("'"))
|
|
) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
env[key] = value;
|
|
}
|
|
return env;
|
|
}
|
|
|
|
const fileEnv = loadEnvFile(path.join(root, '.env'));
|
|
const env = { ...fileEnv, ...process.env };
|
|
const apiSecret = String(env.TKMIND_SERVER__SECRET_KEY ?? '').trim();
|
|
const apiKey = String(env.DEEPSEEK_API_KEY ?? '').trim();
|
|
const apiTargets = String(
|
|
env.TKMIND_API_TARGETS
|
|
?? env.TKMIND_API_TARGET
|
|
?? 'https://127.0.0.1:18006',
|
|
)
|
|
.split(',')
|
|
.map((item) => item.trim())
|
|
.filter(Boolean);
|
|
|
|
if (!apiSecret) {
|
|
console.error('missing TKMIND_SERVER__SECRET_KEY');
|
|
process.exit(1);
|
|
}
|
|
if (!apiKey) {
|
|
console.error('missing DEEPSEEK_API_KEY');
|
|
process.exit(1);
|
|
}
|
|
|
|
const host = String(env.MEMIND_GOOSED_HOST_GATEWAY ?? 'host.docker.internal').trim() || 'host.docker.internal';
|
|
const port = Number(env.MEMIND_DEEPSEEK_NO_THINK_PORT ?? 18036);
|
|
const proxyBaseUrl = `http://${host}:${port}/v1`;
|
|
|
|
async function goosedFetch(apiTarget, pathname, init = {}) {
|
|
const url = new URL(pathname, apiTarget);
|
|
const headers = {
|
|
...(init.headers ?? {}),
|
|
'X-Secret-Key': apiSecret,
|
|
};
|
|
if (init.body && !headers['Content-Type']) {
|
|
headers['Content-Type'] = 'application/json';
|
|
}
|
|
const dispatcher = apiTarget.startsWith('https://') ? insecureDispatcher : undefined;
|
|
return undiciFetch(url, { ...init, headers, dispatcher });
|
|
}
|
|
|
|
async function upsertConfig(apiTarget, key, value, isSecret = false) {
|
|
const res = await goosedFetch(apiTarget, '/config/upsert', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ key, value, is_secret: isSecret }),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
throw new Error(`upsert ${key} on ${apiTarget} failed: ${res.status} ${text}`);
|
|
}
|
|
}
|
|
|
|
async function upsertProvider(apiTarget) {
|
|
const body = {
|
|
engine: 'openai',
|
|
display_name: 'memind_deepseek_no_think',
|
|
api_url: proxyBaseUrl,
|
|
api_key: apiKey,
|
|
models: MODELS,
|
|
supports_streaming: true,
|
|
requires_auth: true,
|
|
preserves_thinking: false,
|
|
};
|
|
|
|
let res = await goosedFetch(
|
|
apiTarget,
|
|
`/config/custom-providers/${encodeURIComponent(GOOSED_PROVIDER_ID)}`,
|
|
{ method: 'PUT', body: JSON.stringify(body) },
|
|
);
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
const missing = res.status === 404 || /provider not found/i.test(text);
|
|
if (!missing) {
|
|
throw new Error(`upsert provider on ${apiTarget} failed: ${res.status} ${text}`);
|
|
}
|
|
res = await goosedFetch(apiTarget, '/config/custom-providers', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
}
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
throw new Error(`create provider on ${apiTarget} failed: ${res.status} ${text}`);
|
|
}
|
|
|
|
await upsertConfig(apiTarget, 'DEEPSEEK_API_KEY', apiKey, true);
|
|
for (const [key, value] of [
|
|
['GOOSE_PROVIDER', GOOSED_PROVIDER_ID],
|
|
['GOOSE_MODEL', DEFAULT_MODEL],
|
|
['TKMIND_PROVIDER', GOOSED_PROVIDER_ID],
|
|
['TKMIND_MODEL', DEFAULT_MODEL],
|
|
['GOOSE_THINKING_EFFORT', 'off'],
|
|
]) {
|
|
await upsertConfig(apiTarget, key, value, false);
|
|
}
|
|
}
|
|
|
|
const results = [];
|
|
for (const target of apiTargets) {
|
|
await upsertProvider(target);
|
|
results.push({ target, provider: GOOSED_PROVIDER_ID, model: DEFAULT_MODEL, proxyBaseUrl });
|
|
}
|
|
|
|
console.log(JSON.stringify({ ok: true, results }, null, 2));
|