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>
154 lines
5.3 KiB
JavaScript
154 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Phase 2: verify Finish SSE carries token_state suitable for Portal billing.
|
|
* 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 { normalizeTokenState } from '../billing.mjs';
|
|
import { enrichTokenStateForBilling, resolveBillingTokenState } from '../billing-token-state.mjs';
|
|
import { collectReplyEvents, createV149Client } from './goose-v149-sse.mjs';
|
|
import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs';
|
|
|
|
enforceRealLlmGate('check-goosed-v149-cost-smoke.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'));
|
|
|
|
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 || '';
|
|
|
|
async function upsertProvider() {
|
|
const body = {
|
|
provider_name: providerId,
|
|
api_key: apiKey,
|
|
api_url: process.env.GOOSE_V149_PROVIDER_API_URL
|
|
|| process.env.DEEPSEEK_API_BASE_URL
|
|
|| 'https://api.deepseek.com/v1',
|
|
models: [model],
|
|
relay_provider: 'deepseek',
|
|
};
|
|
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_COST_SMOKE_SKIP: no DEEPSEEK_API_KEY');
|
|
process.exit(0);
|
|
}
|
|
|
|
await upsertProvider();
|
|
const session = await client.apiJson('/agent/start', { working_dir: workingDir });
|
|
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,
|
|
userMessage: {
|
|
role: 'user',
|
|
created: Date.now(),
|
|
content: [{ type: 'text', text: 'Reply with exactly one word: pong' }],
|
|
metadata: { userVisible: true, agentVisible: true, displayText: 'cost smoke ping' },
|
|
},
|
|
});
|
|
|
|
if (result.outcome !== 'finish') {
|
|
throw new Error(`expected Finish, got ${result.outcome}: ${result.errorEvent?.error ?? 'timeout'}`);
|
|
}
|
|
|
|
const rawTokenState = result.finishEvent?.token_state ?? null;
|
|
if (!rawTokenState) {
|
|
throw new Error('Finish event missing token_state');
|
|
}
|
|
|
|
const normalized = normalizeTokenState(rawTokenState);
|
|
const hasTokens =
|
|
normalized.accumulatedInputTokens > 0
|
|
|| normalized.accumulatedOutputTokens > 0
|
|
|| normalized.inputTokens > 0
|
|
|| normalized.outputTokens > 0;
|
|
if (!hasTokens) {
|
|
throw new Error(`Finish token_state has no token counts: ${JSON.stringify(normalized)}`);
|
|
}
|
|
|
|
const billingState = await resolveBillingTokenState(rawTokenState, { sessionId: session.id });
|
|
const enriched = enrichTokenStateForBilling(rawTokenState, {}, {
|
|
...process.env,
|
|
H5_USE_BACKEND_COST: process.env.H5_USE_BACKEND_COST ?? '1',
|
|
H5_COST_ESTIMATE_FROM_TOKENS: process.env.H5_COST_ESTIMATE_FROM_TOKENS ?? '1',
|
|
});
|
|
|
|
const outputDir = path.join(memindRoot, 'docs', 'baselines');
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
const evidencePath = path.join(
|
|
outputDir,
|
|
`goose-v149-cost-evidence-${new Date().toISOString().replace(/[:.]/g, '-')}.json`,
|
|
);
|
|
const evidence = {
|
|
schemaVersion: 'goose-v149-cost-evidence-v1',
|
|
capturedAt: new Date().toISOString(),
|
|
sessionId: session.id,
|
|
requestId,
|
|
finishReason: result.finishEvent?.reason ?? null,
|
|
tokenState: normalized,
|
|
billingState: {
|
|
accumulatedInputTokens: billingState.accumulatedInputTokens,
|
|
accumulatedOutputTokens: billingState.accumulatedOutputTokens,
|
|
accumulatedCost: billingState.accumulatedCost,
|
|
},
|
|
enrichedCost: enriched.accumulatedCost,
|
|
};
|
|
fs.writeFileSync(evidencePath, `${JSON.stringify(evidence, null, 2)}\n`, 'utf8');
|
|
|
|
console.log(`GOOSE_V149_COST_SMOKE_OK: session=${session.id}`);
|
|
console.log(
|
|
` tokens in=${normalized.accumulatedInputTokens} out=${normalized.accumulatedOutputTokens} cost=${billingState.accumulatedCost ?? enriched.accumulatedCost ?? 'null'}`,
|
|
);
|
|
console.log(` evidence=${evidencePath}`);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(`GOOSE_V149_COST_SMOKE_FAIL: ${error.message}`);
|
|
process.exit(1);
|
|
});
|