mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
123 lines
4 KiB
TypeScript
123 lines
4 KiB
TypeScript
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import {
|
|
createJsonlKtxLlmDebugRequestRecorder,
|
|
summarizeKtxLlmDebugRequest,
|
|
} from './debug-request-recorder.js';
|
|
|
|
describe('summarizeKtxLlmDebugRequest', () => {
|
|
it('records providerOptions positions without message text or tool schemas', () => {
|
|
const summary = summarizeKtxLlmDebugRequest({
|
|
operationName: 'ingest-bundle-wu',
|
|
source: 'metabase',
|
|
jobId: 'job-1',
|
|
unitKey: 'cards/1',
|
|
modelRole: 'candidateExtraction',
|
|
modelId: 'claude-sonnet-4-6',
|
|
messages: [
|
|
{
|
|
role: 'system',
|
|
content: 'SECRET SYSTEM PROMPT',
|
|
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral', ttl: '1h' } } },
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'SECRET USER PROMPT',
|
|
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral', ttl: '5m' } } },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
tools: {
|
|
emit_candidate: {
|
|
description: 'SECRET TOOL DESCRIPTION',
|
|
inputSchema: { secret: true },
|
|
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral', ttl: '1h' } } },
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(summary).toMatchObject({
|
|
operationName: 'ingest-bundle-wu',
|
|
source: 'metabase',
|
|
jobId: 'job-1',
|
|
unitKey: 'cards/1',
|
|
modelRole: 'candidateExtraction',
|
|
modelId: 'claude-sonnet-4-6',
|
|
messageCount: 2,
|
|
toolNames: ['emit_candidate'],
|
|
providerOptions: [
|
|
{
|
|
target: 'message',
|
|
index: 0,
|
|
role: 'system',
|
|
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral', ttl: '1h' } } },
|
|
},
|
|
{
|
|
target: 'message-part',
|
|
index: 1,
|
|
role: 'user',
|
|
partIndex: 0,
|
|
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral', ttl: '5m' } } },
|
|
},
|
|
{
|
|
target: 'tool',
|
|
name: 'emit_candidate',
|
|
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral', ttl: '1h' } } },
|
|
},
|
|
],
|
|
});
|
|
|
|
const serialized = JSON.stringify(summary);
|
|
expect(serialized).not.toContain('SECRET SYSTEM PROMPT');
|
|
expect(serialized).not.toContain('SECRET USER PROMPT');
|
|
expect(serialized).not.toContain('SECRET TOOL DESCRIPTION');
|
|
expect(serialized).not.toContain('inputSchema');
|
|
});
|
|
});
|
|
|
|
describe('createJsonlKtxLlmDebugRequestRecorder', () => {
|
|
let tempDir: string | undefined;
|
|
|
|
afterEach(async () => {
|
|
if (tempDir) {
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
tempDir = undefined;
|
|
}
|
|
});
|
|
|
|
it('appends one JSON object per recorded request', async () => {
|
|
tempDir = await mkdtemp(join(tmpdir(), 'ktx-llm-debug-'));
|
|
const filePath = join(tempDir, 'nested', 'llm-debug.jsonl');
|
|
const recorder = createJsonlKtxLlmDebugRequestRecorder(filePath);
|
|
|
|
await recorder.record({
|
|
timestamp: '2026-05-04T00:00:00.000Z',
|
|
operationName: 'ingest-bundle-wu',
|
|
modelRole: 'candidateExtraction',
|
|
modelId: 'claude-sonnet-4-6',
|
|
messageCount: 2,
|
|
toolNames: ['emit_candidate'],
|
|
providerOptions: [],
|
|
});
|
|
await recorder.record({
|
|
timestamp: '2026-05-04T00:00:01.000Z',
|
|
operationName: 'ingest-bundle-reconcile',
|
|
modelRole: 'reconcile',
|
|
modelId: 'claude-sonnet-4-6',
|
|
messageCount: 2,
|
|
toolNames: [],
|
|
providerOptions: [],
|
|
});
|
|
|
|
const lines = (await readFile(filePath, 'utf8')).trim().split('\n').map((line) => JSON.parse(line));
|
|
expect(lines).toHaveLength(2);
|
|
expect(lines[0]).toMatchObject({ operationName: 'ingest-bundle-wu', modelRole: 'candidateExtraction' });
|
|
expect(lines[1]).toMatchObject({ operationName: 'ingest-bundle-reconcile', modelRole: 'reconcile' });
|
|
});
|
|
});
|