#!/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); });