@anvia/studio 0.2.0 → 0.2.1

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/dist/index.d.ts CHANGED
@@ -300,6 +300,7 @@ type StudioStores = {
300
300
  sessions?: StudioSessionStore | false;
301
301
  traces?: StudioTraceStore;
302
302
  pipelineLogs?: StudioPipelineLogStore | false;
303
+ pipelineRuns?: StudioPipelineRunStore | false;
303
304
  };
304
305
  type StudioUiOptions = {
305
306
  path?: string;
@@ -444,6 +445,39 @@ type StudioPipelineFinalEvent = {
444
445
  pipelineId: string;
445
446
  output: JsonValue;
446
447
  };
448
+ type StudioPipelineRunStatus = "running" | "success" | "error";
449
+ type StudioPipelineRunRecord = {
450
+ runId: string;
451
+ pipelineId: string;
452
+ status: StudioPipelineRunStatus;
453
+ input: JsonValue;
454
+ output?: JsonValue;
455
+ error?: JsonValue;
456
+ metadata?: JsonObject;
457
+ startedAt: string;
458
+ endedAt?: string;
459
+ durationMs?: number;
460
+ };
461
+ type StudioPipelineRunSaveInput = {
462
+ runId: string;
463
+ pipelineId: string;
464
+ status: StudioPipelineRunStatus;
465
+ input: JsonValue;
466
+ output?: JsonValue;
467
+ error?: JsonValue;
468
+ metadata?: JsonObject;
469
+ startedAt: string;
470
+ endedAt?: string;
471
+ durationMs?: number;
472
+ };
473
+ type StudioPipelineRunListOptions = {
474
+ pipelineId: string;
475
+ limit: number;
476
+ };
477
+ type StudioPipelineRunStore = {
478
+ savePipelineRun(input: StudioPipelineRunSaveInput): StudioPipelineRunRecord | Promise<StudioPipelineRunRecord>;
479
+ listPipelineRuns(options: StudioPipelineRunListOptions): StudioPipelineRunRecord[] | Promise<StudioPipelineRunRecord[]>;
480
+ };
447
481
  type StudioPipelineRunRequest = {
448
482
  input: JsonValue;
449
483
  stream?: boolean;
@@ -508,6 +542,6 @@ declare class Studio implements AnviaStudio {
508
542
  type SqliteSessionStoreOptions = {
509
543
  path?: string;
510
544
  };
511
- declare function createSqliteSessionStore(options?: SqliteSessionStoreOptions): StudioSessionStore & StudioTraceStore & StudioPipelineLogStore;
545
+ declare function createSqliteSessionStore(options?: SqliteSessionStoreOptions): StudioSessionStore & StudioTraceStore & StudioPipelineLogStore & StudioPipelineRunStore;
512
546
 
513
- export { type AgentRunRequest, type AgentRunResponse, type AgentRunStreamEvent, type AnviaStudio, type SqliteSessionStoreOptions, Studio, type StudioAgent, type StudioAgentConfig, type StudioAgentKnowledgeConfig, type StudioAgentMcpServerMetadata, type StudioAgentMcpToolMetadata, type StudioAgentMcpsSummary, type StudioAgentToolApprovalMetadata, type StudioAgentToolMetadata, type StudioAgentToolSource, type StudioAgentToolsSummary, type StudioCapability, type StudioCapabilityConfig, type StudioConfig, type StudioErrorCode, type StudioErrorResponse, type StudioKnowledgeEvidence, type StudioKnowledgeEvidenceDocument, type StudioKnowledgeSourceKind, type StudioKnowledgeSourceSummary, type StudioKnowledgeSummary, type StudioOptions, type StudioPipeline, type StudioPipelineConfig, type StudioPipelineDetail, type StudioPipelineFinalEvent, type StudioPipelineLogAppendInput, type StudioPipelineLogCategory, type StudioPipelineLogEntry, type StudioPipelineLogEvent, type StudioPipelineLogLevel, type StudioPipelineLogListOptions, type StudioPipelineLogStore, type StudioPipelineRunRequest, type StudioPipelineRunResponse, type StudioServeOptions, type StudioSession, type StudioSessionCreateInput, type StudioSessionListOptions, type StudioSessionLogAppendInput, type StudioSessionLogCategory, type StudioSessionLogEntry, type StudioSessionLogEvent, type StudioSessionLogLevel, type StudioSessionLogListOptions, type StudioSessionRunStatus, type StudioSessionRunTranscriptInput, type StudioSessionStore, type StudioSessionSummary, type StudioSessionTraceListOptions, type StudioStaticKnowledgeDocument, type StudioStores, type StudioTarget, type StudioToolApproval, type StudioToolApprovalDecision, type StudioToolApprovalRequestEvent, type StudioToolApprovalResultEvent, type StudioToolApprovalStatus, type StudioToolApprovalTranscript, type StudioToolQuestion, type StudioToolQuestionAnswer, type StudioToolQuestionChoice, type StudioToolQuestionPrompt, type StudioToolQuestionRequestEvent, type StudioToolQuestionResultEvent, type StudioToolQuestionStatus, type StudioToolQuestionTranscript, type StudioTrace, type StudioTraceListOptions, type StudioTraceObservation, type StudioTraceObservationKind, StudioTraceObserver, type StudioTraceObserverOptions, type StudioTraceStatus, type StudioTraceStore, type StudioTraceSummary, type StudioTranscriptChatEntry, type StudioTranscriptChildAgentEvent, type StudioTranscriptEntry, type StudioTranscriptReasoningEntry, type StudioTranscriptToolEntry, type StudioUiOptions, createSqliteSessionStore };
547
+ export { type AgentRunRequest, type AgentRunResponse, type AgentRunStreamEvent, type AnviaStudio, type SqliteSessionStoreOptions, Studio, type StudioAgent, type StudioAgentConfig, type StudioAgentKnowledgeConfig, type StudioAgentMcpServerMetadata, type StudioAgentMcpToolMetadata, type StudioAgentMcpsSummary, type StudioAgentToolApprovalMetadata, type StudioAgentToolMetadata, type StudioAgentToolSource, type StudioAgentToolsSummary, type StudioCapability, type StudioCapabilityConfig, type StudioConfig, type StudioErrorCode, type StudioErrorResponse, type StudioKnowledgeEvidence, type StudioKnowledgeEvidenceDocument, type StudioKnowledgeSourceKind, type StudioKnowledgeSourceSummary, type StudioKnowledgeSummary, type StudioOptions, type StudioPipeline, type StudioPipelineConfig, type StudioPipelineDetail, type StudioPipelineFinalEvent, type StudioPipelineLogAppendInput, type StudioPipelineLogCategory, type StudioPipelineLogEntry, type StudioPipelineLogEvent, type StudioPipelineLogLevel, type StudioPipelineLogListOptions, type StudioPipelineLogStore, type StudioPipelineRunListOptions, type StudioPipelineRunRecord, type StudioPipelineRunRequest, type StudioPipelineRunResponse, type StudioPipelineRunSaveInput, type StudioPipelineRunStatus, type StudioPipelineRunStore, type StudioServeOptions, type StudioSession, type StudioSessionCreateInput, type StudioSessionListOptions, type StudioSessionLogAppendInput, type StudioSessionLogCategory, type StudioSessionLogEntry, type StudioSessionLogEvent, type StudioSessionLogLevel, type StudioSessionLogListOptions, type StudioSessionRunStatus, type StudioSessionRunTranscriptInput, type StudioSessionStore, type StudioSessionSummary, type StudioSessionTraceListOptions, type StudioStaticKnowledgeDocument, type StudioStores, type StudioTarget, type StudioToolApproval, type StudioToolApprovalDecision, type StudioToolApprovalRequestEvent, type StudioToolApprovalResultEvent, type StudioToolApprovalStatus, type StudioToolApprovalTranscript, type StudioToolQuestion, type StudioToolQuestionAnswer, type StudioToolQuestionChoice, type StudioToolQuestionPrompt, type StudioToolQuestionRequestEvent, type StudioToolQuestionResultEvent, type StudioToolQuestionStatus, type StudioToolQuestionTranscript, type StudioTrace, type StudioTraceListOptions, type StudioTraceObservation, type StudioTraceObservationKind, StudioTraceObserver, type StudioTraceObserverOptions, type StudioTraceStatus, type StudioTraceStore, type StudioTraceSummary, type StudioTranscriptChatEntry, type StudioTranscriptChildAgentEvent, type StudioTranscriptEntry, type StudioTranscriptReasoningEntry, type StudioTranscriptToolEntry, type StudioUiOptions, createSqliteSessionStore };
package/dist/index.js CHANGED
@@ -1056,6 +1056,82 @@ var SqliteSessionStore = class {
1056
1056
  });
1057
1057
  return rows.map(toPipelineLog);
1058
1058
  }
1059
+ savePipelineRun(input) {
1060
+ const db = this.database();
1061
+ db.prepare(
1062
+ `INSERT INTO runner_pipeline_runs (
1063
+ run_id,
1064
+ pipeline_id,
1065
+ status,
1066
+ input_json,
1067
+ output_json,
1068
+ error_json,
1069
+ metadata_json,
1070
+ started_at,
1071
+ ended_at,
1072
+ duration_ms
1073
+ ) VALUES (
1074
+ $runId,
1075
+ $pipelineId,
1076
+ $status,
1077
+ $input,
1078
+ $output,
1079
+ $error,
1080
+ $metadata,
1081
+ $startedAt,
1082
+ $endedAt,
1083
+ $durationMs
1084
+ )
1085
+ ON CONFLICT(run_id) DO UPDATE SET
1086
+ pipeline_id = excluded.pipeline_id,
1087
+ status = excluded.status,
1088
+ input_json = excluded.input_json,
1089
+ output_json = excluded.output_json,
1090
+ error_json = excluded.error_json,
1091
+ metadata_json = excluded.metadata_json,
1092
+ started_at = excluded.started_at,
1093
+ ended_at = excluded.ended_at,
1094
+ duration_ms = excluded.duration_ms`
1095
+ ).run({
1096
+ $runId: input.runId,
1097
+ $pipelineId: input.pipelineId,
1098
+ $status: input.status,
1099
+ $input: JSON.stringify(input.input),
1100
+ $output: input.output === void 0 ? null : JSON.stringify(input.output),
1101
+ $error: input.error === void 0 ? null : JSON.stringify(input.error),
1102
+ $metadata: input.metadata === void 0 ? null : JSON.stringify(input.metadata),
1103
+ $startedAt: input.startedAt,
1104
+ $endedAt: input.endedAt ?? null,
1105
+ $durationMs: input.durationMs ?? null
1106
+ });
1107
+ return {
1108
+ runId: input.runId,
1109
+ pipelineId: input.pipelineId,
1110
+ status: input.status,
1111
+ input: input.input,
1112
+ ...input.output === void 0 ? {} : { output: input.output },
1113
+ ...input.error === void 0 ? {} : { error: input.error },
1114
+ ...input.metadata === void 0 ? {} : { metadata: input.metadata },
1115
+ startedAt: input.startedAt,
1116
+ ...input.endedAt === void 0 ? {} : { endedAt: input.endedAt },
1117
+ ...input.durationMs === void 0 ? {} : { durationMs: input.durationMs }
1118
+ };
1119
+ }
1120
+ listPipelineRuns(options) {
1121
+ const db = this.database();
1122
+ const rows = db.prepare(
1123
+ `SELECT run_id, pipeline_id, status, input_json, output_json, error_json,
1124
+ metadata_json, started_at, ended_at, duration_ms
1125
+ FROM runner_pipeline_runs
1126
+ WHERE pipeline_id = $pipelineId
1127
+ ORDER BY started_at DESC
1128
+ LIMIT $limit`
1129
+ ).all({
1130
+ $pipelineId: options.pipelineId,
1131
+ $limit: options.limit
1132
+ });
1133
+ return rows.map(toPipelineRun);
1134
+ }
1059
1135
  deleteSession(id) {
1060
1136
  const db = this.database();
1061
1137
  try {
@@ -1290,6 +1366,20 @@ var SqliteSessionStore = class {
1290
1366
  ) STRICT;
1291
1367
  CREATE INDEX IF NOT EXISTS runner_pipeline_logs_pipeline_sequence_idx
1292
1368
  ON runner_pipeline_logs(pipeline_id, sequence ASC);
1369
+ CREATE TABLE IF NOT EXISTS runner_pipeline_runs (
1370
+ run_id TEXT PRIMARY KEY,
1371
+ pipeline_id TEXT NOT NULL,
1372
+ status TEXT NOT NULL,
1373
+ input_json TEXT NOT NULL,
1374
+ output_json TEXT,
1375
+ error_json TEXT,
1376
+ metadata_json TEXT,
1377
+ started_at TEXT NOT NULL,
1378
+ ended_at TEXT,
1379
+ duration_ms INTEGER
1380
+ ) STRICT;
1381
+ CREATE INDEX IF NOT EXISTS runner_pipeline_runs_pipeline_started_idx
1382
+ ON runner_pipeline_runs(pipeline_id, started_at DESC);
1293
1383
  CREATE TABLE IF NOT EXISTS runner_traces (
1294
1384
  id TEXT PRIMARY KEY,
1295
1385
  session_id TEXT NOT NULL,
@@ -1479,6 +1569,23 @@ function toPipelineLog(row) {
1479
1569
  ...metadata === void 0 ? {} : { metadata }
1480
1570
  };
1481
1571
  }
1572
+ function toPipelineRun(row) {
1573
+ const output = parseJsonValue(row.output_json);
1574
+ const error = parseJsonValue(row.error_json);
1575
+ const metadata = parseJsonValue(row.metadata_json);
1576
+ return {
1577
+ runId: row.run_id,
1578
+ pipelineId: row.pipeline_id,
1579
+ status: row.status,
1580
+ input: JSON.parse(row.input_json),
1581
+ ...output === void 0 ? {} : { output },
1582
+ ...error === void 0 ? {} : { error },
1583
+ ...metadata === void 0 ? {} : { metadata },
1584
+ startedAt: row.started_at,
1585
+ ...row.ended_at === null ? {} : { endedAt: row.ended_at },
1586
+ ...row.duration_ms === null ? {} : { durationMs: row.duration_ms }
1587
+ };
1588
+ }
1482
1589
  function messageParts(message) {
1483
1590
  if (message.role === "system") {
1484
1591
  return [{ type: "text", value: { type: "text", text: message.content } }];
@@ -1709,10 +1816,12 @@ function resolveStores(options) {
1709
1816
  const sessions = resolveSessionStore(options, defaultStore);
1710
1817
  const traces = resolveTraceStore(options, sessions, defaultStore);
1711
1818
  const pipelineLogs = resolvePipelineLogStore(options, sessions, defaultStore);
1819
+ const pipelineRuns = resolvePipelineRunStore(options, sessions, pipelineLogs, defaultStore);
1712
1820
  return {
1713
1821
  ...sessions === void 0 ? {} : { sessions },
1714
1822
  ...traces === void 0 ? {} : { traces },
1715
- ...pipelineLogs === void 0 ? {} : { pipelineLogs }
1823
+ ...pipelineLogs === void 0 ? {} : { pipelineLogs },
1824
+ ...pipelineRuns === void 0 ? {} : { pipelineRuns }
1716
1825
  };
1717
1826
  }
1718
1827
  function resolveSessionStore(options, defaultStore) {
@@ -1748,6 +1857,21 @@ function resolvePipelineLogStore(options, sessionStore, defaultStore) {
1748
1857
  }
1749
1858
  return defaultStore;
1750
1859
  }
1860
+ function resolvePipelineRunStore(options, sessionStore, pipelineLogStore, defaultStore) {
1861
+ if (options.stores?.pipelineRuns === false) {
1862
+ return void 0;
1863
+ }
1864
+ if (options.stores?.pipelineRuns !== void 0) {
1865
+ return options.stores.pipelineRuns;
1866
+ }
1867
+ if (sessionStore !== void 0 && isPipelineRunStore(sessionStore)) {
1868
+ return sessionStore;
1869
+ }
1870
+ if (pipelineLogStore !== void 0 && isPipelineRunStore(pipelineLogStore)) {
1871
+ return pipelineLogStore;
1872
+ }
1873
+ return defaultStore;
1874
+ }
1751
1875
  function isTraceStore(store) {
1752
1876
  const candidate = store;
1753
1877
  return typeof candidate.listSessionTraces === "function" && typeof candidate.getTrace === "function" && typeof candidate.saveTrace === "function";
@@ -1756,6 +1880,10 @@ function isPipelineLogStore(store) {
1756
1880
  const candidate = store;
1757
1881
  return typeof candidate.appendPipelineLog === "function" && typeof candidate.listPipelineLogs === "function";
1758
1882
  }
1883
+ function isPipelineRunStore(store) {
1884
+ const candidate = store;
1885
+ return typeof candidate.savePipelineRun === "function" && typeof candidate.listPipelineRuns === "function";
1886
+ }
1759
1887
  function unsupportedCapabilities(stores) {
1760
1888
  return [
1761
1889
  ...stores.sessions === void 0 ? ["sessions"] : [],
@@ -3215,7 +3343,7 @@ function registerPipelineRoutes(app, props) {
3215
3343
  if (!props.pipelineMap.has(pipelineId)) {
3216
3344
  return errorResponse(c, 404, "not_found", "Pipeline not found");
3217
3345
  }
3218
- if (props.store === void 0) {
3346
+ if (props.logStore === void 0) {
3219
3347
  return errorResponse(
3220
3348
  c,
3221
3349
  501,
@@ -3232,7 +3360,7 @@ function registerPipelineRoutes(app, props) {
3232
3360
  if (after === false) {
3233
3361
  return errorResponse(c, 400, "bad_request", "after must be a non-negative integer");
3234
3362
  }
3235
- const logs = await props.store.listPipelineLogs({
3363
+ const logs = await props.logStore.listPipelineLogs({
3236
3364
  pipelineId,
3237
3365
  limit,
3238
3366
  ...after === void 0 ? {} : { after }
@@ -3243,6 +3371,27 @@ function registerPipelineRoutes(app, props) {
3243
3371
  ...logs.length === limit && last !== void 0 ? { nextCursor: last.sequence } : {}
3244
3372
  });
3245
3373
  });
3374
+ app.get("/pipelines/:pipelineId/runs", async (c) => {
3375
+ const pipelineId = c.req.param("pipelineId");
3376
+ if (!props.pipelineMap.has(pipelineId)) {
3377
+ return errorResponse(c, 404, "not_found", "Pipeline not found");
3378
+ }
3379
+ if (props.runStore === void 0) {
3380
+ return errorResponse(
3381
+ c,
3382
+ 501,
3383
+ "unsupported_capability",
3384
+ 'Capability "pipelines.runs" is not implemented by this runner',
3385
+ { capability: "pipelines", operation: "runs" }
3386
+ );
3387
+ }
3388
+ const limit = parsePipelineLogLimit(c.req.query("limit"));
3389
+ if (limit === void 0) {
3390
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
3391
+ }
3392
+ const runs = await props.runStore.listPipelineRuns({ pipelineId, limit });
3393
+ return c.json({ runs });
3394
+ });
3246
3395
  app.post("/pipelines/:pipelineId/runs", async (c) => {
3247
3396
  const pipeline = props.pipelineMap.get(c.req.param("pipelineId"));
3248
3397
  if (pipeline === void 0) {
@@ -3254,8 +3403,9 @@ function registerPipelineRoutes(app, props) {
3254
3403
  }
3255
3404
  const runId = globalThis.crypto.randomUUID();
3256
3405
  const startedAt = Date.now();
3406
+ const startedAtIso = new Date(startedAt).toISOString();
3257
3407
  await appendPipelineLog(
3258
- props.store,
3408
+ props.logStore,
3259
3409
  pipelineRunReceivedLog({
3260
3410
  pipeline,
3261
3411
  runId,
@@ -3264,31 +3414,54 @@ function registerPipelineRoutes(app, props) {
3264
3414
  ...body.metadata === void 0 ? {} : { metadata: body.metadata }
3265
3415
  })
3266
3416
  );
3417
+ await savePipelineRun(props.runStore, {
3418
+ runId,
3419
+ pipelineId: pipeline.id,
3420
+ status: "running",
3421
+ input: body.input,
3422
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
3423
+ startedAt: startedAtIso
3424
+ });
3267
3425
  if (body.stream === true) {
3268
3426
  return streamPipelineRun(c, {
3269
3427
  pipeline,
3270
3428
  runId,
3271
3429
  input: body.input,
3272
3430
  startedAt,
3273
- ...props.store === void 0 ? {} : { store: props.store }
3431
+ startedAtIso,
3432
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
3433
+ ...props.logStore === void 0 ? {} : { logStore: props.logStore },
3434
+ ...props.runStore === void 0 ? {} : { runStore: props.runStore }
3274
3435
  });
3275
3436
  }
3276
3437
  try {
3277
- await appendPipelineLog(props.store, pipelineRunStartedLog(pipeline, runId));
3438
+ await appendPipelineLog(props.logStore, pipelineRunStartedLog(pipeline, runId));
3278
3439
  const output = await pipeline.pipeline.run(body.input, {
3279
3440
  observer: {
3280
3441
  async onEvent(event) {
3281
- await appendPipelineLog(props.store, pipelineStageLog(pipeline.id, runId, event));
3442
+ await appendPipelineLog(props.logStore, pipelineStageLog(pipeline.id, runId, event));
3282
3443
  }
3283
3444
  }
3284
3445
  });
3285
3446
  const jsonOutput = toJsonValue3(output);
3447
+ const endedAt = Date.now();
3448
+ await savePipelineRun(props.runStore, {
3449
+ runId,
3450
+ pipelineId: pipeline.id,
3451
+ status: "success",
3452
+ input: body.input,
3453
+ output: jsonOutput,
3454
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
3455
+ startedAt: startedAtIso,
3456
+ endedAt: new Date(endedAt).toISOString(),
3457
+ durationMs: endedAt - startedAt
3458
+ });
3286
3459
  await appendPipelineLog(
3287
- props.store,
3460
+ props.logStore,
3288
3461
  pipelineRunCompletedLog({
3289
3462
  pipelineId: pipeline.id,
3290
3463
  runId,
3291
- durationMs: Date.now() - startedAt,
3464
+ durationMs: endedAt - startedAt,
3292
3465
  output: jsonOutput
3293
3466
  })
3294
3467
  );
@@ -3299,8 +3472,20 @@ function registerPipelineRoutes(app, props) {
3299
3472
  };
3300
3473
  return c.json(response);
3301
3474
  } catch (error) {
3475
+ const endedAt = Date.now();
3476
+ await savePipelineRun(props.runStore, {
3477
+ runId,
3478
+ pipelineId: pipeline.id,
3479
+ status: "error",
3480
+ input: body.input,
3481
+ error: serializeError2(error),
3482
+ ...body.metadata === void 0 ? {} : { metadata: body.metadata },
3483
+ startedAt: startedAtIso,
3484
+ endedAt: new Date(endedAt).toISOString(),
3485
+ durationMs: endedAt - startedAt
3486
+ });
3302
3487
  await appendPipelineLog(
3303
- props.store,
3488
+ props.logStore,
3304
3489
  pipelineRunFailedLog(pipeline.id, runId, error, startedAt)
3305
3490
  );
3306
3491
  return errorResponse(c, 500, "internal_error", "Pipeline run failed", serializeError2(error));
@@ -3336,13 +3521,13 @@ function streamPipelineRun(c, props) {
3336
3521
  );
3337
3522
  }
3338
3523
  async function* pipelineRunEvents(props) {
3339
- yield* emitPipelineLog(props.store, pipelineRunStartedLog(props.pipeline, props.runId));
3524
+ yield* emitPipelineLog(props.logStore, pipelineRunStartedLog(props.pipeline, props.runId));
3340
3525
  const events = new AsyncEventQueue();
3341
3526
  const run = props.pipeline.pipeline.run(props.input, {
3342
3527
  observer: {
3343
3528
  async onEvent(event) {
3344
3529
  const log = await appendPipelineLog(
3345
- props.store,
3530
+ props.logStore,
3346
3531
  pipelineStageLog(props.pipeline.id, props.runId, event)
3347
3532
  );
3348
3533
  if (log !== void 0) {
@@ -3352,12 +3537,24 @@ async function* pipelineRunEvents(props) {
3352
3537
  }
3353
3538
  }).then(async (output) => {
3354
3539
  const jsonOutput = toJsonValue3(output);
3540
+ const endedAt = Date.now();
3541
+ await savePipelineRun(props.runStore, {
3542
+ runId: props.runId,
3543
+ pipelineId: props.pipeline.id,
3544
+ status: "success",
3545
+ input: props.input,
3546
+ output: jsonOutput,
3547
+ ...props.metadata === void 0 ? {} : { metadata: props.metadata },
3548
+ startedAt: props.startedAtIso,
3549
+ endedAt: new Date(endedAt).toISOString(),
3550
+ durationMs: endedAt - props.startedAt
3551
+ });
3355
3552
  const log = await appendPipelineLog(
3356
- props.store,
3553
+ props.logStore,
3357
3554
  pipelineRunCompletedLog({
3358
3555
  pipelineId: props.pipeline.id,
3359
3556
  runId: props.runId,
3360
- durationMs: Date.now() - props.startedAt,
3557
+ durationMs: endedAt - props.startedAt,
3361
3558
  output: jsonOutput
3362
3559
  })
3363
3560
  );
@@ -3371,8 +3568,20 @@ async function* pipelineRunEvents(props) {
3371
3568
  output: jsonOutput
3372
3569
  });
3373
3570
  }).catch(async (error) => {
3571
+ const endedAt = Date.now();
3572
+ await savePipelineRun(props.runStore, {
3573
+ runId: props.runId,
3574
+ pipelineId: props.pipeline.id,
3575
+ status: "error",
3576
+ input: props.input,
3577
+ error: serializeError2(error),
3578
+ ...props.metadata === void 0 ? {} : { metadata: props.metadata },
3579
+ startedAt: props.startedAtIso,
3580
+ endedAt: new Date(endedAt).toISOString(),
3581
+ durationMs: endedAt - props.startedAt
3582
+ });
3374
3583
  const log = await appendPipelineLog(
3375
- props.store,
3584
+ props.logStore,
3376
3585
  pipelineRunFailedLog(props.pipeline.id, props.runId, error, props.startedAt)
3377
3586
  );
3378
3587
  if (log !== void 0) {
@@ -3392,6 +3601,9 @@ async function* pipelineRunEvents(props) {
3392
3601
  await run;
3393
3602
  }
3394
3603
  }
3604
+ async function savePipelineRun(store, input) {
3605
+ return store?.savePipelineRun(input);
3606
+ }
3395
3607
  async function parsePipelineRunRequest(c) {
3396
3608
  let body;
3397
3609
  try {
@@ -4599,7 +4811,8 @@ function createStudioApp(options) {
4599
4811
  registerPipelineRoutes(app, {
4600
4812
  pipelines,
4601
4813
  pipelineMap,
4602
- ...stores.pipelineLogs === void 0 ? {} : { store: stores.pipelineLogs }
4814
+ ...stores.pipelineLogs === void 0 ? {} : { logStore: stores.pipelineLogs },
4815
+ ...stores.pipelineRuns === void 0 ? {} : { runStore: stores.pipelineRuns }
4603
4816
  });
4604
4817
  app.post("/agents/:agentId/runs", async (c) => {
4605
4818
  const agentId = c.req.param("agentId");