@axlsdk/axl 0.11.6 → 0.12.0

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/README.md CHANGED
@@ -505,7 +505,7 @@ const result = await ctx.ask(extractAgent, 'Extract user from this text', {
505
505
 
506
506
  ### State Stores
507
507
 
508
- Three built-in implementations. All persist the same data: workflow execution checkpoints, `awaitHuman` decisions, session history, memory entries, and the execution state needed for suspend/resume.
508
+ Three built-in implementations. All persist the same data: workflow execution checkpoints, `awaitHuman` decisions, session history, memory entries, execution history, eval history, and the execution state needed for suspend/resume.
509
509
 
510
510
  **Memory** (default) — in-process, no persistence. Use for development and stateless workflows.
511
511
 
package/dist/index.cjs CHANGED
@@ -2666,6 +2666,11 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2666
2666
  model: modelUri,
2667
2667
  promptVersion: agent2._config.version,
2668
2668
  cost: response.cost,
2669
+ tokens: response.usage ? {
2670
+ input: response.usage.prompt_tokens,
2671
+ output: response.usage.completion_tokens,
2672
+ reasoning: response.usage.reasoning_tokens
2673
+ } : void 0,
2669
2674
  duration: Date.now() - startTime,
2670
2675
  data: { prompt, response: response.content }
2671
2676
  });
