@sixb/sqlite 0.1.25 → 0.2.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.
Files changed (44) 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/messages.d.ts.map +1 -1
  6. package/dist/agents/rows.d.ts +6 -0
  7. package/dist/agents/rows.d.ts.map +1 -1
  8. package/dist/agents/runs.d.ts +2 -1
  9. package/dist/agents/runs.d.ts.map +1 -1
  10. package/dist/agents/threads.d.ts +4 -1
  11. package/dist/agents/threads.d.ts.map +1 -1
  12. package/dist/agents/token-rates.d.ts +13 -0
  13. package/dist/agents/token-rates.d.ts.map +1 -0
  14. package/dist/ai-accounting-filters.d.ts +14 -0
  15. package/dist/ai-accounting-filters.d.ts.map +1 -0
  16. package/dist/ai-cost-groups.d.ts +1 -1
  17. package/dist/ai-cost-groups.d.ts.map +1 -1
  18. package/dist/ai-cost-storage.d.ts.map +1 -1
  19. package/dist/index.js +465 -63
  20. package/dist/index.js.map +17 -14
  21. package/dist/migrations.d.ts.map +1 -1
  22. package/dist/objects/query-compiler.d.ts.map +1 -1
  23. package/dist/ontology-storage/commits.d.ts.map +1 -1
  24. package/dist/ontology-storage/materializations.d.ts +2 -1
  25. package/dist/ontology-storage/materializations.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/messages.ts +13 -0
  30. package/src/agents/rows.ts +14 -0
  31. package/src/agents/runs.ts +52 -4
  32. package/src/agents/threads.ts +108 -4
  33. package/src/agents/token-rates.ts +90 -0
  34. package/src/ai-accounting-filters.ts +51 -0
  35. package/src/ai-cost-groups.ts +52 -14
  36. package/src/ai-cost-storage.ts +101 -39
  37. package/src/migrations/080-agent-run-cancel-requests.sql +3 -0
  38. package/src/migrations/081-agent-thread-runs-on.sql +4 -0
  39. package/src/migrations/082-agent-token-rates.sql +17 -0
  40. package/src/migrations/083-agent-computer-sessions.sql +27 -0
  41. package/src/migrations.ts +12 -0
  42. package/src/objects/query-compiler.ts +44 -28
  43. package/src/ontology-storage/commits.ts +9 -0
  44. package/src/ontology-storage/materializations.ts +18 -1
package/dist/index.js CHANGED
@@ -5962,6 +5962,69 @@ CREATE INDEX idx_agent_runs_paused_on_question
5962
5962
  WHERE kind = 'subagent' AND finish_reason = 'awaiting_answer';
5963
5963
  `;
5964
5964
 
5965
+ // src/migrations/080-agent-run-cancel-requests.sql
5966
+ var _080_agent_run_cancel_requests_default = `-- A cancel is recorded on the run, not only broadcast: a runner that never hears the broker learns
5967
+ -- of it at its next lease renewal, and the run token stops working at once.
5968
+ ALTER TABLE agent_runs ADD COLUMN cancel_requested_at TEXT;
5969
+ `;
5970
+
5971
+ // src/migrations/081-agent-thread-runs-on.sql
5972
+ var _081_agent_thread_runs_on_default = `-- Where a conversation's turns run: NULL in the cloud, 'computer' on its owner's computer.
5973
+ ALTER TABLE agent_threads ADD COLUMN runs_on TEXT CHECK (runs_on IS NULL OR runs_on = 'computer');
5974
+ -- Which of its owner's computers runs a computer's thread: NULL until one claims its first turn.
5975
+ ALTER TABLE agent_threads ADD COLUMN computer_id TEXT;
5976
+ `;
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
+
5965
6028
  // src/migrations.ts
5966
6029
  var MIGRATIONS_TABLE_SQL = `
5967
6030
  CREATE TABLE IF NOT EXISTS sixb_migrations (
@@ -6095,7 +6158,11 @@ var sqliteStorageMigrations = defineMigrations({
6095
6158
  throw new Error("[SixbSqliteStorage] Agent runs, threads or messages were lost while giving sub-agents their threads.");
6096
6159
  }
6097
6160
  }, { checksum: checksum(_078_subagent_threads_default) }),
6098
- sqliteSql("079-subagent-questions", _079_subagent_questions_default)
6161
+ sqliteSql("079-subagent-questions", _079_subagent_questions_default),
6162
+ sqliteSql("080-agent-run-cancel-requests", _080_agent_run_cancel_requests_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)
6099
6166
  ]
6100
6167
  });
