Files
memind/scripts/check-goosed-v149-deepseek-tools.mjs
T
john 67220a14ea fix(goose-v149): block unattended real LLM smokes by default
Prevent phase2/phase3/all reruns from burning DashScope tokens unless
GOOSE_V149_ALLOW_REAL_LLM=1 is explicitly set with human approval.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 22:08:39 +08:00

219 lines
6.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Phase 2: DeepSeek provider completes a tool round (todo_write) on v1.49.
* Requires DEEPSEEK_API_KEY (or GOOSE_V149_TEST_API_KEY).
*/
import fs from 'node:fs';
import path from 'node:path';
import { randomUUID } from 'node:crypto';
import { fileURLToPath } from 'node:url';
import { collectReplyEvents, createV149Client } from './goose-v149-sse.mjs';
import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs';
enforceRealLlmGate('check-goosed-v149-deepseek-tools.mjs');
const memindRoot = 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();
let value = trimmed.slice(eq + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"'))
|| (value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
if (!process.env[key]) process.env[key] = value;
}
}
loadEnvFile(path.join(memindRoot, '.env'));
loadEnvFile(path.join(memindRoot, '.env.local'));
const client = createV149Client();
const workingDir = process.env.GOOSE_V149_WORKING_DIR || memindRoot;
const providerId = process.env.GOOSE_V149_PROVIDER_ID || 'custom_tkmind_relay_deepseek';
const model = process.env.GOOSE_V149_PROVIDER_MODEL || 'deepseek-chat';
const apiKey = process.env.DEEPSEEK_API_KEY || process.env.GOOSE_V149_TEST_API_KEY || '';
const timeoutMs = Number(process.env.GOOSE_V149_DEEPSEEK_TOOLS_TIMEOUT_MS || 120_000);
function contentBlocks(event) {
if (event?.type === 'Message') return event.message?.content ?? [];
if (event?.type === 'UpdateConversation') {
const conversation = event.conversation ?? [];
return conversation.flatMap((message) => message?.content ?? []);
}
return [];
}
function blockLooksLikeTool(block = {}) {
const type = String(block.type ?? block.content_type ?? '').toLowerCase();
if (type === 'toolrequest' || type === 'toolresponse') return true;
if (type.includes('tool')) return true;
if (block.tool_call || block.toolCall || block.tool_request || block.toolRequest) return true;
if (block.name && (block.arguments || block.input)) return true;
return false;
}
function resolveToolName(block = {}) {
const direct =
block.tool_call?.name
?? block.toolCall?.name
?? block.name
?? block.tool_request?.tool_call?.name
?? block.toolRequest?.tool_call?.name;
if (direct) return String(direct);
const nested =
block.toolCall?.value?.name
?? block.tool_call?.value?.name
?? block.toolRequest?.tool_call?.value?.name
?? block.tool_request?.tool_call?.value?.name;
return nested ? String(nested) : null;
}
function toolNamesFromEvents(events = []) {
const names = new Set();
for (const event of events) {
for (const block of contentBlocks(event)) {
if (!blockLooksLikeTool(block)) continue;
const name = resolveToolName(block);
if (name) names.add(name);
}
}
return [...names];
}
async function upsertProvider() {
const body = {
engine: 'openai',
display_name: 'tkmind_relay_deepseek',
api_url:
process.env.GOOSE_V149_PROVIDER_API_URL
|| process.env.DEEPSEEK_API_BASE_URL
|| 'https://api.deepseek.com/v1',
api_key: apiKey,
models: [model, 'deepseek-reasoner'],
supports_streaming: true,
requires_auth: true,
preserves_thinking: false,
};
let response = await client.apiFetch(
`/config/custom-providers/${encodeURIComponent(providerId)}`,
{ method: 'PUT', body: JSON.stringify(body) },
);
if (!response.ok && (response.status === 404 || /not found/i.test(await response.text()))) {
await client.apiJson('/config/custom-providers', body);
}
}
async function main() {
if (!apiKey) {
console.log('GOOSE_V149_DEEPSEEK_TOOLS_SKIP: no DEEPSEEK_API_KEY');
process.exit(0);
}
await upsertProvider();
const session = await client.apiJson('/agent/start', {
working_dir: workingDir,
extension_overrides: [
{
type: 'platform',
name: 'todo',
description: 'todo list for tool-round smoke',
display_name: 'Todo',
bundled: false,
available_tools: ['todo_write'],
},
{
type: 'platform',
name: 'skills',
description: 'skills',
available_tools: [],
},
],
});
if (!session?.id) throw new Error('missing session id');
await client.apiJson('/agent/update_provider', {
session_id: session.id,
provider: providerId,
model,
});
const requestId = randomUUID();
const result = await collectReplyEvents({
client,
sessionId: session.id,
requestId,
timeoutMs,
userMessage: {
role: 'user',
created: Date.now(),
content: [{
type: 'text',
text:
'You must call the todo_write tool exactly once with content "- [ ] goose v149 deepseek tool smoke". '
+ 'After the tool succeeds, reply with exactly one word: done',
}],
metadata: {
userVisible: true,
agentVisible: true,
displayText: 'deepseek tool smoke',
},
},
});
if (result.outcome !== 'finish') {
throw new Error(
`expected Finish, got ${result.outcome}: ${result.errorEvent?.error ?? 'timeout'}`,
);
}
const toolNames = toolNamesFromEvents(result.events);
const hasTodoTool = toolNames.some((name) => /todo_write/i.test(name));
if (!hasTodoTool) {
throw new Error(
`no todo_write tool activity in SSE events; tools=${toolNames.join(',') || 'none'}`,
);
}
const outputDir = path.join(memindRoot, 'docs', 'baselines');
fs.mkdirSync(outputDir, { recursive: true });
const evidencePath = path.join(
outputDir,
`goose-v149-deepseek-tools-evidence-${new Date().toISOString().replace(/[:.]/g, '-')}.json`,
);
fs.writeFileSync(
evidencePath,
`${JSON.stringify({
schemaVersion: 'goose-v149-deepseek-tools-v1',
capturedAt: new Date().toISOString(),
sessionId: session.id,
requestId,
providerId,
model,
toolNames,
finishReason: result.finishEvent?.reason ?? null,
eventTypes: result.events.map((event) => event.type),
}, null, 2)}\n`,
'utf8',
);
console.log(`GOOSE_V149_DEEPSEEK_TOOLS_OK: session=${session.id} tools=${toolNames.join(',')}`);
console.log(` evidence=${evidencePath}`);
}
main().catch((error) => {
console.error(`GOOSE_V149_DEEPSEEK_TOOLS_FAIL: ${error.message}`);
process.exit(1);
});