@townco/debugger 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -8
- package/src/App.tsx +13 -0
- package/src/comparison-db.test.ts +113 -0
- package/src/comparison-db.ts +332 -0
- package/src/components/DebuggerHeader.tsx +62 -2
- package/src/components/SessionTimelineView.tsx +173 -0
- package/src/components/SpanTimeline.tsx +6 -4
- package/src/components/UnifiedTimeline.tsx +691 -0
- package/src/db.ts +71 -0
- package/src/index.ts +2 -0
- package/src/lib/metrics.test.ts +51 -0
- package/src/lib/metrics.ts +136 -0
- package/src/lib/pricing.ts +23 -0
- package/src/lib/turnExtractor.ts +64 -23
- package/src/pages/ComparisonView.tsx +685 -0
- package/src/pages/SessionList.tsx +77 -56
- package/src/pages/SessionView.tsx +3 -64
- package/src/pages/TownHall.tsx +406 -0
- package/src/schemas.ts +15 -0
- package/src/server.ts +345 -12
- package/src/types.ts +87 -0
- package/tsconfig.json +14 -0
package/src/server.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import { resetDb } from "@townco/otlp-server/db";
|
|
1
2
|
import { createOtlpServer } from "@townco/otlp-server/http";
|
|
2
3
|
import { serve } from "bun";
|
|
4
|
+
import { ComparisonDb } from "./comparison-db";
|
|
3
5
|
import { DebuggerDb } from "./db";
|
|
4
6
|
import index from "./index.html";
|
|
7
|
+
import { extractSessionMetrics } from "./lib/metrics";
|
|
5
8
|
import { extractTurnMessages } from "./lib/turnExtractor";
|
|
6
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
AgentConfig,
|
|
11
|
+
ComparisonConfig,
|
|
12
|
+
ConversationTrace,
|
|
13
|
+
Span,
|
|
14
|
+
} from "./types";
|
|
7
15
|
|
|
8
16
|
export const DEFAULT_DEBUGGER_PORT = 4000;
|
|
9
17
|
export const DEFAULT_OTLP_PORT = 4318;
|
|
@@ -13,6 +21,7 @@ export interface DebuggerServerOptions {
|
|
|
13
21
|
otlpPort?: number;
|
|
14
22
|
dbPath: string;
|
|
15
23
|
agentName?: string;
|
|
24
|
+
agentServerUrl?: string;
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
export interface DebuggerServerResult {
|
|
@@ -29,6 +38,7 @@ export function startDebuggerServer(
|
|
|
29
38
|
otlpPort = DEFAULT_OTLP_PORT,
|
|
30
39
|
dbPath,
|
|
31
40
|
agentName = "Agent",
|
|
41
|
+
agentServerUrl = "http://localhost:3100",
|
|
32
42
|
} = options;
|
|
33
43
|
|
|
34
44
|
// Start OTLP server (initializes database internally)
|
|
@@ -41,6 +51,59 @@ export function startDebuggerServer(
|
|
|
41
51
|
// Create debugger database connection for reading
|
|
42
52
|
const db = new DebuggerDb(dbPath);
|
|
43
53
|
|
|
54
|
+
// Create comparison database for Town Hall feature
|
|
55
|
+
const comparisonDbPath = dbPath.replace(/\.db$/, "-comparison.db");
|
|
56
|
+
const comparisonDb = new ComparisonDb(comparisonDbPath);
|
|
57
|
+
|
|
58
|
+
// Helper to fetch agent config from agent server
|
|
59
|
+
async function fetchAgentConfig(): Promise<AgentConfig | null> {
|
|
60
|
+
try {
|
|
61
|
+
// Call agent's initialize RPC to get config
|
|
62
|
+
const response = await fetch(`${agentServerUrl}/rpc`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
jsonrpc: "2.0",
|
|
67
|
+
id: "debugger-config",
|
|
68
|
+
method: "initialize",
|
|
69
|
+
params: {
|
|
70
|
+
protocolVersion: 1, // ACP protocol version as number
|
|
71
|
+
clientCapabilities: {},
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
console.error("Failed to fetch agent config:", response.statusText);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const data = await response.json();
|
|
82
|
+
|
|
83
|
+
// Check for JSON-RPC error
|
|
84
|
+
if (data.error) {
|
|
85
|
+
console.error("Agent RPC error:", data.error);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result = data.result;
|
|
90
|
+
if (!result) {
|
|
91
|
+
console.error("No result in agent response");
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Extract config from initialize response
|
|
96
|
+
return {
|
|
97
|
+
model: result._meta?.model || "unknown",
|
|
98
|
+
systemPrompt: result._meta?.systemPrompt || null,
|
|
99
|
+
tools: result._meta?.tools || [],
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error("Error fetching agent config:", error);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
44
107
|
// Start debugger UI server
|
|
45
108
|
const server = serve({
|
|
46
109
|
port,
|
|
@@ -51,6 +114,21 @@ export function startDebuggerServer(
|
|
|
51
114
|
},
|
|
52
115
|
},
|
|
53
116
|
|
|
117
|
+
"/api/reset-database": {
|
|
118
|
+
POST() {
|
|
119
|
+
try {
|
|
120
|
+
resetDb();
|
|
121
|
+
return new Response("Database reset successfully", { status: 200 });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("Error resetting database:", error);
|
|
124
|
+
return new Response(
|
|
125
|
+
`Failed to reset database: ${error instanceof Error ? error.message : String(error)}`,
|
|
126
|
+
{ status: 500 },
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
|
|
54
132
|
"/api/sessions": {
|
|
55
133
|
GET(req) {
|
|
56
134
|
const url = new URL(req.url);
|
|
@@ -96,22 +174,277 @@ export function startDebuggerServer(
|
|
|
96
174
|
);
|
|
97
175
|
}
|
|
98
176
|
|
|
99
|
-
//
|
|
100
|
-
const
|
|
177
|
+
// Query traces by session attribute to avoid race conditions
|
|
178
|
+
const traceIds = db.getTraceIdsBySessionAttribute(sessionId);
|
|
101
179
|
|
|
102
180
|
// Extract messages for each trace
|
|
103
|
-
const conversation: ConversationTrace[] =
|
|
181
|
+
const conversation: ConversationTrace[] = traceIds.map(
|
|
182
|
+
(traceInfo) => {
|
|
183
|
+
const data = db.getTraceById(traceInfo.trace_id);
|
|
184
|
+
const messages = extractTurnMessages(data.spans, data.logs);
|
|
185
|
+
return {
|
|
186
|
+
trace_id: traceInfo.trace_id,
|
|
187
|
+
start_time_unix_nano: traceInfo.start_time_unix_nano,
|
|
188
|
+
userInput: messages.userInput,
|
|
189
|
+
llmOutput: messages.llmOutput,
|
|
190
|
+
agentMessages: messages.agentMessages,
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return Response.json(conversation);
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
// Town Hall API endpoints
|
|
200
|
+
|
|
201
|
+
"/api/agent-config": {
|
|
202
|
+
async GET() {
|
|
203
|
+
const config = await fetchAgentConfig();
|
|
204
|
+
if (!config) {
|
|
205
|
+
return Response.json(
|
|
206
|
+
{ error: "Failed to fetch agent config" },
|
|
207
|
+
{ status: 503 },
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return Response.json(config);
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
"/api/available-models": {
|
|
215
|
+
GET() {
|
|
216
|
+
// List of supported models for comparison
|
|
217
|
+
const models = [
|
|
218
|
+
// Anthropic models
|
|
219
|
+
"claude-sonnet-4-5-20250929",
|
|
220
|
+
"claude-3-5-haiku-20241022",
|
|
221
|
+
"claude-opus-4-5-20251101",
|
|
222
|
+
// Google Gemini models
|
|
223
|
+
"gemini-2.0-flash",
|
|
224
|
+
"gemini-1.5-pro",
|
|
225
|
+
"gemini-1.5-flash",
|
|
226
|
+
];
|
|
227
|
+
return Response.json({ models });
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
"/api/session-first-message/:sessionId": {
|
|
232
|
+
GET(req) {
|
|
233
|
+
const sessionId = req.params.sessionId;
|
|
234
|
+
|
|
235
|
+
// Query logs directly by session attribute to avoid race conditions
|
|
236
|
+
// with trace.session_id association during concurrent sessions
|
|
237
|
+
const message = db.getFirstUserMessageBySession(sessionId);
|
|
238
|
+
|
|
239
|
+
if (!message) {
|
|
240
|
+
return Response.json(
|
|
241
|
+
{ error: "Session not found or has no user message" },
|
|
242
|
+
{ status: 404 },
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return Response.json({ message });
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
"/api/comparison-config": {
|
|
251
|
+
GET() {
|
|
252
|
+
const config = comparisonDb.getLatestConfig();
|
|
253
|
+
return Response.json(config);
|
|
254
|
+
},
|
|
255
|
+
async POST(req) {
|
|
256
|
+
try {
|
|
257
|
+
const body = await req.json();
|
|
258
|
+
const config: ComparisonConfig = {
|
|
259
|
+
id: body.id || crypto.randomUUID(),
|
|
260
|
+
dimension: body.dimension,
|
|
261
|
+
controlModel: body.controlModel,
|
|
262
|
+
variantModel: body.variantModel,
|
|
263
|
+
variantSystemPrompt: body.variantSystemPrompt,
|
|
264
|
+
variantTools: body.variantTools,
|
|
265
|
+
createdAt: body.createdAt || new Date().toISOString(),
|
|
266
|
+
updatedAt: new Date().toISOString(),
|
|
267
|
+
};
|
|
268
|
+
comparisonDb.saveConfig(config);
|
|
269
|
+
return Response.json({ id: config.id });
|
|
270
|
+
} catch (error) {
|
|
271
|
+
return Response.json(
|
|
272
|
+
{ error: "Invalid request body" },
|
|
273
|
+
{ status: 400 },
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
"/api/comparison-session-ids": {
|
|
280
|
+
GET() {
|
|
281
|
+
const sessionIds = comparisonDb.getComparisonSessionIds();
|
|
282
|
+
return Response.json({ sessionIds });
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
"/api/comparison-runs": {
|
|
287
|
+
GET(req) {
|
|
288
|
+
const url = new URL(req.url);
|
|
289
|
+
const limit = Number.parseInt(url.searchParams.get("limit") || "50");
|
|
290
|
+
const offset = Number.parseInt(url.searchParams.get("offset") || "0");
|
|
291
|
+
const sourceSessionId = url.searchParams.get("sourceSessionId");
|
|
292
|
+
|
|
293
|
+
if (sourceSessionId) {
|
|
294
|
+
const runs = comparisonDb.listRunsBySourceSession(sourceSessionId);
|
|
295
|
+
return Response.json(runs);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const runs = comparisonDb.listRuns(limit, offset);
|
|
299
|
+
return Response.json(runs);
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
"/api/comparison-run/:runId": {
|
|
304
|
+
GET(req) {
|
|
305
|
+
const runId = req.params.runId;
|
|
306
|
+
const run = comparisonDb.getRun(runId);
|
|
307
|
+
if (!run) {
|
|
308
|
+
return Response.json(
|
|
309
|
+
{ error: "Comparison run not found" },
|
|
310
|
+
{ status: 404 },
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
return Response.json(run);
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
"/api/run-comparison": {
|
|
318
|
+
async POST(req) {
|
|
319
|
+
try {
|
|
320
|
+
const body = await req.json();
|
|
321
|
+
const { sessionId, configId } = body;
|
|
322
|
+
|
|
323
|
+
if (!sessionId || !configId) {
|
|
324
|
+
return Response.json(
|
|
325
|
+
{ error: "sessionId and configId are required" },
|
|
326
|
+
{ status: 400 },
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Get the comparison config
|
|
331
|
+
const config = comparisonDb.getConfig(configId);
|
|
332
|
+
if (!config) {
|
|
333
|
+
return Response.json(
|
|
334
|
+
{ error: "Comparison config not found" },
|
|
335
|
+
{ status: 404 },
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Get the first user message from the source session
|
|
340
|
+
const traces = db.listTraces(1, 0, sessionId);
|
|
341
|
+
if (traces.length === 0) {
|
|
342
|
+
return Response.json(
|
|
343
|
+
{ error: "Source session not found" },
|
|
344
|
+
{ status: 404 },
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const trace = traces[0];
|
|
349
|
+
if (!trace) {
|
|
350
|
+
return Response.json(
|
|
351
|
+
{ error: "Source session not found" },
|
|
352
|
+
{ status: 404 },
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
104
356
|
const data = db.getTraceById(trace.trace_id);
|
|
105
357
|
const messages = extractTurnMessages(data.spans, data.logs);
|
|
106
|
-
return {
|
|
107
|
-
trace_id: trace.trace_id,
|
|
108
|
-
start_time_unix_nano: trace.start_time_unix_nano,
|
|
109
|
-
userInput: messages.userInput,
|
|
110
|
-
llmOutput: messages.llmOutput,
|
|
111
|
-
};
|
|
112
|
-
});
|
|
113
358
|
|
|
114
|
-
|
|
359
|
+
if (!messages.userInput) {
|
|
360
|
+
return Response.json(
|
|
361
|
+
{ error: "No user message found in source session" },
|
|
362
|
+
{ status: 400 },
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Create the comparison run
|
|
367
|
+
const run = comparisonDb.createRun(
|
|
368
|
+
configId,
|
|
369
|
+
sessionId,
|
|
370
|
+
messages.userInput,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// Return the run info - actual execution will be handled by the frontend
|
|
374
|
+
// which will create two ACP sessions and run them in parallel
|
|
375
|
+
return Response.json({
|
|
376
|
+
runId: run.id,
|
|
377
|
+
firstUserMessage: run.firstUserMessage,
|
|
378
|
+
config,
|
|
379
|
+
});
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error("Error starting comparison:", error);
|
|
382
|
+
return Response.json(
|
|
383
|
+
{ error: "Failed to start comparison" },
|
|
384
|
+
{ status: 500 },
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
"/api/comparison-run/:runId/update": {
|
|
391
|
+
async POST(req) {
|
|
392
|
+
try {
|
|
393
|
+
const runId = req.params.runId;
|
|
394
|
+
const body = await req.json();
|
|
395
|
+
const {
|
|
396
|
+
status,
|
|
397
|
+
controlSessionId,
|
|
398
|
+
variantSessionId,
|
|
399
|
+
controlMetrics,
|
|
400
|
+
variantMetrics,
|
|
401
|
+
controlResponse,
|
|
402
|
+
variantResponse,
|
|
403
|
+
} = body;
|
|
404
|
+
|
|
405
|
+
comparisonDb.updateRunStatus(runId, status, {
|
|
406
|
+
controlSessionId,
|
|
407
|
+
variantSessionId,
|
|
408
|
+
controlMetrics,
|
|
409
|
+
variantMetrics,
|
|
410
|
+
controlResponse,
|
|
411
|
+
variantResponse,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
return Response.json({ success: true });
|
|
415
|
+
} catch (error) {
|
|
416
|
+
return Response.json(
|
|
417
|
+
{ error: "Failed to update comparison run" },
|
|
418
|
+
{ status: 500 },
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
"/api/session-metrics/:sessionId": {
|
|
425
|
+
async GET(req) {
|
|
426
|
+
const sessionId = req.params.sessionId;
|
|
427
|
+
const url = new URL(req.url);
|
|
428
|
+
const model = url.searchParams.get("model") || "unknown";
|
|
429
|
+
|
|
430
|
+
// Query spans by their agent.session_id attribute directly
|
|
431
|
+
// This is more reliable than trace-based lookup because concurrent
|
|
432
|
+
// sessions can cause race conditions in trace association
|
|
433
|
+
const allSpans = db.getSpansBySessionAttribute(sessionId);
|
|
434
|
+
|
|
435
|
+
if (allSpans.length === 0) {
|
|
436
|
+
return Response.json(
|
|
437
|
+
{ error: "Session not found or has no traces" },
|
|
438
|
+
{ status: 404 },
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Get traces for duration calculation (use empty array if not found)
|
|
443
|
+
const traces = db.listTraces(100, 0, sessionId);
|
|
444
|
+
|
|
445
|
+
// Extract metrics
|
|
446
|
+
const metrics = extractSessionMetrics(traces, allSpans, model);
|
|
447
|
+
return Response.json(metrics);
|
|
115
448
|
},
|
|
116
449
|
},
|
|
117
450
|
|
package/src/types.ts
CHANGED
|
@@ -48,10 +48,19 @@ export interface TraceDetailRaw {
|
|
|
48
48
|
logs: Log[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface AgentMessage {
|
|
52
|
+
content: string;
|
|
53
|
+
spanId: string;
|
|
54
|
+
timestamp: number; // end_time_unix_nano of the chat span
|
|
55
|
+
type: "chat" | "tool_call";
|
|
56
|
+
toolName?: string; // Only for tool_call type
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
export interface TraceDetail extends TraceDetailRaw {
|
|
52
60
|
messages: {
|
|
53
61
|
userInput: string | null;
|
|
54
62
|
llmOutput: string | null;
|
|
63
|
+
agentMessages: AgentMessage[];
|
|
55
64
|
};
|
|
56
65
|
}
|
|
57
66
|
|
|
@@ -60,6 +69,7 @@ export interface ConversationTrace {
|
|
|
60
69
|
start_time_unix_nano: number;
|
|
61
70
|
userInput: string | null;
|
|
62
71
|
llmOutput: string | null;
|
|
72
|
+
agentMessages: AgentMessage[];
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
export interface Session {
|
|
@@ -68,3 +78,80 @@ export interface Session {
|
|
|
68
78
|
first_trace_time: number;
|
|
69
79
|
last_trace_time: number;
|
|
70
80
|
}
|
|
81
|
+
|
|
82
|
+
// Town Hall comparison types
|
|
83
|
+
|
|
84
|
+
export type ComparisonDimension = "model" | "system_prompt" | "tools";
|
|
85
|
+
|
|
86
|
+
export interface ComparisonConfig {
|
|
87
|
+
id: string;
|
|
88
|
+
dimension: ComparisonDimension;
|
|
89
|
+
controlModel?: string | undefined; // Original model for comparison
|
|
90
|
+
variantModel?: string | undefined;
|
|
91
|
+
variantSystemPrompt?: string | undefined;
|
|
92
|
+
variantTools?: string[] | undefined; // JSON array of tool names
|
|
93
|
+
createdAt: string;
|
|
94
|
+
updatedAt: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ComparisonConfigRow {
|
|
98
|
+
id: string;
|
|
99
|
+
dimension: string;
|
|
100
|
+
control_model: string | null;
|
|
101
|
+
variant_model: string | null;
|
|
102
|
+
variant_system_prompt: string | null;
|
|
103
|
+
variant_tools: string | null; // JSON string
|
|
104
|
+
created_at: string;
|
|
105
|
+
updated_at: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface SessionMetrics {
|
|
109
|
+
durationMs: number;
|
|
110
|
+
inputTokens: number;
|
|
111
|
+
outputTokens: number;
|
|
112
|
+
totalTokens: number;
|
|
113
|
+
estimatedCost: number;
|
|
114
|
+
toolCallCount: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ComparisonRun {
|
|
118
|
+
id: string;
|
|
119
|
+
configId: string;
|
|
120
|
+
sourceSessionId: string;
|
|
121
|
+
firstUserMessage: string;
|
|
122
|
+
startMessageIndex: number;
|
|
123
|
+
turnCount: number;
|
|
124
|
+
controlSessionId: string | null;
|
|
125
|
+
variantSessionId: string | null;
|
|
126
|
+
status: "pending" | "running" | "completed" | "failed";
|
|
127
|
+
startedAt: string;
|
|
128
|
+
completedAt: string | null;
|
|
129
|
+
controlMetrics: SessionMetrics | null;
|
|
130
|
+
variantMetrics: SessionMetrics | null;
|
|
131
|
+
controlResponse: string | null;
|
|
132
|
+
variantResponse: string | null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface ComparisonRunRow {
|
|
136
|
+
id: string;
|
|
137
|
+
config_id: string;
|
|
138
|
+
source_session_id: string;
|
|
139
|
+
first_user_message: string;
|
|
140
|
+
start_message_index: number;
|
|
141
|
+
turn_count: number;
|
|
142
|
+
control_session_id: string | null;
|
|
143
|
+
variant_session_id: string | null;
|
|
144
|
+
status: string;
|
|
145
|
+
started_at: string;
|
|
146
|
+
completed_at: string | null;
|
|
147
|
+
control_metrics: string | null; // JSON string
|
|
148
|
+
variant_metrics: string | null; // JSON string
|
|
149
|
+
control_response: string | null;
|
|
150
|
+
variant_response: string | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface AgentConfig {
|
|
154
|
+
model: string;
|
|
155
|
+
systemPrompt: string | null;
|
|
156
|
+
tools: Array<{ name: string; description?: string }>;
|
|
157
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@townco/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"composite": false,
|
|
5
|
+
"declaration": false,
|
|
6
|
+
"baseUrl": ".",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"paths": {
|
|
9
|
+
"@/*": ["./src/*"]
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"],
|
|
13
|
+
"exclude": ["dist", "node_modules"]
|
|
14
|
+
}
|