6101
6168
  function sqliteStoragePath(basePath) {
@@ -7061,6 +7128,11 @@ function rowToThreadRecord(row) {
7061
7128
  sandboxState: JSON.parse(row.sandbox_state)
7062
7129
  },
7063
7130
  ...row.sandbox_params == null ? {} : { sandboxParams: snapshotAgentThreadSandboxParams(JSON.parse(row.sandbox_params)) },
7131
+ ...row.runs_on == null ? {} : { runsOn: row.runs_on },
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) },
7064
7136
  status: row.status,
7065
7137
  activeRunId: row.active_run_id,
7066
7138
  lastMessageAt: row.last_message_at ? new Date(row.last_message_at) : undefined,
@@ -7084,6 +7156,7 @@ function rowToRunRecord(row) {
7084
7156
  token: row.execution_token,
7085
7157
  queueLeaseExpiresAt: new Date(row.execution_queue_lease_expires_at)
7086
7158
  } : undefined,
7159
+ cancelRequestedAt: row.cancel_requested_at ? new Date(row.cancel_requested_at) : undefined,
7087
7160
  createdAt: new Date(row.created_at),
7088
7161
  startedAt: row.started_at ? new Date(row.started_at) : undefined,
7089
7162
  completedAt: row.completed_at ? new Date(row.completed_at) : undefined
@@ -7542,6 +7615,55 @@ class SqliteAgentContextCheckpointStore {
7542
7615
  }
7543
7616
  }
7544
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
+
7545
7667
  // src/agents/memory.ts
