@sixb/sqlite 0.1.26 → 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.
Files changed (43) hide show
  1. package/dist/agents/agent-storage.d.ts +8 -3
  2. package/dist/agents/agent-storage.d.ts.map +1 -1
  3. package/dist/agents/computers.d.ts +22 -0
  4. package/dist/agents/computers.d.ts.map +1 -0
  5. package/dist/agents/rows.d.ts +3 -0
  6. package/dist/agents/rows.d.ts.map +1 -1
  7. package/dist/agents/runs.d.ts.map +1 -1
  8. package/dist/agents/threads.d.ts +3 -1
  9. package/dist/agents/threads.d.ts.map +1 -1
  10. package/dist/agents/token-rates.d.ts +13 -0
  11. package/dist/agents/token-rates.d.ts.map +1 -0
  12. package/dist/ai-accounting-filters.d.ts +14 -0
  13. package/dist/ai-accounting-filters.d.ts.map +1 -0
  14. package/dist/ai-cost-groups.d.ts +1 -1
  15. package/dist/ai-cost-groups.d.ts.map +1 -1
  16. package/dist/ai-cost-storage.d.ts.map +1 -1
  17. package/dist/index.js +421 -75
  18. package/dist/index.js.map +18 -15
  19. package/dist/migrations.d.ts.map +1 -1
  20. package/dist/objects/query-compiler.d.ts.map +1 -1
  21. package/dist/ontology-storage/commits.d.ts.map +1 -1
  22. package/dist/ontology-storage/materializations.d.ts +2 -1
  23. package/dist/ontology-storage/materializations.d.ts.map +1 -1
  24. package/dist/pipeline-run-storage.d.ts.map +1 -1
  25. package/dist/sync-run-storage.d.ts.map +1 -1
  26. package/package.json +3 -3
  27. package/src/agents/agent-storage.ts +10 -3
  28. package/src/agents/computers.ts +112 -0
  29. package/src/agents/rows.ts +9 -1
  30. package/src/agents/runs.ts +25 -3
  31. package/src/agents/threads.ts +100 -22
  32. package/src/agents/token-rates.ts +90 -0
  33. package/src/ai-accounting-filters.ts +51 -0
  34. package/src/ai-cost-groups.ts +52 -14
  35. package/src/ai-cost-storage.ts +101 -39
  36. package/src/migrations/082-agent-token-rates.sql +17 -0
  37. package/src/migrations/083-agent-computer-sessions.sql +27 -0
  38. package/src/migrations.ts +6 -0
  39. package/src/objects/query-compiler.ts +26 -15
  40. package/src/ontology-storage/commits.ts +9 -0
  41. package/src/ontology-storage/materializations.ts +18 -1
  42. package/src/pipeline-run-storage.ts +5 -7
  43. package/src/sync-run-storage.ts +2 -7
package/dist/index.js CHANGED
@@ -5975,6 +5975,56 @@ ALTER TABLE agent_threads ADD COLUMN runs_on TEXT CHECK (runs_on IS NULL OR runs
5975
5975
  ALTER TABLE agent_threads ADD COLUMN computer_id TEXT;
5976
5976
  `;
5977
5977
 
5978
+ // src/migrations/082-agent-token-rates.sql
5979
+ var _082_agent_token_rates_default = `-- What each model's tokenizer measured on a project's agent runs, one row per run: the bytes of a
5980
+ -- request's text as the Agent's context estimator weighs them, against the text tokens the
5981
+ -- provider counted. A record deletes the model's rows past its newest 20, so the table stays small.
5982
+ -- Timestamps are ISO-8601 UTC text and compare lexicographically.
5983
+ CREATE TABLE agent_token_rates (
5984
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
5985
+ run_id TEXT NOT NULL CHECK (length(trim(run_id)) > 0),
5986
+ provider_id TEXT NOT NULL CHECK (length(trim(provider_id)) > 0),
5987
+ model_id TEXT NOT NULL CHECK (length(trim(model_id)) > 0),
5988
+ bytes INTEGER NOT NULL CHECK (bytes > 0),
5989
+ tokens INTEGER NOT NULL CHECK (tokens > 0),
5990
+ measured_at TEXT NOT NULL,
5991
+ PRIMARY KEY (project_id, run_id)
5992
+ );
5993
+
5994
+ CREATE INDEX idx_agent_token_rates_model
5995
+ ON agent_token_rates (project_id, provider_id, model_id, measured_at DESC, run_id DESC);
5996
+ `;
5997
+
5998
+ // src/migrations/083-agent-computer-sessions.sql
5999
+ var _083_agent_computer_sessions_default = `-- Where a computer's thread runs: the computer's name and the folder there, beside its id. A
6000
+ -- computer kept before folders were has none until its owner moves the thread.
6001
+ ALTER TABLE agent_threads ADD COLUMN computer_name TEXT;
6002
+ ALTER TABLE agent_threads ADD COLUMN computer_folder TEXT;
6003
+ UPDATE agent_threads SET computer_id = NULL WHERE computer_id IS NOT NULL;
6004
+ -- When its turns last moved to where they run now: the files earlier turns used are not there.
6005
+ ALTER TABLE agent_threads ADD COLUMN moved_at TEXT;
6006
+
6007
+ -- Each open sixb on its owner's computers, a lease it renews: while one is live, the threads that
6008
+ -- run on its computer in its folder are reachable there.
6009
+ CREATE TABLE agent_computer_sessions (
6010
+ project_id TEXT NOT NULL,
6011
+ owner_principal_type TEXT NOT NULL CHECK (owner_principal_type IN ('user', 'serviceAccount', 'system')),
6012
+ owner_principal_id TEXT NOT NULL CHECK (length(owner_principal_id) > 0),
6013
+ id TEXT NOT NULL CHECK (length(id) > 0),
6014
+ computer_id TEXT NOT NULL,
6015
+ computer_name TEXT NOT NULL,
6016
+ folder TEXT NOT NULL,
6017
+ expires_at TEXT NOT NULL,
6018
+ PRIMARY KEY (project_id, owner_principal_type, owner_principal_id, id)
6019
+ );
6020
+ CREATE INDEX agent_computer_sessions_by_expiry ON agent_computer_sessions (project_id, expires_at);
6021
+
6022
+ -- Each open sixb asks every 20 seconds which of its owner's threads here have a turn active.
6023
+ CREATE INDEX agent_threads_active_on_computer
6024
+ ON agent_threads (project_id, owner_principal_type, owner_principal_id, computer_id, computer_folder)
6025
+ WHERE active_run_id IS NOT NULL;
6026
+ `;
6027
+
5978
6028
  // src/migrations.ts
5979
6029
  var MIGRATIONS_TABLE_SQL = `
5980
6030
  CREATE TABLE IF NOT EXISTS sixb_migrations (
@@ -6110,7 +6160,9 @@ var sqliteStorageMigrations = defineMigrations({
6110
6160
  }, { checksum: checksum(_078_subagent_threads_default) }),
6111
6161
  sqliteSql("079-subagent-questions", _079_subagent_questions_default),
6112
6162
  sqliteSql("080-agent-run-cancel-requests", _080_agent_run_cancel_requests_default),
6113
- sqliteSql("081-agent-thread-runs-on", _081_agent_thread_runs_on_default)
6163
+ sqliteSql("081-agent-thread-runs-on", _081_agent_thread_runs_on_default),
6164
+ sqliteSql("082-agent-token-rates", _082_agent_token_rates_default),
6165
+ sqliteSql("083-agent-computer-sessions", _083_agent_computer_sessions_default)
6114
6166
  ]
6115
6167
  });
