67220a14ea
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>
219 lines
6.6 KiB
JavaScript
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);
|
|
});
|