@@ -4053,6 +4058,8 @@ var MemoryStore = class {
4053
4058
  decisions = /* @__PURE__ */ new Map();
4054
4059
  executionStates = /* @__PURE__ */ new Map();
4055
4060
  memories = /* @__PURE__ */ new Map();
4061
+ executionHistory = /* @__PURE__ */ new Map();
4062
+ evalHistory = /* @__PURE__ */ new Map();
4056
4063
  constructor() {
4057
4064
  this.loadPersistedState();
4058
4065
  }
@@ -4149,11 +4156,36 @@ var MemoryStore = class {
4149
4156
  async getAllMemory(scope) {
4150
4157
  const scopeMap = this.memories.get(scope);
4151
4158
  if (!scopeMap) return [];
4152
- return Array.from(scopeMap.entries()).map(([key, value]) => ({ key, value }));
4159
+ return Array.from(scopeMap.entries()).map(([key, value]) => ({
4160
+ key,
4161
+ value: structuredClone(value)
4162
+ }));
4153
4163
  }
4154
4164
  async deleteMemory(scope, key) {
4155
4165
  this.memories.get(scope)?.delete(key);
4156
4166
  }
4167
+ // ── Execution History ──────────────────────────────────────────────
4168
+ async saveExecution(execution) {
4169
+ this.executionHistory.set(execution.executionId, structuredClone(execution));
4170
+ }
4171
+ async getExecution(executionId) {
4172
+ const exec = this.executionHistory.get(executionId);
4173
+ return exec ? structuredClone(exec) : null;
4174
+ }
4175
+ async listExecutions(limit) {
4176
+ const sorted = [...this.executionHistory.values()].sort((a, b) => b.startedAt - a.startedAt);
4177
+ const result = limit ? sorted.slice(0, limit) : sorted;
4178
+ return result.map((e) => structuredClone(e));
4179
+ }
4180
+ // ── Eval History ──────────────────────────────────────────────────
4181
+ async saveEvalResult(entry) {
4182
+ this.evalHistory.set(entry.id, structuredClone(entry));
4183
+ }
4184
+ async listEvalResults(limit) {
4185
+ const sorted = [...this.evalHistory.values()].sort((a, b) => b.timestamp - a.timestamp);
4186
+ const result = limit ? sorted.slice(0, limit) : sorted;
4187
+ return result.map((e) => structuredClone(e));
4188
+ }
4157
4189
  // ── Sessions (Studio introspection) ─────────────────────────────────
4158
4190
  async listSessions() {
4159
4191
  return [...this.sessions.keys()];
@@ -4213,6 +4245,19 @@ function safeJsonParse(data) {
4213
4245
  return null;
4214
4246
  }
4215
4247
  }
4248
+ function rowToExecutionInfo(row) {
4249
+ return {
4250
+ executionId: row.execution_id,
4251
+ workflow: row.workflow,
4252
+ status: row.status,
4253
+ totalCost: row.total_cost,
4254
+ startedAt: row.started_at,
4255
+ completedAt: row.completed_at ?? void 0,
4256
+ duration: row.duration,
4257
+ error: row.error ?? void 0,
4258
+ steps: safeJsonParse(row.steps) ?? []
4259
+ };
4260
+ }
4216
4261
  var SQLiteStore = class {
4217
4262
  db;
4218
4263
  constructor(filePath) {
@@ -4273,6 +4318,28 @@ var SQLiteStore = class {
4273
4318
  updated_at TEXT NOT NULL DEFAULT (datetime('now')),
4274
4319
  PRIMARY KEY (scope, key)
4275
4320
  );
4321
+
4322
+ CREATE TABLE IF NOT EXISTS execution_history (
4323
+ execution_id TEXT PRIMARY KEY,
4324
+ workflow TEXT NOT NULL,
4325
+ status TEXT NOT NULL,
4326
+ total_cost REAL NOT NULL DEFAULT 0,
4327
+ started_at INTEGER NOT NULL,
4328
+ completed_at INTEGER,
4329
+ duration INTEGER NOT NULL DEFAULT 0,
4330
+ error TEXT,
4331
+ steps TEXT NOT NULL
4332
+ );
4333
+
4334
+ CREATE TABLE IF NOT EXISTS eval_history (
4335
+ id TEXT PRIMARY KEY,
4336
+ eval_name TEXT NOT NULL,
4337
+ timestamp INTEGER NOT NULL,
4338
+ data TEXT NOT NULL
4339
+ );
4340
+
4341
+ CREATE INDEX IF NOT EXISTS idx_exec_history_started ON execution_history (started_at DESC);
4342
+ CREATE INDEX IF NOT EXISTS idx_eval_history_timestamp ON eval_history (timestamp DESC);
4276
4343
  `);
4277
4344
  }
4278
4345
  // ── Checkpoints ────────────────────────────────────────────────────────
@@ -4386,6 +4453,47 @@ var SQLiteStore = class {
4386
4453
  const rows = stmt.all();
4387
4454
  return rows.map((r) => r.execution_id);
4388
4455
  }
4456
+ // ── Execution History ────────────────────────────────────────────────────
4457
+ async saveExecution(execution) {
4458
+ this.db.prepare(
4459
+ "INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, steps) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
4460
+ ).run(
4461
+ execution.executionId,
4462
+ execution.workflow,
4463
+ execution.status,
4464
+ execution.totalCost,
4465
+ execution.startedAt,
4466
+ execution.completedAt ?? null,
4467
+ execution.duration,
4468
+ execution.error ?? null,
4469
+ JSON.stringify(execution.steps)
4470
+ );
4471
+ }
4472
+ async getExecution(executionId) {
4473
+ const row = this.db.prepare("SELECT * FROM execution_history WHERE execution_id = ?").get(executionId);
4474
+ return row ? rowToExecutionInfo(row) : null;
4475
+ }
4476
+ async listExecutions(limit) {
4477
+ const sql = limit ? "SELECT * FROM execution_history ORDER BY started_at DESC LIMIT ?" : "SELECT * FROM execution_history ORDER BY started_at DESC";
4478
+ const rows = limit ? this.db.prepare(sql).all(limit) : this.db.prepare(sql).all();
4479
+ return rows.map(rowToExecutionInfo);
4480
+ }
4481
+ // ── Eval History ────────────────────────────────────────────────────────
4482
+ async saveEvalResult(entry) {
4483
+ this.db.prepare(
4484
+ "INSERT OR REPLACE INTO eval_history (id, eval_name, timestamp, data) VALUES (?, ?, ?, ?)"
4485
+ ).run(entry.id, entry.eval, entry.timestamp, JSON.stringify(entry.data));
4486
+ }
4487
+ async listEvalResults(limit) {
4488
+ const sql = limit ? "SELECT * FROM eval_history ORDER BY timestamp DESC LIMIT ?" : "SELECT * FROM eval_history ORDER BY timestamp DESC";
4489
+ const rows = limit ? this.db.prepare(sql).all(limit) : this.db.prepare(sql).all();
4490
+ return rows.map((r) => ({
4491
+ id: r.id,
4492
+ eval: r.eval_name,
4493
+ timestamp: r.timestamp,
4494
+ data: safeJsonParse(r.data)
4495
+ }));
4496
+ }
4389
4497
  // ── Sessions (Studio introspection) ────────────────────────────────────
4390
4498
  async listSessions() {
4391
4499
  const rows = this.db.prepare("SELECT session_id FROM sessions").all();
@@ -5271,6 +5379,10 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5271
5379
  mcpManager;
5272
5380
  memoryManager;
5273
5381
  spanManager = new NoopSpanManager();
5382
+ historicalExecutions = /* @__PURE__ */ new Map();
5383
+ historicalExecutionsLoadPromise = null;
5384
+ evalHistory = [];
5385
+ evalHistoryLoadPromise = null;
5274
5386
  constructor(config) {
5275
5387
  super();
5276
5388
  this.config = resolveConfig(config ?? {});
@@ -5315,6 +5427,20 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5315
5427
  return new MemoryStore();
5316
5428
  }
5317
5429
  }
5430
+ /**
5431
+ * Persist a completed/failed execution to the state store (fire-and-forget)
5432
+ * and move it from the active executions map to the historical cache.
5433
+ */
5434
+ persistExecution(execInfo) {
5435
+ const snapshot = structuredClone(execInfo);
5436
+ if (this.stateStore.saveExecution) {
5437
+ this.stateStore.saveExecution(snapshot).catch(() => {
5438
+ });
5439
+ }
5440
+ const id = execInfo.executionId;
5441
+ this.historicalExecutions.set(id, snapshot);
5442
+ this.executions.delete(id);
5443
+ }
5318
5444
  /** Register a workflow with the runtime. */
5319
5445
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5320
5446
  register(workflow2) {
@@ -5393,6 +5519,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5393
5519
  async runRegisteredEval(name) {
5394
5520
  const entry = this.registeredEvals.get(name);
5395
5521
  if (!entry) throw new Error(`Eval "${name}" is not registered`);
5522
+ let result;
5396
5523
  if (entry.executeWorkflow) {
5397
5524
  let runEvalFn;
5398
5525
  try {
@@ -5404,20 +5531,50 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5404
5531
  }
5405
5532
  const originalExecuteFn = entry.executeWorkflow;
5406
5533
  const wrappedExecuteFn = async (input, runtime) => {
5407
- const { result, cost: trackedCost } = await this.trackCost(async () => {
5534
+ const { result: result2, cost: trackedCost } = await this.trackCost(async () => {
5408
5535
  return originalExecuteFn(input, runtime);
5409
5536
  });
5410
- return { output: result.output, cost: result.cost ?? trackedCost };
5537
+ return { output: result2.output, cost: result2.cost ?? trackedCost };
5411
5538
  };
5412
- return runEvalFn(entry.config, wrappedExecuteFn, void 0, this);
5539
+ result = await runEvalFn(entry.config, wrappedExecuteFn, void 0, this);
5540
+ } else {
5541
+ result = await this.eval(
5542
+ entry.config
5543
+ );
5413
5544
  }
5414
- return this.eval(
5415
- entry.config
5416
- );
5545
+ const resultObj = result;
5546
+ try {
5547
+ await this.saveEvalResult({
5548
+ id: resultObj.id ?? (0, import_node_crypto2.randomUUID)(),
5549
+ eval: name,
5550
+ timestamp: Date.now(),
5551
+ data: structuredClone(result)
5552
+ });
5553
+ } catch {
5554
+ }
5555
+ return result;
5417
5556
  }
5418
- /** Get all execution info (running + completed). */
5419
- getExecutions() {
5420
- return [...this.executions.values()];
5557
+ /** Get all execution info (running + completed + historical). */
5558
+ async getExecutions() {
5559
+ if (!this.historicalExecutionsLoadPromise && this.stateStore.listExecutions) {
5560
+ this.historicalExecutionsLoadPromise = this.stateStore.listExecutions().then((stored) => {
5561
+ for (const exec of stored) {
5562
+ if (!this.executions.has(exec.executionId) && !this.historicalExecutions.has(exec.executionId)) {
5563
+ this.historicalExecutions.set(exec.executionId, exec);
5564
+ }
5565
+ }
5566
+ }).catch(() => {
5567
+ this.historicalExecutionsLoadPromise = null;
5568
+ });
5569
+ }
5570
+ if (this.historicalExecutionsLoadPromise) {
5571
+ await this.historicalExecutionsLoadPromise;
5572
+ }
5573
+ const merged = new Map(this.historicalExecutions);
5574
+ for (const [id, exec] of this.executions) {
5575
+ merged.set(id, exec);
5576
+ }
5577
+ return [...merged.values()].sort((a, b) => b.startedAt - a.startedAt);
5421
5578
  }
5422
5579
  /**
5423
5580
  * Create a WorkflowContext for ad-hoc use (evals, tool testing, prototyping).
@@ -5549,6 +5706,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5549
5706
  }
5550
5707
  span.setAttribute("axl.workflow.cost", execInfo.totalCost);
5551
5708
  span.setAttribute("axl.workflow.duration", execInfo.duration);
5709
+ this.persistExecution(execInfo);
5552
5710
  return output;
5553
5711
  } catch (err) {
5554
5712
  execInfo.status = "failed";
@@ -5556,6 +5714,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5556
5714
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
5557
5715
  execInfo.error = err instanceof Error ? err.message : String(err);
5558
5716
  ctx.log("workflow_end", { workflow: name, status: "failed", error: execInfo.error });
5717
+ this.persistExecution(execInfo);
5559
5718
  throw err;
5560
5719
  } finally {
5561
5720
  this.abortControllers.delete(executionId);
@@ -5569,6 +5728,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5569
5728
  const controller = new AbortController();
5570
5729
  axlStream.on("close", () => controller.abort());
5571
5730
  let execInfo;
5731
+ let ctx;
5572
5732
  const run = async () => {
5573
5733
  const workflow2 = this.workflows.get(name);
5574
5734
  if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
@@ -5587,7 +5747,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5587
5747
  duration: 0
5588
5748
  };
5589
5749
  this.executions.set(executionId, execInfo);
5590
- const ctx = new WorkflowContext({
5750
+ const wfCtx = new WorkflowContext({
5591
5751
  input: validated,
5592
5752
  executionId,
5593
5753
  metadata: options?.metadata,
@@ -5675,6 +5835,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5675
5835
  policy: "finish_and_stop"
5676
5836
  }
5677
5837
  });
5838
+ ctx = wfCtx;
5678
5839
  return this.spanManager.withSpanAsync(
5679
5840
  "axl.workflow.execute",
5680
5841
  {
@@ -5684,16 +5845,23 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5684
5845
  },
5685
5846
  async (span) => {
5686
5847
  try {
5687
- const rawResult = await workflow2.handler(ctx);
5848
+ const rawResult = await workflow2.handler(wfCtx);
5688
5849
  const result = workflow2.outputSchema ? workflow2.outputSchema.parse(rawResult) : rawResult;
5689
5850
  execInfo.status = "completed";
5690
5851
  execInfo.completedAt = Date.now();
5691
5852
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
5853
+ wfCtx.log("workflow_end", {
5854
+ workflow: name,
5855
+ status: "completed",
5856
+ duration: execInfo.duration,
5857
+ cost: execInfo.totalCost
5858
+ });
5692
5859
  if (this.stateStore.deleteCheckpoints) {
5693
5860
  await this.stateStore.deleteCheckpoints(executionId);
5694
5861
  }
5695
5862
  span.setAttribute("axl.workflow.cost", execInfo.totalCost);
5696
5863
  span.setAttribute("axl.workflow.duration", execInfo.duration);
5864
+ this.persistExecution(execInfo);
5697
5865
  return result;
5698
5866
  } finally {
5699
5867
  this.abortControllers.delete(executionId);
@@ -5707,6 +5875,12 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5707
5875
  execInfo.completedAt = Date.now();
5708
5876
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
5709
5877
  execInfo.error = err instanceof Error ? err.message : String(err);
5878
+ ctx?.log("workflow_end", {
5879
+ workflow: name,
5880
+ status: "failed",
5881
+ error: execInfo.error
5882
+ });
5883
+ this.persistExecution(execInfo);
5710
5884
  }
5711
5885
  axlStream._error(err instanceof Error ? err : new Error(String(err)));
5712
5886
  });
@@ -5750,7 +5924,45 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
5750
5924
  }
5751
5925
  /** Get execution details by ID. */
5752
5926
  async getExecution(executionId) {
5753
- return this.executions.get(executionId);
5927
+ const inMemory = this.executions.get(executionId);
5928
+ if (inMemory) return inMemory;
5929
+ const cached = this.historicalExecutions.get(executionId);
5930
+ if (cached) return cached;
5931
+ if (this.stateStore.getExecution) {
5932
+ const stored = await this.stateStore.getExecution(executionId);
5933
+ if (stored) {
5934
+ this.historicalExecutions.set(executionId, stored);
5935
+ return stored;
5936
+ }
5937
+ }
5938
+ return void 0;
5939
+ }
5940
+ /** Save an eval result to history. */
5941
+ async saveEvalResult(entry) {
5942
+ this.evalHistory.unshift(entry);
5943
+ if (this.stateStore.saveEvalResult) {
5944
+ await this.stateStore.saveEvalResult(entry);
5945
+ }
5946
+ }
5947
+ /** Get eval result history (most recent first). */
5948
+ async getEvalHistory() {
5949
+ if (!this.evalHistoryLoadPromise && this.stateStore.listEvalResults) {
5950
+ this.evalHistoryLoadPromise = this.stateStore.listEvalResults().then((stored) => {
5951
+ const ids = new Set(this.evalHistory.map((e) => e.id));
5952
+ for (const entry of stored) {
5953
+ if (!ids.has(entry.id)) {
5954
+ this.evalHistory.push(entry);
5955
+ }
5956
+ }
5957
+ this.evalHistory.sort((a, b) => b.timestamp - a.timestamp);
5958
+ }).catch(() => {
5959
+ this.evalHistoryLoadPromise = null;
5960
+ });
5961
+ }
5962
+ if (this.evalHistoryLoadPromise) {
5963
+ await this.evalHistoryLoadPromise;
5964
+ }
5965
+ return [...this.evalHistory];
5754
5966
  }
5755
5967
  /** List pending human decisions. */
5756
5968
  async getPendingDecisions() {
@@ -6058,6 +6270,18 @@ var RedisStore = class _RedisStore {
6058
6270
  pendingExecSetKey() {
6059
6271
  return "axl:pending-executions";
6060
6272
  }
6273
+ execHistoryKey(executionId) {
6274
+ return `axl:exec-history:${executionId}`;
6275
+ }
6276
+ execHistorySetKey() {
6277
+ return "axl:exec-history-ids";
6278
+ }
6279
+ evalHistoryKey(id) {
6280
+ return `axl:eval-history:${id}`;
6281
+ }
6282
+ evalHistorySetKey() {
6283
+ return "axl:eval-history-ids";
6284
+ }
6061
6285
  // ── Checkpoints ──────────────────────────────────────────────────────
6062
6286
  async saveCheckpoint(executionId, step, data) {
6063
6287
  await this.client.hSet(this.checkpointKey(executionId), String(step), JSON.stringify(data));
@@ -6129,6 +6353,42 @@ var RedisStore = class _RedisStore {
6129
6353
  async listPendingExecutions() {
6130
6354
  return this.client.sMembers(this.pendingExecSetKey());
6131
6355
  }
6356
+ // ── Execution History ────────────────────────────────────────────────
6357
+ async saveExecution(execution) {
6358
+ await this.client.sAdd(this.execHistorySetKey(), execution.executionId);
6359
+ await this.client.set(this.execHistoryKey(execution.executionId), JSON.stringify(execution));
6360
+ }
6361
+ async getExecution(executionId) {
6362
+ const raw = await this.client.get(this.execHistoryKey(executionId));
6363
+ return raw ? JSON.parse(raw) : null;
6364
+ }
6365
+ async listExecutions(limit) {
6366
+ const ids = await this.client.sMembers(this.execHistorySetKey());
6367
+ if (ids.length === 0) return [];
6368
+ const entries = [];
6369
+ for (const id of ids) {
6370
+ const raw = await this.client.get(this.execHistoryKey(id));
6371
+ if (raw) entries.push(JSON.parse(raw));
6372
+ }
6373
+ entries.sort((a, b) => b.startedAt - a.startedAt);
6374
+ return limit ? entries.slice(0, limit) : entries;
6375
+ }
6376
+ // ── Eval History ────────────────────────────────────────────────────
6377
+ async saveEvalResult(entry) {
6378
+ await this.client.sAdd(this.evalHistorySetKey(), entry.id);
6379
+ await this.client.set(this.evalHistoryKey(entry.id), JSON.stringify(entry));
6380
+ }
6381
+ async listEvalResults(limit) {
6382
+ const ids = await this.client.sMembers(this.evalHistorySetKey());
6383
+ if (ids.length === 0) return [];
6384
+ const entries = [];
6385
+ for (const id of ids) {
6386
+ const raw = await this.client.get(this.evalHistoryKey(id));
6387
+ if (raw) entries.push(JSON.parse(raw));
6388
+ }
6389
+ entries.sort((a, b) => b.timestamp - a.timestamp);
6390
+ return limit ? entries.slice(0, limit) : entries;
6391
+ }
6132
6392
  // ── Sessions (Studio introspection) ────────────────────────────────────
6133
6393
  async listSessions() {
6134
6394
  return this.client.sMembers("axl:session-ids");