6116
6168
  function sqliteStoragePath(basePath) {
@@ -7077,7 +7129,10 @@ function rowToThreadRecord(row) {
7077
7129
  },
7078
7130
  ...row.sandbox_params == null ? {} : { sandboxParams: snapshotAgentThreadSandboxParams(JSON.parse(row.sandbox_params)) },
7079
7131
  ...row.runs_on == null ? {} : { runsOn: row.runs_on },
7080
- ...row.computer_id == null ? {} : { computerId: row.computer_id },
7132
+ ...row.computer_id == null || row.computer_name == null || row.computer_folder == null ? {} : {
7133
+ computer: { id: row.computer_id, name: row.computer_name, folder: row.computer_folder }
7134
+ },
7135
+ ...row.moved_at == null ? {} : { movedAt: new Date(row.moved_at) },
7081
7136
  status: row.status,
7082
7137
  activeRunId: row.active_run_id,
7083
7138
  lastMessageAt: row.last_message_at ? new Date(row.last_message_at) : undefined,
@@ -7560,6 +7615,55 @@ class SqliteAgentContextCheckpointStore {
7560
7615
  }
7561
7616
  }
7562
7617
 
7618
+ // src/agents/computers.ts
7619
+ import { assertAgentComputerSession } from "@sixb/core/internal/agent-run-storage-provider";
7620
+
7621
+ class SqliteAgentComputerSessionStore {
7622
+ db;
7623
+ constructor(db) {
7624
+ this.db = db;
7625
+ }
7626
+ async hold(input) {
7627
+ assertAgentComputerSession(input, "SixbSqlite");
7628
+ this.db.query(`INSERT INTO agent_computer_sessions (
7629
+ project_id, owner_principal_type, owner_principal_id, id,
7630
+ computer_id, computer_name, folder, expires_at
7631
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
7632
+ ON CONFLICT (project_id, owner_principal_type, owner_principal_id, id)
7633
+ DO UPDATE SET
7634
+ computer_id = excluded.computer_id,
7635
+ computer_name = excluded.computer_name,
7636
+ folder = excluded.folder,
7637
+ expires_at = excluded.expires_at`).run(input.projectId, input.ownerPrincipal.type, input.ownerPrincipal.id, input.id, input.computer.id, input.computer.name, input.computer.folder, input.expiresAt.toISOString());
7638
+ }
7639
+ async release(input) {
7640
+ const row = this.db.query(`DELETE FROM agent_computer_sessions
7641
+ WHERE project_id = ? AND owner_principal_type = ? AND owner_principal_id = ? AND id = ?
7642
+ RETURNING *`).get(input.projectId, input.ownerPrincipal.type, input.ownerPrincipal.id, input.id);
7643
+ return row && rowToSession(row);
7644
+ }
7645
+ async listLive(input) {
7646
+ const rows = this.db.query(`SELECT * FROM agent_computer_sessions
7647
+ WHERE project_id = ? AND owner_principal_type = ? AND owner_principal_id = ?
7648
+ AND expires_at > ?
7649
+ ORDER BY id`).all(input.projectId, input.ownerPrincipal.type, input.ownerPrincipal.id, input.now.toISOString());
7650
+ return rows.map(rowToSession);
7651
+ }
7652
+ async removeLapsed(input) {
7653
+ const rows = this.db.query(`DELETE FROM agent_computer_sessions WHERE project_id = ? AND expires_at <= ? RETURNING *`).all(input.projectId, input.now.toISOString());
7654
+ return rows.map(rowToSession);
7655
+ }
7656
+ }
7657
+ function rowToSession(row) {
7658
+ return {
7659
+ projectId: row.project_id,
7660
+ id: row.id,
7661
+ ownerPrincipal: { type: row.owner_principal_type, id: row.owner_principal_id },
7662
+ computer: { id: row.computer_id, name: row.computer_name, folder: row.folder },
7663
+ expiresAt: new Date(row.expires_at)
7664
+ };
7665
+ }
7666
+
7563
7667
  // src/agents/memory.ts
7564
7668
  import {
7565
7669
  agentMemoryConflicts,
@@ -7806,7 +7910,9 @@ import {
7806
7910
  assertConversationAgentRunSpec,
7807
7911
  assertConversationThreadRow,
7808
7912
  assertCreateSubagentRunInput,
7913
+ assertStartsOnComputer,
7809
7914
  assertSubagentThreadTakesRun,
7915
+ assertTurnInThreadPlace,
7810
7916
  newSubagentThreadRecord,
7811
7917
  subagentRunMatchesCreateInput
7812
7918
  } from "@sixb/core/internal/agent-run-storage-provider";
@@ -7888,7 +7994,7 @@ class SqliteAgentRunStore {
7888
7994
  runId: input.id
7889
7995
  });