7546
7668
  import {
7547
7669
  agentMemoryConflicts,
@@ -7708,6 +7830,12 @@ class SqliteAgentMessageStore {
7708
7830
  }
7709
7831
  async deleteByRunId(input) {
7710
7832
  return this.db.transaction(() => {
7833
+ if (input.fence) {
7834
+ const owner = this.db.query("SELECT 1 AS present FROM agent_runs WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?").get(input.projectId, input.fence.runId, input.fence.executionToken);
7835
+ if (!owner) {
7836
+ throw new AgentStorageError3("execution_lost", `[SixbSqlite] Execution token is no longer current on agent run '${input.fence.runId}'.`);
7837
+ }
7838
+ }
7711
7839
  const thread = this.db.query("SELECT 1 AS present FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.threadId);
7712
7840
  if (!thread) {
7713
7841
  throw new AgentStorageError3("thread_not_found", `[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`);
@@ -7782,7 +7910,9 @@ import {
7782
7910
  assertConversationAgentRunSpec,
7783
7911
  assertConversationThreadRow,
7784
7912
  assertCreateSubagentRunInput,
7913
+ assertStartsOnComputer,
7785
7914
  assertSubagentThreadTakesRun,
7915
+ assertTurnInThreadPlace,
7786
7916
  newSubagentThreadRecord,
7787
7917
  subagentRunMatchesCreateInput
7788
7918
  } from "@sixb/core/internal/agent-run-storage-provider";
@@ -7864,7 +7994,7 @@ class SqliteAgentRunStore {
7864
7994
  runId: input.id
7865
7995
  });
7866
7996
  return this.db.transaction(() => {
7867
- 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);
7868
7998
  if (!thread) {
7869
7999
  throw new AgentStorageError4("thread_not_found", `[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`);
7870
8000
  }
@@ -7872,6 +8002,7 @@ class SqliteAgentRunStore {
7872
8002
  if (thread.active_run_id !== null) {
7873
8003
  throw new AgentStorageError4("active_run_exists", `[SixbSqlite] Agent thread '${input.threadId}' already has an active run '${thread.active_run_id}'.`);
7874
8004
  }
8005
+ assertTurnInThreadPlace(thread.runs_on, input.spec, input.threadId, "SixbSqlite");
7875
8006
  try {
7876
8007
  this.db.query(`
7877
8008
  INSERT INTO agent_runs (
@@ -7971,7 +8102,11 @@ class SqliteAgentRunStore {
7971
8102
  async start(input) {
7972
8103
  const startedAt = input.startedAt ?? new Date;
7973
8104
  return this.db.transaction(() => {
7974
- 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
+ }
7975
8110
  this.db.query(`
7976
8111
  UPDATE agent_runs
7977
8112
  SET
@@ -8001,7 +8136,10 @@ class SqliteAgentRunStore {
8001
8136
  }
8002
8137
  async reclaim(input) {
8003
8138
  return this.db.transaction(() => {
8004
- this.requireRunning(input.projectId, input.id);
8139
+ const run = this.requireRunning(input.projectId, input.id);
8140
+ if (input.ifLapsedBy && run.execution_queue_lease_expires_at !== null && new Date(run.execution_queue_lease_expires_at).getTime() > input.ifLapsedBy.getTime()) {
8141
+ throw new AgentStorageError4("invalid_state", `[SixbSqlite] Agent run '${input.id}' is still held by its runner, whose lease has not lapsed.`);
8142
+ }
8005
8143
  this.db.query(`
8006
8144
  UPDATE agent_runs
8007
8145
  SET
@@ -8027,6 +8165,16 @@ class SqliteAgentRunStore {
8027
8165
  return this.requireRun(input.projectId, input.id);
8028
8166
  })();
8029
8167
  }
8168
+ async requestCancel(input) {
8169
+ return this.db.transaction(() => {
8170
+ this.db.query(`
8171
+ UPDATE agent_runs
8172
+ SET cancel_requested_at = ?
8173
+ WHERE project_id = ? AND id = ? AND status = 'running' AND cancel_requested_at IS NULL
8174
+ `).run((input.at ?? new Date).toISOString(), input.projectId, input.id);
8175
+ return this.requireRun(input.projectId, input.id);
8176
+ })();
8177
+ }
8030
8178
  async finish(input) {
8031
8179
  const completedAt = input.completedAt ?? new Date;
8032
8180
  const errorValue = input.status === "succeeded" || input.error === undefined ? null : serializeSixbFailure(input.error, AGENT_RUN_FAILURE_CODES2);
@@ -8142,6 +8290,9 @@ class SqliteAgentRunStore {
8142
8290
  whereClauses.push("parent_run_id = ?");
8143
8291
  args.push(input.parentRunId);
8144
8292
  }
8293
+ if (input.excludeComputerTurns) {
8294
+ whereClauses.push("json_extract(spec, '$.runsOn') IS NOT 'computer'");
8295
+ }
8145
8296
  appendRunListFilters(whereClauses, args, input, "COALESCE(started_at, created_at)");
8146
8297
  const { rows, total, hasMore } = queryRunList({
8147
8298
  db: this.db,
@@ -8259,9 +8410,15 @@ class SqliteAgentApprovalModeStore {
8259
8410
 
8260
8411
  // src/agents/threads.ts
8261
8412
  import {
8413
+ assertAgentThreadComputer,
8414
+ assertAssignableComputer,
8415
+ assertComputerPlace,
8262
8416
  assertConversationThreadRow as assertConversationThreadRow2,
8417
+ assertMovableToCloud,
8418
+ movesComputerPlace,
8263
8419
  participantsToAdd,
8264
8420
  planAgentThreadFork,
8421
+ queuedTurnInCloud,
8265
8422
  remapArtifactIds,
8266
8423
  snapshotAgentThreadSandboxParams as snapshotAgentThreadSandboxParams2,
8267
8424
  transitionAgentThreadSandbox
@@ -8284,6 +8441,7 @@ class SqliteAgentThreadStore {
8284
8441
  }).immediate();
8285
8442
  }
8286
8443
  async create(input) {
8444
+ assertComputerPlace(input.runsOn, input.computer, "SixbSqlite");
8287
8445
  const sandboxParams = input.sandboxParams === undefined ? null : JSON.stringify(snapshotAgentThreadSandboxParams2(input.sandboxParams));
8288
8446
  const createdAt = input.createdAt ?? new Date;
8289
8447
  const updatedAt = input.updatedAt ?? createdAt;
@@ -8296,14 +8454,18 @@ class SqliteAgentThreadStore {
8296
8454
  owner_principal_id,
8297
8455
  title,
8298
8456
  sandbox_params,
8457
+ runs_on,
8458
+ computer_id,
8459
+ computer_name,
8460
+ computer_folder,
8299
8461
  status,
8300
8462
  active_run_id,
8301
8463
  last_message_at,
8302
8464
  message_count,
8303
8465
  created_at,
8304
8466
  updated_at
8305
- ) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, NULL, 0, ?, ?)
8306
- `).run(input.projectId, input.id, input.ownerPrincipal.type, input.ownerPrincipal.id, input.title ?? null, sandboxParams, 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());
8307
8469
  } catch (error) {
8308
8470
  if (isUniqueConstraintError(error)) {
8309
8471
  throw new AgentStorageError6("duplicate_id", `[SixbSqlite] Agent thread '${input.id}' already exists for project '${input.projectId}'.`);
@@ -8312,9 +8474,13 @@ class SqliteAgentThreadStore {
8312
8474
  }
8313
8475
  return this.requireThread(input.projectId, input.id);
8314
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
+ }
8315
8481
  async fork(input) {
8316
8482
  return this.db.transaction(() => {
8317
- 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);
8318
8484
  if (!source) {
8319
8485
  throw new AgentStorageError6("thread_not_found", `[SixbSqlite] Agent thread '${input.sourceThreadId}' not found for project '${input.projectId}'.`);
8320
8486
  }
@@ -8336,6 +8502,7 @@ class SqliteAgentThreadStore {
8336
8502
  ORDER BY summarized_through_seq DESC, created_at DESC, id DESC
8337
8503
  `).all(input.projectId, source.id);
8338
8504
  const plan = planAgentThreadFork(input, {
8505
+ workspace: { runsOn: source.runs_on, movedAt: source.moved_at },
8339
8506
  ...activeRun?.trigger_message_id ? { triggerMessageId: activeRun.trigger_message_id } : {},
8340
8507
  messages,
8341
8508
  checkpoints: checkpoints.map(rowToContextCheckpointRecord)
@@ -8438,6 +8605,10 @@ class SqliteAgentThreadStore {
8438
8605
  )`);
8439
8606
  args.push(input.participantPrincipal.type, input.participantPrincipal.id);
8440
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
+ }
8441
8612
  const dir = input.order === "asc" ? "ASC" : "DESC";
8442
8613
  const { rows, total, hasMore } = queryAgentList({
8443
8614
  db: this.db,
@@ -8484,6 +8655,42 @@ class SqliteAgentThreadStore {
8484
8655
  `).run(input.projectId, input.threadId, input.principal.type, input.principal.id);
8485
8656
  return result.changes > 0;
8486
8657
  }
8658
+ async assignComputer(input) {
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();
8693
+ }
8487
8694
  async listParticipants(input) {
8488
8695
  return this.selectParticipants(input.projectId, [...new Set(input.threadIds)]);
8489
8696
  }
@@ -8518,10 +8725,11 @@ class SqliteAgentThreadStore {
8518
8725
  active_run_id,
8519
8726
  last_message_at,
8520
8727
  message_count,
8728
+ moved_at,
8521
8729
  created_at,
8522
8730
  updated_at
8523
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
8524
- `).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());
8525
8733
  } catch (error) {
8526
8734
  if (isUniqueConstraintError(error)) {
8527
8735
  throw new AgentStorageError6("duplicate_id", `[SixbSqlite] Agent thread '${thread.id}' already exists for project '${thread.projectId}'.`);
@@ -8569,6 +8777,53 @@ class SqliteAgentThreadStore {
8569
8777
  }
8570
8778
  }
8571
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
+
8572
8827
  // src/agents/agent-storage.ts
8573
8828
  class SqliteAgentStorage {
8574
8829
  threads;
@@ -8578,6 +8833,8 @@ class SqliteAgentStorage {
8578
8833
  memory;
8579
8834
  approvals;
8580
8835
  approvalModes;
8836
+ computers;
8837
+ tokenRates;
8581
8838
  connection;
8582
8839
  constructor(options) {
8583
8840
  this.connection = openSqliteStoreConnection(options);
@@ -8588,6 +8845,8 @@ class SqliteAgentStorage {
8588
8845
  this.memory = new SqliteAgentMemoryStore(this.connection.db);
8589
8846
  this.approvals = new SqliteAgentApprovalStore(this.connection.db);
8590
8847
  this.approvalModes = new SqliteAgentApprovalModeStore(this.connection.db);
8848
+ this.computers = new SqliteAgentComputerSessionStore(this.connection.db);
8849
+ this.tokenRates = new SqliteAgentTokenRateStore(this.connection.db);
8591
8850
  }
8592
8851
  close() {
8593
8852
  closeSqliteStoreConnection(this.connection);
@@ -8611,15 +8870,52 @@ import {
8611
8870
  AiCostStorageError
8612
8871
  } from "@sixb/core/storage";
8613
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
+
8614
8904
  // src/ai-cost-groups.ts
8615
8905
  import {
8616
8906
  aiModelCallGroupFragmentFromRow,
8617
8907
  buildAiModelCallGroupsFromFragments,
8618
8908
  normalizeAiModelCallAccountingQuery,
8909
+ normalizeAiModelCallGroupSort,
8619
8910
  TOKENLESS_MODEL_KINDS
8620
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
+ }
8621
8916
  async function listAiModelCallGroups(connection, input) {
8622
8917
  const query = normalizeAiModelCallAccountingQuery(input);
8918
+ const sort = normalizeAiModelCallGroupSort(input.sort);
8623
8919
  const parameters = [
8624
8920
  query.projectId,
8625
8921
  query.from.toISOString(),
@@ -8628,20 +8924,28 @@ async function listAiModelCallGroups(connection, input) {
8628
8924
  query.modelId ?? null,
8629
8925
  query.valuationStatus ?? null
8630
8926
  ];
8631
- const count = connection.db.query(COUNT_SQL).get(...parameters);
8632
- 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);
8633
8930
  const total = Number(count?.total);
8634
8931
  if (!Number.isSafeInteger(total) || total < 0)
8635
8932
  throw new RangeError("[Sixb] Invalid model-call group count.");
8636
- const items = buildAiModelCallGroupsFromFragments(rows.map(aiModelCallGroupFragmentFromRow));
8933
+ const items = buildAiModelCallGroupsFromFragments(rows.map(aiModelCallGroupFragmentFromRow), sort);
8637
8934
  return { items, total, hasMore: query.offset + items.length < total };
8638
8935
  }
8639
8936
  var TOKENLESS_KINDS = TOKENLESS_MODEL_KINDS.map((kind) => `'${kind}'`).join(", ");
8640
8937
  var FILTERED_SQL = `
8641
8938
  WITH filtered AS (
8642
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,
8643
8946
  usage.execution_id, usage.provider_id, usage.requested_model_id, usage.occurred_at,
8644
- 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,
8645
8949
  usage.audio_duration_ms,
8646
8950
  execution.executor_kind, execution.executor_id, execution.authority_primitive_id AS primitive_id,
8647
8951
  COALESCE(cost.status, 'unvalued') AS valuation_status,
@@ -8654,7 +8958,8 @@ WITH filtered AS (
8654
8958
  workflow_node.node_id AS workflow_agent_step_id,
8655
8959
  workflow_agent.node_run_id AS workflow_node_run_id,
8656
8960
  workflow_node.workflow_id AS workflow_id,
8657
- 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
8658
8963
  FROM ai_model_call_usage AS usage
8659
8964
  JOIN executions AS execution ON execution.project_id = usage.project_id AND execution.id = usage.execution_id
8660
8965
  LEFT JOIN agent_runs AS direct_agent
@@ -8674,23 +8979,33 @@ WITH filtered AS (
8674
8979
  AND (?4 IS NULL OR usage.provider_id = ?4)
8675
8980
  AND (?5 IS NULL OR usage.requested_model_id = ?5)
8676
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)}
8677
8989
  ), roots AS (
8678
- SELECT root_execution_id, MAX(occurred_at) AS last_call_at
8679
- 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
8680
8992
  )
8681
8993
  `;
8682
8994
  var COUNT_SQL = `${FILTERED_SQL} SELECT CAST(COUNT(*) AS TEXT) AS total FROM roots`;
8683
8995
  var PAGE_SQL = `${FILTERED_SQL}, page AS (
8684
- SELECT root_execution_id FROM roots
8685
- 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
8686
8999
  )
8687
- 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,
8688
9001
  MIN(occurred_at) AS first_call_at, MAX(occurred_at) AS last_call_at,
8689
9002
  CAST(COUNT(*) AS TEXT) AS call_count,
8690
9003
  CAST(COUNT(total_tokens) AS TEXT) AS token_count,
8691
9004
  CAST(COUNT(CASE WHEN total_tokens IS NULL AND model_kind IN (${TOKENLESS_KINDS}) THEN 1 END) AS TEXT)
8692
9005
  AS tokenless_count,
8693
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,
8694
9009
  CAST(SUM(output_images) AS TEXT) AS output_images,
8695
9010
  CAST(SUM(input_characters) AS TEXT) AS input_characters,
8696
9011
  CAST(SUM(audio_duration_ms) AS TEXT) AS audio_duration_ms,
@@ -8707,9 +9022,11 @@ SELECT filtered.root_execution_id, execution_id, provider_id, requested_model_id
8707
9022
  MAX(workflow_agent_step_id) AS workflow_agent_step_id,
8708
9023
  MAX(workflow_node_run_id) AS workflow_node_run_id,
8709
9024
  MAX(workflow_id) AS workflow_id,
8710
- MAX(workflow_run_id) AS workflow_run_id
8711
- FROM filtered JOIN page ON page.root_execution_id = filtered.root_execution_id
8712
- 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,
8713
9030
  valuation_status, amount_currency
8714
9031
  `;
8715
9032
 
@@ -9013,7 +9330,7 @@ class SqliteAiCostStorage {
9013
9330
  }
9014
9331
  async queryProjectOverview(input) {
9015
9332
  const query = normalizeAiAccountingQuery(input);
9016
- 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);
9017
9334
  return buildAiAccountingOverviewFromFragments(input, rows.map(aggregateFragmentFromRow));
9018
9335
  }
9019
9336
  async listModelCalls(input) {
@@ -9060,12 +9377,32 @@ var SQLITE_ACCOUNTING_LIST_FILTER = `
9060
9377
  ELSE 'unvalued'
9061
9378
  END
9062
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
9063
9401
  `;
9064
9402
  var SQLITE_ACCOUNTING_LIST_COUNT_SQL = `
9065
9403
  SELECT CAST(COUNT(*) AS TEXT) AS total
9066
9404
  FROM ai_model_call_usage AS usage
9067
- LEFT JOIN ai_model_call_valuations AS cost
9068
- ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
9405
+ ${SQLITE_ACCOUNTING_LIST_JOINS}
9069
9406
  WHERE ${SQLITE_ACCOUNTING_LIST_FILTER}
9070
9407
  `;
9071
9408
  var SQLITE_ACCOUNTING_LIST_PAGE_SQL = `
@@ -9100,18 +9437,7 @@ var SQLITE_ACCOUNTING_LIST_PAGE_SQL = `
9100
9437
  cost.details AS cost_details,
9101
9438
  cost.rated_at AS cost_rated_at
9102
9439
  FROM ai_model_call_usage AS usage
9103
- JOIN executions AS execution ON execution.project_id = usage.project_id AND execution.id = usage.execution_id
9104
- LEFT JOIN agent_runs AS direct_agent
9105
- ON direct_agent.project_id = usage.project_id
9106
- AND direct_agent.execution_id = usage.execution_id
9107
- LEFT JOIN workflow_agent_node_runs AS workflow_agent
9108
- ON workflow_agent.project_id = usage.project_id
9109
- AND workflow_agent.execution_id = usage.execution_id
9110
- LEFT JOIN workflow_node_runs AS workflow_node
9111
- ON workflow_node.project_id = workflow_agent.project_id
9112
- AND workflow_node.id = workflow_agent.node_run_id
9113
- LEFT JOIN ai_model_call_valuations AS cost
9114
- ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
9440
+ ${SQLITE_ACCOUNTING_LIST_JOINS}
9115
9441
  WHERE ${SQLITE_ACCOUNTING_LIST_FILTER}
9116
9442
  ORDER BY usage.occurred_at DESC, usage.id ASC
9117
9443
  LIMIT ? OFFSET ?
@@ -9150,7 +9476,10 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
9150
9476
  ELSE 'unvalued'
9151
9477
  END AS valuation_status,
9152
9478
  CASE WHEN cost.status = 'rated' THEN cost.currency ELSE NULL END AS amount_currency,
9153
- 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
9154
9483
  FROM ai_model_call_usage AS usage
9155
9484
  JOIN executions AS execution ON execution.project_id = usage.project_id AND execution.id = usage.execution_id
9156
9485
  LEFT JOIN agent_runs AS direct_agent
@@ -9167,10 +9496,19 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
9167
9496
  WHERE usage.project_id = ? AND usage.occurred_at >= ? AND usage.occurred_at < ?
9168
9497
  AND (? IS NULL OR usage.provider_id = ?)
9169
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
+ })}
9170
9508
  ), expanded AS (
9171
9509
  SELECT 'totals' AS dimension, NULL AS key_1, NULL AS key_2, NULL AS key_3, NULL AS bucket_start,
9172
- classified.*
9173
- FROM classified
9510
+ filtered.*
9511
+ FROM filtered
9174
9512
  UNION ALL
9175
9513
  SELECT 'series', NULL, NULL, NULL,
9176
9514
  CASE ?
@@ -9182,18 +9520,39 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
9182
9520
  printf('-%d days', (CAST(strftime('%w', occurred_at) AS INTEGER) + 6) % 7)
9183
9521
  )
9184
9522
  END,
9185
- classified.*
9186
- FROM classified
9523
+ filtered.*
9524
+ FROM filtered
9187
9525
  UNION ALL
9188
- 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
9189
9538
  UNION ALL
9190
- SELECT 'kind', model_kind, NULL, NULL, NULL, classified.* FROM classified
9539
+ SELECT 'model', provider_id, requested_model_id, NULL, NULL, filtered.* FROM filtered
9191
9540
  UNION ALL
9192
- SELECT 'agent', attribution_agent_kind, attribution_workflow_id, attribution_agent_step_id, NULL, classified.*
9193
- FROM classified WHERE attribution_agent_kind IS NOT NULL
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
9194
9548
  UNION ALL
9195
- SELECT 'workflow', attribution_workflow_id, NULL, NULL, NULL, classified.*
9196
- FROM classified WHERE attribution_workflow_id IS NOT NULL
9549
+ SELECT 'kind', model_kind, NULL, NULL, NULL, filtered.* FROM filtered
9550
+ UNION ALL
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
9553
+ UNION ALL
9554
+ SELECT 'workflow', attribution_workflow_id, NULL, NULL, NULL, filtered.*
9555
+ FROM filtered WHERE attribution_workflow_id IS NOT NULL
9197
9556
  )
9198
9557
  SELECT dimension,
9199
9558
  key_1,
@@ -9254,11 +9613,16 @@ function accountingListFilterParameters(query) {
9254
9613
  query.executionId ?? null,
9255
9614
  query.executionId ?? null,
9256
9615
  query.valuationStatus ?? null,
9257
- query.valuationStatus ?? null
9616
+ query.valuationStatus ?? null,
9617
+ ...sqliteAccountingFilterParameters(query)
9258
9618
  ];
9259
9619
  }
9260
9620
  function aggregateFragmentFromRow(row) {
9261
- 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" ? {
9262
9626
  agentKind: required(row.key_1),
9263
9627
  ...row.key_1 === "workflowAgent" ? { workflowId: required(row.key_2), agentStepId: required(row.key_3) } : {}
9264
9628
  } : row.dimension === "workflow" ? { workflowId: required(row.key_1) } : row.dimension === "series" ? { start: new Date(required(row.bucket_start)) } : {};
@@ -13209,6 +13573,7 @@ import {
13209
13573
  import {
13210
13574
  hasVectorProfile,
13211
13575
  isVectorProfileQuery,
13576
+ linkEndpointObjectTypeIds,
13212
13577
  normalizeVector,
13213
13578
  vectorSearchCandidateLimit
13214
13579
  } from "@sixb/core/internal/query";
@@ -13270,7 +13635,7 @@ function compileObjectQueryInternal(projectId, query, ctx) {
13270
13635
  case "page":
13271
13636
  return compilePage(projectId, query.input, query.pageSize, query.pageToken, ctx);
13272
13637
  case "traverse":
13273
- return compileTraversal(projectId, query.input, query.linkId, query.direction, query.sourceObjectTypeId, ctx);
13638
+ return compileTraversal(projectId, query, ctx);
13274
13639
  case "set":
13275
13640
  return compileSet(projectId, query.op, query.inputs, ctx);
13276
13641
  case "project":
@@ -13551,9 +13916,11 @@ function compilePage(projectId, inputQuery, rawPageSize, pageToken, ctx) {
13551
13916
  }
13552
13917
  };
13553
13918
  }
13554
- function compileTraversal(projectId, inputQuery, linkId, direction, sourceObjectTypeId, ctx) {
13555
- 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));
13556
13922
  const outputAlias = direction === "outgoing" ? "target_object" : "source_object";
13923
+ const endpointType = compileEndpointTypes(outputAlias, linkEndpointObjectTypeIds(traversal));
13557
13924
  const joinSql = direction === "outgoing" ? `
13558
13925
  JOIN ${ctx.source.linksTable} AS edge
13559
13926
  ON edge.project_id = input.project_id
@@ -13564,6 +13931,7 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
13564
13931
  ON target_object.project_id = edge.project_id
13565
13932
  AND target_object.object_type_id = edge.target_type_id
13566
13933
  AND target_object.primary_id = edge.target_id
13934
+ ${endpointType.sql}
13567
13935
  ` : `
13568
13936
  JOIN ${ctx.source.linksTable} AS edge
13569
13937
  ON edge.project_id = input.project_id
@@ -13575,6 +13943,7 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
13575
13943
  ON source_object.project_id = edge.project_id
13576
13944
  AND source_object.object_type_id = edge.source_type_id
13577
13945
  AND source_object.primary_id = edge.source_id
13946
+ ${endpointType.sql}
13578
13947
  `;
13579
13948
  const order = compileOrder(identityOrderFields());
13580
13949
  const qualifiedOrder = compileOrder(identityOrderFields(), outputAlias);
@@ -13587,7 +13956,8 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
13587
13956
  const args = [
13588
13957
  ...input.args,
13589
13958
  linkId,
13590
- ...sourceObjectTypeId === undefined ? [] : [sourceObjectTypeId],
13959
+ ...direction === "incoming" && sourceObjectTypeId !== undefined ? [sourceObjectTypeId] : [],
13960
+ ...endpointType.args,
13591
13961
  ...qualifiedOrder.args
13592
13962
  ];
13593
13963
  return {
@@ -13603,6 +13973,14 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
13603
13973
  }
13604
13974
  };
13605
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
+ }
13606
13984
  function compileExpand(projectId, inputQuery, expansions, ctx) {
13607
13985
  const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
13608
13986
  const expand = compileExpansionsObject(expansions, { project: "input.project_id", type: "input.object_type_id", id: "input.primary_id" }, "", ctx);
@@ -13653,31 +14031,36 @@ function compileExpansionValue(expansion, parent, path, ctx) {
13653
14031
  `${parentId} = ${parent.id}`,
13654
14032
  `${edge}.link_id = ?`
13655
14033
  ];
13656
- const whereArgs = [expansion.linkId];
14034
+ const endpointType = compileEndpointTypes(target, expansion.endpointObjectTypeIds);
14035
+ const whereArgs = [...endpointType.args, expansion.linkId];
13657
14036
  if (incoming && expansion.sourceObjectTypeId !== undefined) {
13658
14037
  whereParts.push(`${edge}.source_type_id = ?`);
13659
14038
  whereArgs.push(expansion.sourceObjectTypeId);
13660
14039
  }
13661
- const inner = `
13662
- SELECT ${child.sql} AS elem, row_number() OVER (ORDER BY ${order.sql}) AS _ord
14040
+ const from = `
13663
14041
  FROM ${ctx.source.linksTable} AS ${edge}
13664
14042
  JOIN ${ctx.source.objectsTable} AS ${target}
13665
14043
  ON ${target}.project_id = ${edge}.project_id
13666
14044
  AND ${target}.object_type_id = ${neighborType}
13667
14045
  AND ${target}.primary_id = ${neighborId}
14046
+ ${endpointType.sql}
13668
14047
  WHERE ${whereParts.join(" AND ")}
13669
14048
  `;
13670
- const innerArgs = [...child.args, ...order.args, ...whereArgs];
13671
14049
  if (expansion.cardinality === "one") {
13672
14050
  return {
13673
- sql: `(SELECT ${ranked}.elem FROM (${inner}) AS ${ranked} WHERE ${ranked}._ord = 1)`,
13674
- args: innerArgs
14051
+ sql: `(SELECT ${child.sql} ${from} ORDER BY ${order.sql} LIMIT 1)`,
14052
+ args: [...child.args, ...whereArgs, ...order.args]
13675
14053
  };
13676
14054
  }
13677
14055
  const limit = expansion.limit ?? DEFAULT_EXPANSION_FANOUT;
13678
14056
  return {
13679
- sql: `COALESCE((SELECT json_group_array(json(${ranked}.elem) ORDER BY ${ranked}._ord) FROM (${inner}) AS ${ranked} WHERE ${ranked}._ord <= ?), '[]')`,
13680
- args: [...innerArgs, limit]
14057
+ sql: `COALESCE((SELECT json_group_array(json(${ranked}.elem) ORDER BY ${ranked}._ord) FROM (
14058
+ SELECT ${child.sql} AS elem, row_number() OVER (ORDER BY ${order.sql}) AS _ord
14059
+ ${from}
14060
+ ORDER BY ${order.sql}
14061
+ LIMIT ?
14062
+ ) AS ${ranked}), '[]')`,
14063
+ args: [...child.args, ...order.args, ...whereArgs, ...order.args, limit]
13681
14064
  };
13682
14065
  }
13683
14066
  function compileExpansionChildJson(expansion, edge, target, path, ctx) {
@@ -15563,6 +15946,9 @@ class SqliteOntologyCommitStorage {
15563
15946
  assertNonnegativeInteger(offset, "Ontology commit list offset");
15564
15947
  if (limit !== -1)
15565
15948
  assertNonnegativeInteger(limit, "Ontology commit list limit");
15949
+ if (input.committedAt !== undefined) {
15950
+ assertTimestamp(input.committedAt, "Ontology commit list committedAt");
15951
+ }
15566
15952
  const filters = ["project_id = ?"];
15567
15953
  const values = [input.projectId];
15568
15954
  if (input.run?.kind === "action") {
@@ -15572,6 +15958,10 @@ class SqliteOntologyCommitStorage {
15572
15958
  filters.push("origin_run_id = ? AND origin_kind IN ('projection', 'telemetry')");
15573
15959
  values.push(input.run.id);
15574
15960
  }
15961
+ if (input.committedAt !== undefined) {
15962
+ filters.push("committed_at = ?");
15963
+ values.push(input.committedAt);
15964
+ }
15575
15965
  const where = filters.join(" AND ");
15576
15966
  const totalRow = this.db.query(`SELECT COUNT(*) AS total FROM ontology_commits WHERE ${where}`).get(...values);
15577
15967
  const direction = input.order === "desc" ? "DESC" : "ASC";
@@ -15623,6 +16013,7 @@ import {
15623
16013
  invalidCorrelation as invalidCorrelation2,
15624
16014
  materializationOutboxWrite,
15625
16015
  materializationPlanChunk,
16016
+ prepareMaterializationStateRead,
15626
16017
  sameNonnegativeCounts as sameCounts,
15627
16018
  uniqueSorted
15628
16019
  } from "@sixb/core/internal/ontology-storage-provider";
@@ -17794,6 +18185,17 @@ class SqliteOntologyMaterializationStorage {
17794
18185
  }
17795
18186
  }
17796
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
+ }
17797
18199
  async stageWork(input) {
17798
18200
  this.sessions.stage(input);
17799
18201
  await yieldSqliteEventLoop();
@@ -21808,4 +22210,4 @@ export {
21808
22210
  migrateSqliteStorage
21809
22211
  };
21810
22212
 
21811
- //# debugId=34FFE55D6BF6FFCE64756E2164756E21
22213
+ //# debugId=38D04B34BA5E877464756E2164756E21