#!/usr/bin/env node /** * Point Memind LLM + local goosed (127.0.0.1:18006) at a working relay profile. * * Prefers ~/.config/goose/custom_providers/andu_deepseek.json (same as Goose Desktop), * then H5_LOCAL_RELAY_* env overrides. * * Usage: * node scripts/fix-local-goose-relay.mjs */ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createDbPool } from '../db.mjs'; import { createLlmProviderService, resolveChatCompletionsUrl, testRelayConnection, } from '../llm-providers.mjs'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); function loadEnvFile(filePath) { if (!fs.existsSync(filePath)) return; 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(); const value = trimmed.slice(eq + 1).trim(); if (!process.env[key]) process.env[key] = value; } } function loadGooseAnduDeepseekProfile() { const profilePath = path.join( os.homedir(), '.config/goose/custom_providers/andu_deepseek.json', ); if (!fs.existsSync(profilePath)) return null; const raw = JSON.parse(fs.readFileSync(profilePath, 'utf8')); const authHeader = raw?.headers?.Authorization ?? ''; const token = authHeader.replace(/^Bearer\s+/i, '').trim(); const baseUrl = String(raw?.base_url ?? '').trim(); if (!token || !baseUrl) return null; const models = (raw?.models ?? []).map((item) => item.name).filter(Boolean); return { name: raw.display_name ?? 'Local Goose DeepSeek', apiUrl: resolveChatCompletionsUrl(baseUrl), apiKey: token, models: models.length ? models : ['deepseek-chat'], defaultModel: raw?.models?.[0]?.name ?? 'deepseek-chat', relayProvider: 'deepseek', }; } loadEnvFile(path.join(root, '.env')); const gooseProfile = loadGooseAnduDeepseekProfile(); const relayUrl = process.env.H5_LOCAL_RELAY_URL ?? gooseProfile?.apiUrl ?? 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions'; const relayToken = process.env.H5_LOCAL_RELAY_TOKEN ?? gooseProfile?.apiKey ?? ''; const relayModel = process.env.H5_LOCAL_RELAY_MODEL ?? gooseProfile?.defaultModel ?? 'deepseek-chat'; const relayModels = process.env.H5_LOCAL_RELAY_MODELS ? process.env.H5_LOCAL_RELAY_MODELS.split(',') .map((item) => item.trim()) .filter(Boolean) : (gooseProfile?.models ?? ['deepseek-chat', 'deepseek-reasoner']); const configName = process.env.H5_LOCAL_RELAY_NAME ?? gooseProfile?.name ?? 'Local Goose DeepSeek'; if (!relayToken) { console.error('缺少 relay token:设置 H5_LOCAL_RELAY_TOKEN 或配置 ~/.config/goose/custom_providers/andu_deepseek.json'); process.exit(1); } const pool = createDbPool(); const svc = createLlmProviderService(pool, { apiTarget: process.env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006', apiSecret: process.env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret', }); const [rows] = await pool.query( 'SELECT id FROM h5_llm_provider_keys WHERE is_selected = 1 LIMIT 1', ); const id = rows[0]?.id; if (!id) { console.error('未找到已启用的 LLM 配置'); process.exit(1); } const updated = await svc.updateKey(id, { name: configName, apiUrl: relayUrl, apiKey: relayToken, models: relayModels, defaultModel: relayModel, relayProvider: 'deepseek', }); if (!updated.ok) { console.error('更新失败:', updated.message); process.exit(1); } const synced = await svc.syncSelectedToGoosed(); if (!synced.ok) { console.error('同步 goosed 失败:', synced.message); process.exit(1); } const test = await testRelayConnection({ apiUrl: relayUrl, apiKey: relayToken, model: relayModel, relayProvider: 'deepseek', }); console.log('LLM 已对接本地 goosed:', process.env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006'); console.log('Relay:', relayUrl); console.log('goosed provider:', synced.providerId, 'model:', synced.model); console.log('连通测试:', test.ok ? `OK (${test.latencyMs}ms)` : test.message); await pool.end(); process.exit(test.ok ? 0 : 1);