7890
7996
  return this.db.transaction(() => {
7891
- const thread = this.db.query("SELECT id, kind, active_run_id FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.threadId);
7997
+ const thread = this.db.query("SELECT id, kind, active_run_id, runs_on FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.threadId);
7892
7998
  if (!thread) {
7893
7999
  throw new AgentStorageError4("thread_not_found", `[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`);
7894
8000
  }
@@ -7896,6 +8002,7 @@ class SqliteAgentRunStore {
7896
8002
  if (thread.active_run_id !== null) {
7897
8003
  throw new AgentStorageError4("active_run_exists", `[SixbSqlite] Agent thread '${input.threadId}' already has an active run '${thread.active_run_id}'.`);
7898
8004
  }
8005
+ assertTurnInThreadPlace(thread.runs_on, input.spec, input.threadId, "SixbSqlite");
7899
8006
  try {
7900
8007
  this.db.query(`
7901
8008
  INSERT INTO agent_runs (
@@ -7995,7 +8102,11 @@ class SqliteAgentRunStore {
7995
8102
  async start(input) {
7996
8103
  const startedAt = input.startedAt ?? new Date;
7997
8104
  return this.db.transaction(() => {
7998
- this.requireStatus(input.projectId, input.id, "queued");
8105
+ const run = this.requireStatus(input.projectId, input.id, "queued");
8106
+ if (input.onComputer) {
8107
+ const thread = this.db.query("SELECT computer_id AS id, computer_folder AS folder FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, run.thread_id);
8108
+ assertStartsOnComputer(rowToRunRecord(run), thread?.id ? thread : undefined, input.onComputer, "SixbSqlite");
8109
+ }
7999
8110
  this.db.query(`
8000
8111
  UPDATE agent_runs
8001
8112
  SET
@@ -8179,6 +8290,9 @@ class SqliteAgentRunStore {
8179
8290
  whereClauses.push("parent_run_id = ?");
8180
8291
  args.push(input.parentRunId);
8181
8292
  }
8293
+ if (input.excludeComputerTurns) {
8294
+ whereClauses.push("json_extract(spec, '$.runsOn') IS NOT 'computer'");
8295
+ }
8182
8296
  appendRunListFilters(whereClauses, args, input, "COALESCE(started_at, created_at)");
8183
8297
  const { rows, total, hasMore } = queryRunList({
8184
8298
  db: this.db,
@@ -8296,12 +8410,15 @@ class SqliteAgentApprovalModeStore {
8296
8410
 
8297
8411
  // src/agents/threads.ts
8298
8412
  import {
8413
+ assertAgentThreadComputer,
8299
8414
  assertAssignableComputer,
8300
- assertComputerId,
8415
+ assertComputerPlace,
8301
8416
  assertConversationThreadRow as assertConversationThreadRow2,
8302
- assertRunsOn,
8417
+ assertMovableToCloud,
8418
+ movesComputerPlace,
8303
8419
  participantsToAdd,
8304
8420
  planAgentThreadFork,
8421
+ queuedTurnInCloud,
8305
8422
  remapArtifactIds,
8306
8423
  snapshotAgentThreadSandboxParams as snapshotAgentThreadSandboxParams2,
8307
8424
  transitionAgentThreadSandbox
@@ -8324,7 +8441,7 @@ class SqliteAgentThreadStore {
8324
8441
  }).immediate();
8325
8442
  }
8326
8443
  async create(input) {
8327
- assertRunsOn(input.runsOn, "Agent thread", "SixbSqlite");
8444
+ assertComputerPlace(input.runsOn, input.computer, "SixbSqlite");
8328
8445
  const sandboxParams = input.sandboxParams === undefined ? null : JSON.stringify(snapshotAgentThreadSandboxParams2(input.sandboxParams));
8329
8446
  const createdAt = input.createdAt ?? new Date;
8330
8447
  const updatedAt = input.updatedAt ?? createdAt;
@@ -8338,14 +8455,17 @@ class SqliteAgentThreadStore {
8338
8455
  title,
8339
8456
  sandbox_params,
8340
8457
  runs_on,
8458
+ computer_id,
8459
+ computer_name,
8460
+ computer_folder,
8341
8461
  status,
8342
8462
  active_run_id,
8343
8463
  last_message_at,
8344
8464
  message_count,
8345
8465
  created_at,
8346
8466
  updated_at
8347
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, 0, ?, ?)
8348
- `).run(input.projectId, input.id, input.ownerPrincipal.type, input.ownerPrincipal.id, input.title ?? null, sandboxParams, input.runsOn ?? null, input.status ?? "active", createdAt.toISOString(), updatedAt.toISOString());
8467
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, 0, ?, ?)
8468
+ `).run(input.projectId, input.id, input.ownerPrincipal.type, input.ownerPrincipal.id, input.title ?? null, sandboxParams, input.runsOn ?? null, input.computer?.id ?? null, input.computer?.name ?? null, input.computer?.folder ?? null, input.status ?? "active", createdAt.toISOString(), updatedAt.toISOString());
8349
8469
  } catch (error) {
8350
8470
  if (isUniqueConstraintError(error)) {
8351
8471
  throw new AgentStorageError6("duplicate_id", `[SixbSqlite] Agent thread '${input.id}' already exists for project '${input.projectId}'.`);
@@ -8354,9 +8474,13 @@ class SqliteAgentThreadStore {
8354
8474
  }
8355
8475
  return this.requireThread(input.projectId, input.id);
8356
8476
  }
8477
+ activeRunOf(projectId, thread) {
8478
+ const row = thread?.activeRunId ? this.db.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?").get(projectId, thread.activeRunId) : null;
8479
+ return row ? rowToRunRecord(row) : null;
8480
+ }
8357
8481
  async fork(input) {
8358
8482
  return this.db.transaction(() => {
8359
- const source = this.db.query("SELECT id, kind, active_run_id FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.sourceThreadId);
8483
+ const source = this.db.query("SELECT id, kind, active_run_id, runs_on, moved_at FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.sourceThreadId);
8360
8484
  if (!source) {
8361
8485
  throw new AgentStorageError6("thread_not_found", `[SixbSqlite] Agent thread '${input.sourceThreadId}' not found for project '${input.projectId}'.`);
8362
8486
  }
@@ -8378,6 +8502,7 @@ class SqliteAgentThreadStore {
8378
8502
  ORDER BY summarized_through_seq DESC, created_at DESC, id DESC
8379
8503
  `).all(input.projectId, source.id);
8380
8504
  const plan = planAgentThreadFork(input, {
8505
+ workspace: { runsOn: source.runs_on, movedAt: source.moved_at },
8381
8506
  ...activeRun?.trigger_message_id ? { triggerMessageId: activeRun.trigger_message_id } : {},
8382
8507
  messages,
8383
8508
  checkpoints: checkpoints.map(rowToContextCheckpointRecord)
@@ -8480,6 +8605,10 @@ class SqliteAgentThreadStore {
8480
8605
  )`);
8481
8606
  args.push(input.participantPrincipal.type, input.participantPrincipal.id);
8482
8607
  }
8608
+ if (input.activeOnComputer) {
8609
+ whereClauses.push("computer_id = ? AND computer_folder = ? AND active_run_id IS NOT NULL");
8610
+ args.push(input.activeOnComputer.id, input.activeOnComputer.folder);
8611
+ }
8483
8612
  const dir = input.order === "asc" ? "ASC" : "DESC";
8484
8613
  const { rows, total, hasMore } = queryAgentList({
8485
8614
  db: this.db,
@@ -8527,16 +8656,40 @@ class SqliteAgentThreadStore {
8527
8656
  return result.changes > 0;
8528
8657
  }
8529
8658
  async assignComputer(input) {
8530
- assertComputerId(input.computerId, "SixbSqlite");
8531
- this.db.query(`
8532
- UPDATE agent_threads SET computer_id = ?
8533
- WHERE project_id = ? AND id = ? AND runs_on = 'computer'
8534
- ${input.ifUnassigned ? "AND computer_id IS NULL" : ""}
8535
- `).run(input.computerId, input.projectId, input.id);
8536
- const row = this.db.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
8537
- const thread = row ? rowToThreadRecord(row) : null;
8538
- assertAssignableComputer(thread, input.id, "SixbSqlite");
8539
- return thread;
8659
+ assertAgentThreadComputer(input.computer, "SixbSqlite");
8660
+ return this.db.transaction(() => {
8661
+ const row = this.db.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
8662
+ const thread = row ? rowToThreadRecord(row) : null;
8663
+ assertAssignableComputer(thread, this.activeRunOf(input.projectId, thread), input.computer, input.id, "SixbSqlite");
8664
+ const movedAt = movesComputerPlace(thread, input.computer) ? (input.movedAt ?? new Date).toISOString() : null;
8665
+ this.db.query(`
8666
+ UPDATE agent_threads
8667
+ SET computer_id = ?, computer_name = ?, computer_folder = ?,
8668
+ moved_at = COALESCE(?, moved_at)
8669
+ WHERE project_id = ? AND id = ?
8670
+ `).run(input.computer.id, input.computer.name, input.computer.folder, movedAt, input.projectId, input.id);
8671
+ return this.requireThread(input.projectId, input.id);
8672
+ }).immediate();
8673
+ }
8674
+ async moveToCloud(input) {
8675
+ return this.db.transaction(() => {
8676
+ const row = this.db.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
8677
+ const thread = row ? rowToThreadRecord(row) : null;
8678
+ const active = this.activeRunOf(input.projectId, thread);
8679
+ assertMovableToCloud(thread, active, input.id, "SixbSqlite");
8680
+ const queued = queuedTurnInCloud(active);
8681
+ if (queued) {
8682
+ this.db.query("UPDATE agent_runs SET spec = ? WHERE project_id = ? AND id = ?").run(JSON.stringify(queued.spec), input.projectId, queued.id);
8683
+ }
8684
+ this.db.query(`
8685
+ UPDATE agent_threads
8686
+ SET runs_on = NULL, computer_id = NULL, computer_name = NULL, computer_folder = NULL,
8687
+ moved_at = ?
8688
+ WHERE project_id = ? AND id = ?
8689
+ `).run((input.movedAt ?? new Date).toISOString(), input.projectId, input.id);
8690
+ const moved = this.requireThread(input.projectId, input.id);
8691
+ return { thread: moved, ...queued ? { queued } : {} };
8692
+ }).immediate();
8540
8693
  }
8541
8694
  async listParticipants(input) {
8542
8695
  return this.selectParticipants(input.projectId, [...new Set(input.threadIds)]);
@@ -8572,10 +8725,11 @@ class SqliteAgentThreadStore {
8572
8725
  active_run_id,
8573
8726
  last_message_at,
8574
8727
  message_count,
8728
+ moved_at,
8575
8729
  created_at,
8576
8730
  updated_at
8577
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
8578
- `).run(thread.projectId, thread.id, thread.ownerPrincipal.type, thread.ownerPrincipal.id, thread.title ?? null, thread.sandboxParams === undefined ? null : JSON.stringify(thread.sandboxParams), thread.sandboxState === undefined ? null : JSON.stringify(thread.sandboxState), thread.status, thread.lastMessageAt?.toISOString() ?? null, thread.messageCount, thread.createdAt.toISOString(), thread.updatedAt.toISOString());
8731
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?)
8732
+ `).run(thread.projectId, thread.id, thread.ownerPrincipal.type, thread.ownerPrincipal.id, thread.title ?? null, thread.sandboxParams === undefined ? null : JSON.stringify(thread.sandboxParams), thread.sandboxState === undefined ? null : JSON.stringify(thread.sandboxState), thread.status, thread.lastMessageAt?.toISOString() ?? null, thread.messageCount, thread.movedAt?.toISOString() ?? null, thread.createdAt.toISOString(), thread.updatedAt.toISOString());
8579
8733
  } catch (error) {
8580
8734
  if (isUniqueConstraintError(error)) {
8581
8735
  throw new AgentStorageError6("duplicate_id", `[SixbSqlite] Agent thread '${thread.id}' already exists for project '${thread.projectId}'.`);
@@ -8623,6 +8777,53 @@ class SqliteAgentThreadStore {
8623
8777
  }
8624
8778
  }
8625
8779
 
8780
+ // src/agents/token-rates.ts
8781
+ import {
8782
+ AGENT_TOKEN_RATE_SAMPLES,
8783
+ assertAgentTokenRateRecord
8784
+ } from "@sixb/core/internal/agent-run-storage-provider";
8785
+ class SqliteAgentTokenRateStore {
8786
+ db;
8787
+ constructor(db) {
8788
+ this.db = db;
8789
+ }
8790
+ async record(input) {
8791
+ assertAgentTokenRateRecord(input, "SixbSqlite");
8792
+ runImmediateTransaction(this.db, () => {
8793
+ this.db.query(`INSERT INTO agent_token_rates (
8794
+ project_id, run_id, provider_id, model_id, bytes, tokens, measured_at
8795
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
8796
+ ON CONFLICT (project_id, run_id) DO UPDATE SET
8797
+ provider_id = excluded.provider_id, model_id = excluded.model_id,
8798
+ bytes = excluded.bytes, tokens = excluded.tokens, measured_at = excluded.measured_at`).run(input.projectId, input.runId, input.providerId, input.modelId, input.bytes, input.tokens, input.measuredAt.toISOString());
8799
+ this.db.query(`DELETE FROM agent_token_rates
8800
+ WHERE project_id = ?1 AND provider_id = ?2 AND model_id = ?3
8801
+ AND run_id NOT IN (
8802
+ SELECT run_id FROM agent_token_rates
8803
+ WHERE project_id = ?1 AND provider_id = ?2 AND model_id = ?3
8804
+ ORDER BY measured_at DESC, run_id DESC
8805
+ LIMIT ?4
8806
+ )`).run(input.projectId, input.providerId, input.modelId, AGENT_TOKEN_RATE_SAMPLES);
8807
+ });
8808
+ }
8809
+ async listLatest(input) {
8810
+ const rows = this.db.query(`SELECT project_id, run_id, provider_id, model_id, bytes, tokens, measured_at
8811
+ FROM agent_token_rates
8812
+ WHERE project_id = ? AND provider_id = ? AND model_id = ?
8813
+ ORDER BY measured_at DESC, run_id DESC
8814
+ LIMIT ?`).all(input.projectId, input.providerId, input.modelId, AGENT_TOKEN_RATE_SAMPLES);
8815
+ return rows.map((row) => ({
8816
+ projectId: row.project_id,
8817
+ providerId: row.provider_id,
8818
+ modelId: row.model_id,
8819
+ runId: row.run_id,
8820
+ bytes: row.bytes,
8821
+ tokens: row.tokens,
8822
+ measuredAt: new Date(row.measured_at)
8823
+ }));
8824
+ }
8825
+ }
8826
+
8626
8827
  // src/agents/agent-storage.ts
8627
8828
  class SqliteAgentStorage {
8628
8829
  threads;
@@ -8632,6 +8833,8 @@ class SqliteAgentStorage {
8632
8833
  memory;
8633
8834
  approvals;
8634
8835
  approvalModes;
8836
+ computers;
8837
+ tokenRates;
8635
8838
  connection;
8636
8839
  constructor(options) {
8637
8840
  this.connection = openSqliteStoreConnection(options);
@@ -8642,6 +8845,8 @@ class SqliteAgentStorage {
8642
8845
  this.memory = new SqliteAgentMemoryStore(this.connection.db);
8643
8846
  this.approvals = new SqliteAgentApprovalStore(this.connection.db);
8644
8847
  this.approvalModes = new SqliteAgentApprovalModeStore(this.connection.db);
8848
+ this.computers = new SqliteAgentComputerSessionStore(this.connection.db);
8849
+ this.tokenRates = new SqliteAgentTokenRateStore(this.connection.db);
8645
8850
  }
8646
8851
  close() {
8647
8852
  closeSqliteStoreConnection(this.connection);
@@ -8665,15 +8870,52 @@ import {
8665
8870
  AiCostStorageError
8666
8871
  } from "@sixb/core/storage";
8667
8872
 
8873
+ // src/ai-accounting-filters.ts
8874
+ var SQLITE_SOURCE_WORKFLOW = `COALESCE(
8875
+ workflow_node.workflow_id,
8876
+ CASE WHEN execution.executor_kind = 'workflow' THEN execution.authority_primitive_id END
8877
+ )`;
8878
+ var SQLITE_SOURCE_KIND = `CASE
8879
+ WHEN direct_agent.id IS NOT NULL THEN 'agent'
8880
+ WHEN ${SQLITE_SOURCE_WORKFLOW} IS NOT NULL THEN 'workflow'
8881
+ WHEN execution.executor_kind = 'action' THEN 'action'
8882
+ ELSE 'other'
8883
+ END`;
8884
+ function sqliteAccountingFilter(columns) {
8885
+ return `(? IS NULL OR ${columns.kind} = ?)
8886
+ AND (? IS NULL OR ${columns.sourceKind} = ?)
8887
+ AND (? IS NULL OR ${columns.sourceWorkflow} = ?)
8888
+ AND (
8889
+ ? IS NULL
8890
+ OR (? = 'none' AND ${columns.userId} IS NULL AND ${columns.serviceAccountId} IS NULL)
8891
+ OR (? = 'user' AND ${columns.userId} = ?)
8892
+ OR (? = 'serviceAccount' AND ${columns.serviceAccountId} = ?)
8893
+ )`;
8894
+ }
8895
+ function sqliteAccountingFilterParameters(query) {
8896
+ const kind = query.modelKind ?? null;
8897
+ const source = query.source?.kind ?? null;
8898
+ const workflow = query.source?.kind === "workflow" ? query.source.workflowId ?? null : null;
8899
+ const mode = query.requester === undefined ? null : query.requester === "none" ? "none" : query.requester.type;
8900
+ const id = query.requester === undefined || query.requester === "none" ? null : query.requester.id;
8901
+ return [kind, kind, source, source, workflow, workflow, mode, mode, mode, id, mode, id];
8902
+ }
8903
+
8668
8904
  // src/ai-cost-groups.ts
8669
8905
  import {
8670
8906
  aiModelCallGroupFragmentFromRow,
8671
8907
  buildAiModelCallGroupsFromFragments,
8672
8908
  normalizeAiModelCallAccountingQuery,
8909
+ normalizeAiModelCallGroupSort,
8673
8910
  TOKENLESS_MODEL_KINDS
8674
8911
  } from "@sixb/core/internal/ai-cost-storage-provider";
8912
+ function numbered(sql, start) {
8913
+ let next = start;
8914
+ return sql.replace(/\?(?!\d)/g, () => `?${next++}`);
8915
+ }
8675
8916
  async function listAiModelCallGroups(connection, input) {
8676
8917
  const query = normalizeAiModelCallAccountingQuery(input);
8918
+ const sort = normalizeAiModelCallGroupSort(input.sort);
8677
8919
  const parameters = [
8678
8920
  query.projectId,
8679
8921
  query.from.toISOString(),
@@ -8682,20 +8924,28 @@ async function listAiModelCallGroups(connection, input) {
8682
8924
  query.modelId ?? null,
8683
8925
  query.valuationStatus ?? null
8684
8926
  ];
8685
- const count = connection.db.query(COUNT_SQL).get(...parameters);
8686
- const rows = connection.db.query(PAGE_SQL).all(...parameters, query.limit, query.offset);
8927
+ const filters = sqliteAccountingFilterParameters(query);
8928
+ const count = connection.db.query(COUNT_SQL).get(...parameters, ...filters);
8929
+ const rows = connection.db.query(PAGE_SQL).all(...parameters, ...filters, query.limit, query.offset, sort);
8687
8930
  const total = Number(count?.total);
8688
8931
  if (!Number.isSafeInteger(total) || total < 0)
8689
8932
  throw new RangeError("[Sixb] Invalid model-call group count.");
8690
- const items = buildAiModelCallGroupsFromFragments(rows.map(aiModelCallGroupFragmentFromRow));
8933
+ const items = buildAiModelCallGroupsFromFragments(rows.map(aiModelCallGroupFragmentFromRow), sort);
8691
8934
  return { items, total, hasMore: query.offset + items.length < total };
8692
8935
  }
8693
8936
  var TOKENLESS_KINDS = TOKENLESS_MODEL_KINDS.map((kind) => `'${kind}'`).join(", ");
8694
8937
  var FILTERED_SQL = `
8695
8938
  WITH filtered AS (
8696
8939
  SELECT COALESCE(root_agent.execution_id, usage.execution_id) AS root_execution_id,
8940
+ -- What the call is listed under, as aiModelCallGroupKey computes it.
8941
+ CASE
8942
+ WHEN root_agent.thread_id IS NOT NULL THEN 'thread:' || root_agent.thread_id
8943
+ WHEN workflow_node.workflow_run_id IS NOT NULL THEN 'workflowRun:' || workflow_node.workflow_run_id
8944
+ ELSE COALESCE(root_agent.execution_id, usage.execution_id)
8945
+ END AS group_key,
8697
8946
  usage.execution_id, usage.provider_id, usage.requested_model_id, usage.occurred_at,
8698
- usage.total_tokens, usage.model_kind, usage.output_images, usage.input_characters,
8947
+ usage.total_tokens, usage.input_tokens, usage.cache_read_input_tokens,
8948
+ usage.model_kind, usage.output_images, usage.input_characters,
8699
8949
  usage.audio_duration_ms,
8700
8950
  execution.executor_kind, execution.executor_id, execution.authority_primitive_id AS primitive_id,
8701
8951
  COALESCE(cost.status, 'unvalued') AS valuation_status,
@@ -8708,7 +8958,8 @@ WITH filtered AS (
8708
8958
  workflow_node.node_id AS workflow_agent_step_id,
8709
8959
  workflow_agent.node_run_id AS workflow_node_run_id,
8710
8960
  workflow_node.workflow_id AS workflow_id,
8711
- workflow_node.workflow_run_id AS workflow_run_id
8961
+ workflow_node.workflow_run_id AS workflow_run_id,
8962
+ execution.requested_by_user_id, execution.requested_by_service_account_id
8712
8963
  FROM ai_model_call_usage AS usage
8713
8964
  JOIN executions AS execution ON execution.project_id = usage.project_id AND execution.id = usage.execution_id
8714
8965
  LEFT JOIN agent_runs AS direct_agent
@@ -8728,23 +8979,33 @@ WITH filtered AS (
8728
8979
  AND (?4 IS NULL OR usage.provider_id = ?4)
8729
8980
  AND (?5 IS NULL OR usage.requested_model_id = ?5)
8730
8981
  AND (?6 IS NULL OR COALESCE(cost.status, 'unvalued') = ?6)
8982
+ AND ${numbered(sqliteAccountingFilter({
8983
+ kind: "usage.model_kind",
8984
+ sourceKind: SQLITE_SOURCE_KIND,
8985
+ sourceWorkflow: SQLITE_SOURCE_WORKFLOW,
8986
+ userId: "execution.requested_by_user_id",
8987
+ serviceAccountId: "execution.requested_by_service_account_id"
8988
+ }), 7)}
8731
8989
  ), roots AS (
8732
- SELECT root_execution_id, MAX(occurred_at) AS last_call_at
8733
- FROM filtered GROUP BY root_execution_id
8990
+ SELECT group_key, MAX(occurred_at) AS last_call_at, TOTAL(amount_nanos) AS cost_nanos
8991
+ FROM filtered GROUP BY group_key
8734
8992
  )
8735
8993
  `;
8736
8994
  var COUNT_SQL = `${FILTERED_SQL} SELECT CAST(COUNT(*) AS TEXT) AS total FROM roots`;
8737
8995
  var PAGE_SQL = `${FILTERED_SQL}, page AS (
8738
- SELECT root_execution_id FROM roots
8739
- ORDER BY last_call_at DESC, root_execution_id ASC LIMIT ?7 OFFSET ?8
8996
+ SELECT group_key FROM roots
8997
+ ORDER BY CASE WHEN ?21 = 'cost' THEN cost_nanos END DESC, last_call_at DESC, group_key ASC
8998
+ LIMIT ?19 OFFSET ?20
8740
8999
  )
8741
- SELECT filtered.root_execution_id, execution_id, provider_id, requested_model_id,
9000
+ SELECT filtered.group_key, root_execution_id, execution_id, provider_id, requested_model_id,
8742
9001
  MIN(occurred_at) AS first_call_at, MAX(occurred_at) AS last_call_at,
8743
9002
  CAST(COUNT(*) AS TEXT) AS call_count,
8744
9003
  CAST(COUNT(total_tokens) AS TEXT) AS token_count,
8745
9004
  CAST(COUNT(CASE WHEN total_tokens IS NULL AND model_kind IN (${TOKENLESS_KINDS}) THEN 1 END) AS TEXT)
8746
9005
  AS tokenless_count,
8747
9006
  CAST(SUM(total_tokens) AS TEXT) AS total_tokens,
9007
+ CAST(SUM(input_tokens) AS TEXT) AS input_tokens,
9008
+ CAST(SUM(cache_read_input_tokens) AS TEXT) AS cache_read_input_tokens,
8748
9009
  CAST(SUM(output_images) AS TEXT) AS output_images,
8749
9010
  CAST(SUM(input_characters) AS TEXT) AS input_characters,
8750
9011
  CAST(SUM(audio_duration_ms) AS TEXT) AS audio_duration_ms,
@@ -8761,9 +9022,11 @@ SELECT filtered.root_execution_id, execution_id, provider_id, requested_model_id
8761
9022
  MAX(workflow_agent_step_id) AS workflow_agent_step_id,
8762
9023
  MAX(workflow_node_run_id) AS workflow_node_run_id,
8763
9024
  MAX(workflow_id) AS workflow_id,
8764
- MAX(workflow_run_id) AS workflow_run_id
8765
- FROM filtered JOIN page ON page.root_execution_id = filtered.root_execution_id
8766
- GROUP BY filtered.root_execution_id, execution_id, provider_id, requested_model_id,
9025
+ MAX(workflow_run_id) AS workflow_run_id,
9026
+ MAX(requested_by_user_id) AS requested_by_user_id,
9027
+ MAX(requested_by_service_account_id) AS requested_by_service_account_id
9028
+ FROM filtered JOIN page ON page.group_key = filtered.group_key
9029
+ GROUP BY filtered.group_key, root_execution_id, execution_id, provider_id, requested_model_id,
8767
9030
  valuation_status, amount_currency
8768
9031
  `;
8769
9032
 
@@ -9067,7 +9330,7 @@ class SqliteAiCostStorage {
9067
9330
  }
9068
9331
  async queryProjectOverview(input) {
9069
9332
  const query = normalizeAiAccountingQuery(input);
9070
- const rows = this.connection.db.query(SQLITE_ACCOUNTING_OVERVIEW_SQL).all(query.projectId, query.from.toISOString(), query.to.toISOString(), query.providerId ?? null, query.providerId ?? null, query.modelId ?? null, query.modelId ?? null, input.bucket);
9333
+ const rows = this.connection.db.query(SQLITE_ACCOUNTING_OVERVIEW_SQL).all(query.projectId, query.from.toISOString(), query.to.toISOString(), query.providerId ?? null, query.providerId ?? null, query.modelId ?? null, query.modelId ?? null, ...sqliteAccountingFilterParameters(query), input.bucket, input.bucket);
9071
9334
  return buildAiAccountingOverviewFromFragments(input, rows.map(aggregateFragmentFromRow));
9072
9335
  }
9073
9336
  async listModelCalls(input) {
@@ -9114,12 +9377,32 @@ var SQLITE_ACCOUNTING_LIST_FILTER = `
9114
9377
  ELSE 'unvalued'
9115
9378
  END
9116
9379
  )
9380
+ AND ${sqliteAccountingFilter({
9381
+ kind: "usage.model_kind",
9382
+ sourceKind: SQLITE_SOURCE_KIND,
9383
+ sourceWorkflow: SQLITE_SOURCE_WORKFLOW,
9384
+ userId: "execution.requested_by_user_id",
9385
+ serviceAccountId: "execution.requested_by_service_account_id"
9386
+ })}
9387
+ `;
9388
+ var SQLITE_ACCOUNTING_LIST_JOINS = `
9389
+ JOIN executions AS execution ON execution.project_id = usage.project_id AND execution.id = usage.execution_id
9390
+ LEFT JOIN agent_runs AS direct_agent
9391
+ ON direct_agent.project_id = usage.project_id
9392
+ AND direct_agent.execution_id = usage.execution_id
9393
+ LEFT JOIN workflow_agent_node_runs AS workflow_agent
9394
+ ON workflow_agent.project_id = usage.project_id
9395
+ AND workflow_agent.execution_id = usage.execution_id
9396
+ LEFT JOIN workflow_node_runs AS workflow_node
9397
+ ON workflow_node.project_id = workflow_agent.project_id
9398
+ AND workflow_node.id = workflow_agent.node_run_id
9399
+ LEFT JOIN ai_model_call_valuations AS cost
9400
+ ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
9117
9401
  `;
9118
9402
  var SQLITE_ACCOUNTING_LIST_COUNT_SQL = `
9119
9403
  SELECT CAST(COUNT(*) AS TEXT) AS total
9120
9404
  FROM ai_model_call_usage AS usage
9121
- LEFT JOIN ai_model_call_valuations AS cost
9122
- ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
9405
+ ${SQLITE_ACCOUNTING_LIST_JOINS}
9123
9406
  WHERE ${SQLITE_ACCOUNTING_LIST_FILTER}
9124
9407
  `;
9125
9408
  var SQLITE_ACCOUNTING_LIST_PAGE_SQL = `
@@ -9154,18 +9437,7 @@ var SQLITE_ACCOUNTING_LIST_PAGE_SQL = `
9154
9437
  cost.details AS cost_details,
9155
9438
  cost.rated_at AS cost_rated_at
9156
9439
  FROM ai_model_call_usage AS usage
9157
- JOIN executions AS execution ON execution.project_id = usage.project_id AND execution.id = usage.execution_id
9158
- LEFT JOIN agent_runs AS direct_agent
9159
- ON direct_agent.project_id = usage.project_id
9160
- AND direct_agent.execution_id = usage.execution_id
9161
- LEFT JOIN workflow_agent_node_runs AS workflow_agent
9162
- ON workflow_agent.project_id = usage.project_id
9163
- AND workflow_agent.execution_id = usage.execution_id
9164
- LEFT JOIN workflow_node_runs AS workflow_node
9165
- ON workflow_node.project_id = workflow_agent.project_id
9166
- AND workflow_node.id = workflow_agent.node_run_id
9167
- LEFT JOIN ai_model_call_valuations AS cost
9168
- ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
9440
+ ${SQLITE_ACCOUNTING_LIST_JOINS}
9169
9441
  WHERE ${SQLITE_ACCOUNTING_LIST_FILTER}
9170
9442
  ORDER BY usage.occurred_at DESC, usage.id ASC
9171
9443
  LIMIT ? OFFSET ?
@@ -9204,7 +9476,10 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
9204
9476
  ELSE 'unvalued'
9205
9477
  END AS valuation_status,
9206
9478
  CASE WHEN cost.status = 'rated' THEN cost.currency ELSE NULL END AS amount_currency,
9207
- CASE WHEN cost.status = 'rated' THEN cost.amount_nanos ELSE NULL END AS amount_nanos
9479
+ CASE WHEN cost.status = 'rated' THEN cost.amount_nanos ELSE NULL END AS amount_nanos,
9480
+ execution.requested_by_user_id AS requester_user_id,
9481
+ execution.requested_by_service_account_id AS requester_service_account_id,
9482
+ ${SQLITE_SOURCE_KIND} AS source_kind
9208
9483
  FROM ai_model_call_usage AS usage
9209
9484
  JOIN executions AS execution ON execution.project_id = usage.project_id AND execution.id = usage.execution_id
9210
9485
  LEFT JOIN agent_runs AS direct_agent
@@ -9221,10 +9496,19 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
9221
9496
  WHERE usage.project_id = ? AND usage.occurred_at >= ? AND usage.occurred_at < ?
9222
9497
  AND (? IS NULL OR usage.provider_id = ?)
9223
9498
  AND (? IS NULL OR usage.requested_model_id = ?)
9499
+ ), filtered AS (
9500
+ SELECT * FROM classified
9501
+ WHERE ${sqliteAccountingFilter({
9502
+ kind: "model_kind",
9503
+ sourceKind: "source_kind",
9504
+ sourceWorkflow: "attribution_workflow_id",
9505
+ userId: "requester_user_id",
9506
+ serviceAccountId: "requester_service_account_id"
9507
+ })}
9224
9508
  ), expanded AS (
9225
9509
  SELECT 'totals' AS dimension, NULL AS key_1, NULL AS key_2, NULL AS key_3, NULL AS bucket_start,
9226
- classified.*
9227
- FROM classified
9510
+ filtered.*
9511
+ FROM filtered
9228
9512
  UNION ALL
9229
9513
  SELECT 'series', NULL, NULL, NULL,
9230
9514
  CASE ?
@@ -9236,18 +9520,39 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
9236
9520
  printf('-%d days', (CAST(strftime('%w', occurred_at) AS INTEGER) + 6) % 7)
9237
9521
  )
9238
9522
  END,
9239
- classified.*
9240
- FROM classified
9523
+ filtered.*
9524
+ FROM filtered
9241
9525
  UNION ALL
9242
- SELECT 'model', provider_id, requested_model_id, NULL, NULL, classified.* FROM classified
9526
+ SELECT 'modelSeries', provider_id, requested_model_id, NULL,
9527
+ CASE ?
9528
+ WHEN 'hour' THEN strftime('%Y-%m-%dT%H:00:00.000Z', occurred_at)
9529
+ WHEN 'day' THEN strftime('%Y-%m-%dT00:00:00.000Z', occurred_at)
9530
+ ELSE strftime(
9531
+ '%Y-%m-%dT00:00:00.000Z',
9532
+ occurred_at,
9533
+ printf('-%d days', (CAST(strftime('%w', occurred_at) AS INTEGER) + 6) % 7)
9534
+ )
9535
+ END,
9536
+ filtered.*
9537
+ FROM filtered
9538
+ UNION ALL
9539
+ SELECT 'model', provider_id, requested_model_id, NULL, NULL, filtered.* FROM filtered
9540
+ UNION ALL
9541
+ SELECT 'person',
9542
+ CASE
9543
+ WHEN requester_user_id IS NOT NULL THEN 'user'
9544
+ WHEN requester_service_account_id IS NOT NULL THEN 'serviceAccount'
9545
+ END,
9546
+ COALESCE(requester_user_id, requester_service_account_id), NULL, NULL, filtered.*
9547
+ FROM filtered
9243
9548
  UNION ALL
9244
- SELECT 'kind', model_kind, NULL, NULL, NULL, classified.* FROM classified
9549
+ SELECT 'kind', model_kind, NULL, NULL, NULL, filtered.* FROM filtered
9245
9550
  UNION ALL
9246
- SELECT 'agent', attribution_agent_kind, attribution_workflow_id, attribution_agent_step_id, NULL, classified.*
9247
- FROM classified WHERE attribution_agent_kind IS NOT NULL
9551
+ SELECT 'agent', attribution_agent_kind, attribution_workflow_id, attribution_agent_step_id, NULL, filtered.*
9552
+ FROM filtered WHERE attribution_agent_kind IS NOT NULL
9248
9553
  UNION ALL
9249
- SELECT 'workflow', attribution_workflow_id, NULL, NULL, NULL, classified.*
9250
- FROM classified WHERE attribution_workflow_id IS NOT NULL
9554
+ SELECT 'workflow', attribution_workflow_id, NULL, NULL, NULL, filtered.*
9555
+ FROM filtered WHERE attribution_workflow_id IS NOT NULL
9251
9556
  )
9252
9557
  SELECT dimension,
9253
9558
  key_1,
@@ -9308,11 +9613,16 @@ function accountingListFilterParameters(query) {
9308
9613
  query.executionId ?? null,
9309
9614
  query.executionId ?? null,
9310
9615
  query.valuationStatus ?? null,
9311
- query.valuationStatus ?? null
9616
+ query.valuationStatus ?? null,
9617
+ ...sqliteAccountingFilterParameters(query)
9312
9618
  ];
9313
9619
  }
9314
9620
  function aggregateFragmentFromRow(row) {
9315
- const dimensionKeys = row.dimension === "model" ? { providerId: required(row.key_1), modelId: required(row.key_2) } : row.dimension === "kind" ? withModelKind(aiAccountingModelKind(row.key_1)) : row.dimension === "agent" ? {
9621
+ const dimensionKeys = row.dimension === "model" ? { providerId: required(row.key_1), modelId: required(row.key_2) } : row.dimension === "modelSeries" ? {
9622
+ providerId: required(row.key_1),
9623
+ modelId: required(row.key_2),
9624
+ start: new Date(required(row.bucket_start))
9625
+ } : row.dimension === "person" ? row.key_1 && row.key_2 ? { requesterType: row.key_1, requesterId: row.key_2 } : {} : row.dimension === "kind" ? withModelKind(aiAccountingModelKind(row.key_1)) : row.dimension === "agent" ? {
9316
9626
  agentKind: required(row.key_1),
9317
9627
  ...row.key_1 === "workflowAgent" ? { workflowId: required(row.key_2), agentStepId: required(row.key_3) } : {}
9318
9628
  } : row.dimension === "workflow" ? { workflowId: required(row.key_1) } : row.dimension === "series" ? { start: new Date(required(row.bucket_start)) } : {};
@@ -13263,6 +13573,7 @@ import {
13263
13573
  import {
13264
13574
  hasVectorProfile,
13265
13575
  isVectorProfileQuery,
13576
+ linkEndpointObjectTypeIds,
13266
13577
  normalizeVector,
13267
13578
  vectorSearchCandidateLimit
13268
13579
  } from "@sixb/core/internal/query";
@@ -13324,7 +13635,7 @@ function compileObjectQueryInternal(projectId, query, ctx) {
13324
13635
  case "page":
13325
13636
  return compilePage(projectId, query.input, query.pageSize, query.pageToken, ctx);
13326
13637
  case "traverse":
13327
- return compileTraversal(projectId, query.input, query.linkId, query.direction, query.sourceObjectTypeId, ctx);
13638
+ return compileTraversal(projectId, query, ctx);
13328
13639
  case "set":
13329
13640
  return compileSet(projectId, query.op, query.inputs, ctx);
13330
13641
  case "project":
@@ -13605,9 +13916,11 @@ function compilePage(projectId, inputQuery, rawPageSize, pageToken, ctx) {
13605
13916
  }
13606
13917
  };
13607
13918
  }
13608
- function compileTraversal(projectId, inputQuery, linkId, direction, sourceObjectTypeId, ctx) {
13609
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
13919
+ function compileTraversal(projectId, traversal, ctx) {
13920
+ const { linkId, direction, sourceObjectTypeId } = traversal;
13921
+ const input = compileObjectQueryInternal(projectId, traversal.input, exactContext(ctx));
13610
13922
  const outputAlias = direction === "outgoing" ? "target_object" : "source_object";
13923
+ const endpointType = compileEndpointTypes(outputAlias, linkEndpointObjectTypeIds(traversal));
13611
13924
  const joinSql = direction === "outgoing" ? `
13612
13925
  JOIN ${ctx.source.linksTable} AS edge
13613
13926
  ON edge.project_id = input.project_id
@@ -13618,6 +13931,7 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
13618
13931
  ON target_object.project_id = edge.project_id
13619
13932
  AND target_object.object_type_id = edge.target_type_id
13620
13933
  AND target_object.primary_id = edge.target_id
13934
+ ${endpointType.sql}
13621
13935
  ` : `
13622
13936
  JOIN ${ctx.source.linksTable} AS edge
13623
13937
  ON edge.project_id = input.project_id
@@ -13629,6 +13943,7 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
13629
13943
  ON source_object.project_id = edge.project_id
13630
13944
  AND source_object.object_type_id = edge.source_type_id
13631
13945
  AND source_object.primary_id = edge.source_id
13946
+ ${endpointType.sql}
13632
13947
  `;
13633
13948
  const order = compileOrder(identityOrderFields());
13634
13949
  const qualifiedOrder = compileOrder(identityOrderFields(), outputAlias);
@@ -13641,7 +13956,8 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
13641
13956
  const args = [
13642
13957
  ...input.args,
13643
13958
  linkId,
13644
- ...sourceObjectTypeId === undefined ? [] : [sourceObjectTypeId],
13959
+ ...direction === "incoming" && sourceObjectTypeId !== undefined ? [sourceObjectTypeId] : [],
13960
+ ...endpointType.args,
13645
13961
  ...qualifiedOrder.args
13646
13962
  ];
13647
13963
  return {
@@ -13657,6 +13973,14 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
13657
13973
  }
13658
13974
  };
13659
13975
  }
13976
+ function compileEndpointTypes(alias, objectTypeIds) {
13977
+ if (!objectTypeIds?.length)
13978
+ return { sql: "", args: [] };
13979
+ return {
13980
+ sql: `AND ${alias}.object_type_id IN (${objectTypeIds.map(() => "?").join(", ")})`,
13981
+ args: [...objectTypeIds]
13982
+ };
13983
+ }
13660
13984
  function compileExpand(projectId, inputQuery, expansions, ctx) {
13661
13985
  const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
13662
13986
  const expand = compileExpansionsObject(expansions, { project: "input.project_id", type: "input.object_type_id", id: "input.primary_id" }, "", ctx);
@@ -13707,7 +14031,8 @@ function compileExpansionValue(expansion, parent, path, ctx) {
13707
14031
  `${parentId} = ${parent.id}`,
13708
14032
  `${edge}.link_id = ?`
13709
14033
  ];
13710
- const whereArgs = [expansion.linkId];
14034
+ const endpointType = compileEndpointTypes(target, expansion.endpointObjectTypeIds);
14035
+ const whereArgs = [...endpointType.args, expansion.linkId];
13711
14036
  if (incoming && expansion.sourceObjectTypeId !== undefined) {
13712
14037
  whereParts.push(`${edge}.source_type_id = ?`);
13713
14038
  whereArgs.push(expansion.sourceObjectTypeId);
@@ -13718,6 +14043,7 @@ function compileExpansionValue(expansion, parent, path, ctx) {
13718
14043
  ON ${target}.project_id = ${edge}.project_id
13719
14044
  AND ${target}.object_type_id = ${neighborType}
13720
14045
  AND ${target}.primary_id = ${neighborId}
14046
+ ${endpointType.sql}
13721
14047
  WHERE ${whereParts.join(" AND ")}
13722
14048
  `;
13723
14049
  if (expansion.cardinality === "one") {
@@ -15620,6 +15946,9 @@ class SqliteOntologyCommitStorage {
15620
15946
  assertNonnegativeInteger(offset, "Ontology commit list offset");
15621
15947
  if (limit !== -1)
15622
15948
  assertNonnegativeInteger(limit, "Ontology commit list limit");
15949
+ if (input.committedAt !== undefined) {
15950
+ assertTimestamp(input.committedAt, "Ontology commit list committedAt");
15951
+ }
15623
15952
  const filters = ["project_id = ?"];
15624
15953
  const values = [input.projectId];
15625
15954
  if (input.run?.kind === "action") {
@@ -15629,6 +15958,10 @@ class SqliteOntologyCommitStorage {
15629
15958
  filters.push("origin_run_id = ? AND origin_kind IN ('projection', 'telemetry')");
15630
15959
  values.push(input.run.id);
15631
15960
  }
15961
+ if (input.committedAt !== undefined) {
15962
+ filters.push("committed_at = ?");
15963
+ values.push(input.committedAt);
15964
+ }
15632
15965
  const where = filters.join(" AND ");
15633
15966
  const totalRow = this.db.query(`SELECT COUNT(*) AS total FROM ontology_commits WHERE ${where}`).get(...values);
15634
15967
  const direction = input.order === "desc" ? "DESC" : "ASC";
@@ -15680,6 +16013,7 @@ import {
15680
16013
  invalidCorrelation as invalidCorrelation2,
15681
16014
  materializationOutboxWrite,
15682
16015
  materializationPlanChunk,
16016
+ prepareMaterializationStateRead,
15683
16017
  sameNonnegativeCounts as sameCounts,
15684
16018
  uniqueSorted
15685
16019
  } from "@sixb/core/internal/ontology-storage-provider";
@@ -17851,6 +18185,17 @@ class SqliteOntologyMaterializationStorage {
17851
18185
  }
17852
18186
  }
17853
18187
  }
18188
+ async readState(input) {
18189
+ const request = prepareMaterializationStateRead(input);
18190
+ return runDeferredReadTransaction(this.db, () => {
18191
+ const reader = new SqliteMaterializationStateReader(this.db, request.projectId);
18192
+ return {
18193
+ objects: reader.objectStates(request.objects),
18194
+ links: reader.linkStates(request.links),
18195
+ linkScopes: reader.linkSlotStates(request.linkScopes)
18196
+ };
18197
+ });
18198
+ }
17854
18199
  async stageWork(input) {
17855
18200
  this.sessions.stage(input);
17856
18201
  await yieldSqliteEventLoop();
@@ -18973,7 +19318,10 @@ class SqliteOntologyStorage {
18973
19318
  // src/pipeline-run-storage.ts
18974
19319
  import { parseSixbFailure as parseSixbFailure4, serializeSixbFailure as serializeSixbFailure3 } from "@sixb/core/internal/errors";
18975
19320
  import { assertPipelineRunExecution } from "@sixb/core/internal/pipeline-run-storage-provider";
18976
- import { canRequeuePipelineRunAfterEnqueueFailure } from "@sixb/core/internal/storage";
19321
+ import {
19322
+ canFinishPipelineRun,
19323
+ canRequeuePipelineRunAfterEnqueueFailure
19324
+ } from "@sixb/core/internal/storage";
18977
19325
  import { PIPELINE_RUN_FAILURE_CODES, PipelineRunError } from "@sixb/core/storage";
18978
19326
 
18979
19327
  // src/latest-run-query.ts
@@ -19089,8 +19437,7 @@ class SqlitePipelineRunStorage {
19089
19437
  if (!existing) {
19090
19438
  throw new PipelineRunError(`[SixbSqlite] Pipeline run '${input.id}' not found for project '${input.projectId}'.`);
19091
19439
  }
19092
- const enqueueFailure = input.status === "failed" && input.error.code === "queue.enqueue_failed";
19093
- if (enqueueFailure && existing.status !== "queued" || !enqueueFailure && existing.status !== "running") {
19440
+ if (!canFinishPipelineRun(existing.status, input)) {
19094
19441
  throw new PipelineRunError(`[SixbSqlite] Pipeline run '${input.id}' cannot finish from status '${existing.status}'.`);
19095
19442
  }
19096
19443
  this.db.query(`
@@ -20301,7 +20648,7 @@ function invalidRecord2(message) {
20301
20648
 
20302
20649
  // src/sync-run-storage.ts
20303
20650
  import { parseSixbFailure as parseSixbFailure5, serializeSixbFailure as serializeSixbFailure5 } from "@sixb/core/internal/errors";
20304
- import { canRequeueSyncRunAfterEnqueueFailure } from "@sixb/core/internal/storage";
20651
+ import { canFinishSyncRun, canRequeueSyncRunAfterEnqueueFailure } from "@sixb/core/internal/storage";
20305
20652
  import { assertSyncRunExecution } from "@sixb/core/internal/sync-run-storage-provider";
20306
20653
  import { SYNC_RUN_FAILURE_CODES, SyncRunError } from "@sixb/core/storage";
20307
20654
  class SqliteSyncRunStorage {
@@ -20389,8 +20736,7 @@ class SqliteSyncRunStorage {
20389
20736
  if (!existing) {
20390
20737
  throw new SyncRunError(`[SixbSqlite] Sync run '${input.id}' not found for project '${input.projectId}'.`);
20391
20738
  }
20392
- const enqueueFailure = input.status === "failed" && input.error.code === "queue.enqueue_failed";
20393
- if (enqueueFailure && existing.status !== "queued" || !enqueueFailure && existing.status !== "running") {
20739
+ if (!canFinishSyncRun(existing.status, input)) {
20394
20740
  throw new SyncRunError(`[SixbSqlite] Sync run '${input.id}' cannot finish from status '${existing.status}'.`);
20395
20741
  }
20396
20742
  if (input.status === "succeeded") {
@@ -21865,4 +22211,4 @@ export {
21865
22211
  migrateSqliteStorage
21866
22212
  };
21867
22213
 
21868
- //# debugId=21418853CB8E412664756E2164756E21
22214
+ //# debugId=70BDC079F392018764756E2164756E21