@sixb/sqlite 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-storage.d.ts +5 -3
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/checkpoints.d.ts +14 -0
- package/dist/agents/checkpoints.d.ts.map +1 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/messages.d.ts +2 -1
- package/dist/agents/messages.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +18 -1
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/ai-cost-storage.d.ts +15 -0
- package/dist/ai-cost-storage.d.ts.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +800 -53
- package/dist/index.js.map +12 -10
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agents/agent-storage.ts +6 -3
- package/src/agents/checkpoints.ts +175 -0
- package/src/agents/index.ts +1 -0
- package/src/agents/messages.ts +59 -0
- package/src/agents/rows.ts +44 -0
- package/src/ai-cost-storage.ts +744 -0
- package/src/index.ts +8 -2
- package/src/migrations/026-ai-cost-accounting.sql +57 -0
- package/src/migrations/027-agent-context-checkpoints.sql +28 -0
- package/src/migrations/028-object-override-edit-times.sql +3 -0
- package/src/migrations.ts +10 -0
- package/src/ontology-storage/materialization-state.ts +2 -0
- package/src/ontology-storage/materialization-writer.ts +6 -3
package/dist/index.js
CHANGED
|
@@ -2918,6 +2918,103 @@ CREATE INDEX idx_connector_connections_status
|
|
|
2918
2918
|
ON connector_connections (project_id, connector_id, status, id);
|
|
2919
2919
|
`;
|
|
2920
2920
|
|
|
2921
|
+
// src/migrations/026-ai-cost-accounting.sql
|
|
2922
|
+
var _026_ai_cost_accounting_default = `CREATE TABLE ai_model_call_valuations (
|
|
2923
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
2924
|
+
usage_record_id TEXT NOT NULL CHECK (length(trim(usage_record_id)) > 0),
|
|
2925
|
+
status TEXT NOT NULL CHECK (status IN ('rated', 'unpriceable')),
|
|
2926
|
+
provider_id TEXT CHECK (provider_id IS NULL OR length(trim(provider_id)) > 0),
|
|
2927
|
+
model_id TEXT CHECK (model_id IS NULL OR length(trim(model_id)) > 0),
|
|
2928
|
+
currency TEXT CHECK (
|
|
2929
|
+
currency IS NULL OR (length(currency) = 3 AND currency GLOB '[A-Z][A-Z][A-Z]')
|
|
2930
|
+
),
|
|
2931
|
+
amount_nanos INTEGER CHECK (
|
|
2932
|
+
amount_nanos IS NULL OR (typeof(amount_nanos) = 'integer' AND amount_nanos >= 0)
|
|
2933
|
+
),
|
|
2934
|
+
reason TEXT CHECK (
|
|
2935
|
+
reason IS NULL OR reason IN (
|
|
2936
|
+
'missingBillingIdentity',
|
|
2937
|
+
'missingCatalogEntry',
|
|
2938
|
+
'missingUsageMeter',
|
|
2939
|
+
'unsupportedPricingDimension',
|
|
2940
|
+
'invalidUsageForFormula'
|
|
2941
|
+
)
|
|
2942
|
+
),
|
|
2943
|
+
details TEXT NOT NULL CHECK (json_valid(details) AND json_type(details) = 'object'),
|
|
2944
|
+
rated_at TEXT NOT NULL CHECK (length(trim(rated_at)) > 0),
|
|
2945
|
+
PRIMARY KEY (project_id, usage_record_id),
|
|
2946
|
+
FOREIGN KEY (project_id, usage_record_id)
|
|
2947
|
+
REFERENCES ai_model_call_usage(project_id, id)
|
|
2948
|
+
ON DELETE RESTRICT,
|
|
2949
|
+
CHECK (
|
|
2950
|
+
(
|
|
2951
|
+
status = 'rated'
|
|
2952
|
+
AND provider_id IS NOT NULL
|
|
2953
|
+
AND model_id IS NOT NULL
|
|
2954
|
+
AND currency IS NOT NULL
|
|
2955
|
+
AND amount_nanos IS NOT NULL
|
|
2956
|
+
AND reason IS NULL
|
|
2957
|
+
)
|
|
2958
|
+
OR
|
|
2959
|
+
(
|
|
2960
|
+
status = 'unpriceable'
|
|
2961
|
+
AND currency IS NULL
|
|
2962
|
+
AND amount_nanos IS NULL
|
|
2963
|
+
AND reason IS NOT NULL
|
|
2964
|
+
AND (
|
|
2965
|
+
(reason = 'missingBillingIdentity' AND provider_id IS NULL AND model_id IS NULL)
|
|
2966
|
+
OR
|
|
2967
|
+
(reason <> 'missingBillingIdentity' AND provider_id IS NOT NULL AND model_id IS NOT NULL)
|
|
2968
|
+
)
|
|
2969
|
+
)
|
|
2970
|
+
)
|
|
2971
|
+
);
|
|
2972
|
+
|
|
2973
|
+
CREATE INDEX idx_ai_model_call_usage_provider_model_time
|
|
2974
|
+
ON ai_model_call_usage (project_id, provider_id, requested_model_id, occurred_at, id);
|
|
2975
|
+
CREATE INDEX idx_ai_model_call_usage_model_time
|
|
2976
|
+
ON ai_model_call_usage (project_id, requested_model_id, occurred_at, id);
|
|
2977
|
+
CREATE INDEX idx_ai_model_call_valuations_summary
|
|
2978
|
+
ON ai_model_call_valuations (project_id, status, currency, usage_record_id);
|
|
2979
|
+
`;
|
|
2980
|
+
|
|
2981
|
+
// src/migrations/027-agent-context-checkpoints.sql
|
|
2982
|
+
var _027_agent_context_checkpoints_default = `CREATE TABLE agent_context_checkpoints (
|
|
2983
|
+
project_id TEXT NOT NULL,
|
|
2984
|
+
id TEXT NOT NULL,
|
|
2985
|
+
thread_id TEXT NOT NULL,
|
|
2986
|
+
created_by_run_id TEXT NOT NULL,
|
|
2987
|
+
previous_checkpoint_id TEXT,
|
|
2988
|
+
reason TEXT NOT NULL CHECK (reason IN ('threshold', 'overflow')),
|
|
2989
|
+
summary TEXT NOT NULL CHECK (length(trim(summary)) > 0),
|
|
2990
|
+
summary_format_version INTEGER NOT NULL CHECK (summary_format_version = 1),
|
|
2991
|
+
summarized_through_seq INTEGER NOT NULL CHECK (summarized_through_seq >= 1),
|
|
2992
|
+
observed_head_seq INTEGER NOT NULL,
|
|
2993
|
+
estimated_input_tokens_before INTEGER NOT NULL CHECK (estimated_input_tokens_before >= 0),
|
|
2994
|
+
estimated_input_tokens_after INTEGER NOT NULL CHECK (estimated_input_tokens_after >= 0),
|
|
2995
|
+
summary_model_id TEXT NOT NULL CHECK (length(trim(summary_model_id)) > 0),
|
|
2996
|
+
created_at TEXT NOT NULL,
|
|
2997
|
+
PRIMARY KEY (project_id, id),
|
|
2998
|
+
UNIQUE (project_id, created_by_run_id),
|
|
2999
|
+
FOREIGN KEY (project_id, thread_id)
|
|
3000
|
+
REFERENCES agent_threads(project_id, id) ON DELETE CASCADE,
|
|
3001
|
+
FOREIGN KEY (project_id, created_by_run_id)
|
|
3002
|
+
REFERENCES agent_runs(project_id, id) ON DELETE RESTRICT,
|
|
3003
|
+
FOREIGN KEY (project_id, previous_checkpoint_id)
|
|
3004
|
+
REFERENCES agent_context_checkpoints(project_id, id) ON DELETE RESTRICT,
|
|
3005
|
+
CHECK (summarized_through_seq < observed_head_seq)
|
|
3006
|
+
);
|
|
3007
|
+
|
|
3008
|
+
CREATE INDEX idx_agent_context_checkpoints_thread_latest
|
|
3009
|
+
ON agent_context_checkpoints(project_id, thread_id, summarized_through_seq DESC, created_at DESC, id DESC);
|
|
3010
|
+
`;
|
|
3011
|
+
|
|
3012
|
+
// src/migrations/028-object-override-edit-times.sql
|
|
3013
|
+
var _028_object_override_edit_times_default = `ALTER TABLE ontology_object_overrides
|
|
3014
|
+
ADD COLUMN edited_at TEXT NOT NULL DEFAULT '{}'
|
|
3015
|
+
CHECK (json_valid(edited_at) AND json_type(edited_at) = 'object');
|
|
3016
|
+
`;
|
|
3017
|
+
|
|
2921
3018
|
// src/migrations.ts
|
|
2922
3019
|
var MIGRATIONS_TABLE_SQL = `
|
|
2923
3020
|
CREATE TABLE IF NOT EXISTS sixb_migrations (
|
|
@@ -2977,7 +3074,10 @@ var sqliteStorageMigrations = defineMigrations({
|
|
|
2977
3074
|
}
|
|
2978
3075
|
db.run(_024_ontology_commit_executions_default);
|
|
2979
3076
|
}, { checksum: checksum(_024_ontology_commit_executions_default) }),
|
|
2980
|
-
sqliteSql("025-connector-connections", _025_connector_connections_default)
|
|
3077
|
+
sqliteSql("025-connector-connections", _025_connector_connections_default),
|
|
3078
|
+
sqliteSql("026-ai-cost-accounting", _026_ai_cost_accounting_default),
|
|
3079
|
+
sqliteSql("027-agent-context-checkpoints", _027_agent_context_checkpoints_default),
|
|
3080
|
+
sqliteSql("028-object-override-edit-times", _028_object_override_edit_times_default)
|
|
2981
3081
|
]
|
|
2982
3082
|
});
|
|
2983
3083
|
function sqliteStoragePath(basePath) {
|
|
@@ -3628,17 +3728,18 @@ function isUniqueConstraintError(error) {
|
|
|
3628
3728
|
return error instanceof Error && error.message.includes("UNIQUE constraint failed");
|
|
3629
3729
|
}
|
|
3630
3730
|
|
|
3631
|
-
// src/agents/
|
|
3632
|
-
import {
|
|
3731
|
+
// src/agents/checkpoints.ts
|
|
3732
|
+
import {
|
|
3733
|
+
agentContextCheckpointMatchesCreateInput,
|
|
3734
|
+
assertAgentContextCheckpointAnchors,
|
|
3735
|
+
assertAgentContextCheckpointAuthority,
|
|
3736
|
+
assertAgentContextCheckpointReplayState,
|
|
3737
|
+
assertCreateAgentContextCheckpointInput
|
|
3738
|
+
} from "@sixb/core/internal/agent-run-storage-provider";
|
|
3633
3739
|
import {
|
|
3634
3740
|
AgentStorageError
|
|
3635
3741
|
} from "@sixb/core/storage";
|
|
3636
3742
|
|
|
3637
|
-
// src/storage-errors.ts
|
|
3638
|
-
function isUniqueConstraintError2(error) {
|
|
3639
|
-
return error instanceof Error && error.message.includes("UNIQUE constraint failed");
|
|
3640
|
-
}
|
|
3641
|
-
|
|
3642
3743
|
// src/agents/rows.ts
|
|
3643
3744
|
import { parseSixbFailure } from "@sixb/core/internal/errors";
|
|
3644
3745
|
import {
|
|
@@ -3700,6 +3801,27 @@ function rowToMessageRecord(row) {
|
|
|
3700
3801
|
completedAt: row.completed_at ? new Date(row.completed_at) : undefined
|
|
3701
3802
|
};
|
|
3702
3803
|
}
|
|
3804
|
+
function rowToContextCheckpointRecord(row) {
|
|
3805
|
+
if (row.summary_format_version !== 1) {
|
|
3806
|
+
throw new Error(`[SixbSqlite] Unsupported agent context checkpoint summary format version '${row.summary_format_version}'.`);
|
|
3807
|
+
}
|
|
3808
|
+
return {
|
|
3809
|
+
id: row.id,
|
|
3810
|
+
projectId: row.project_id,
|
|
3811
|
+
threadId: row.thread_id,
|
|
3812
|
+
createdByRunId: row.created_by_run_id,
|
|
3813
|
+
previousCheckpointId: row.previous_checkpoint_id ?? undefined,
|
|
3814
|
+
reason: row.reason,
|
|
3815
|
+
summary: row.summary,
|
|
3816
|
+
summaryFormatVersion: 1,
|
|
3817
|
+
summarizedThroughSeq: row.summarized_through_seq,
|
|
3818
|
+
observedHeadSeq: row.observed_head_seq,
|
|
3819
|
+
estimatedInputTokensBefore: row.estimated_input_tokens_before,
|
|
3820
|
+
estimatedInputTokensAfter: row.estimated_input_tokens_after,
|
|
3821
|
+
summaryModelId: row.summary_model_id,
|
|
3822
|
+
createdAt: new Date(row.created_at)
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3703
3825
|
function principalFromColumns(type, id) {
|
|
3704
3826
|
return type && id ? { type, id } : undefined;
|
|
3705
3827
|
}
|
|
@@ -3721,6 +3843,106 @@ function queryAgentList(input) {
|
|
|
3721
3843
|
return { rows, total, hasMore: offset + rows.length < total };
|
|
3722
3844
|
}
|
|
3723
3845
|
|
|
3846
|
+
// src/agents/checkpoints.ts
|
|
3847
|
+
class SqliteAgentContextCheckpointStore {
|
|
3848
|
+
db;
|
|
3849
|
+
constructor(db) {
|
|
3850
|
+
this.db = db;
|
|
3851
|
+
}
|
|
3852
|
+
async create(input) {
|
|
3853
|
+
assertCreateAgentContextCheckpointInput(input, "SixbSqlite");
|
|
3854
|
+
return this.db.transaction(() => {
|
|
3855
|
+
const runRow = this.db.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.createdByRunId);
|
|
3856
|
+
const threadRow = this.db.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.threadId);
|
|
3857
|
+
assertAgentContextCheckpointAuthority({
|
|
3858
|
+
create: input,
|
|
3859
|
+
run: runRow ? rowToRunRecord(runRow) : null,
|
|
3860
|
+
thread: threadRow ? rowToThreadRecord(threadRow) : null,
|
|
3861
|
+
prefix: "SixbSqlite"
|
|
3862
|
+
});
|
|
3863
|
+
const latest = this.findLatest(input.projectId, input.threadId);
|
|
3864
|
+
const headRow = this.db.query("SELECT COALESCE(MAX(seq), 0) AS seq FROM agent_messages WHERE project_id = ? AND thread_id = ?").get(input.projectId, input.threadId);
|
|
3865
|
+
const existingForRun = this.db.query("SELECT * FROM agent_context_checkpoints WHERE project_id = ? AND created_by_run_id = ?").get(input.projectId, input.createdByRunId);
|
|
3866
|
+
if (existingForRun) {
|
|
3867
|
+
const record = rowToContextCheckpointRecord(existingForRun);
|
|
3868
|
+
if (agentContextCheckpointMatchesCreateInput(record, input)) {
|
|
3869
|
+
assertAgentContextCheckpointReplayState({
|
|
3870
|
+
create: input,
|
|
3871
|
+
existing: record,
|
|
3872
|
+
latest,
|
|
3873
|
+
headSeq: headRow.seq,
|
|
3874
|
+
prefix: "SixbSqlite"
|
|
3875
|
+
});
|
|
3876
|
+
return record;
|
|
3877
|
+
}
|
|
3878
|
+
throw new AgentStorageError("invalid_state", `[SixbSqlite] Agent run '${input.createdByRunId}' already created a different context checkpoint.`);
|
|
3879
|
+
}
|
|
3880
|
+
const duplicateId = this.db.query("SELECT 1 FROM agent_context_checkpoints WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
|
|
3881
|
+
if (duplicateId) {
|
|
3882
|
+
throw new AgentStorageError("duplicate_id", `[SixbSqlite] Agent context checkpoint '${input.id}' already exists for project '${input.projectId}'.`);
|
|
3883
|
+
}
|
|
3884
|
+
const firstRetainedRow = this.db.query("SELECT * FROM agent_messages WHERE project_id = ? AND thread_id = ? AND seq > ? ORDER BY seq ASC LIMIT 1").get(input.projectId, input.threadId, input.summarizedThroughSeq);
|
|
3885
|
+
assertAgentContextCheckpointAnchors({
|
|
3886
|
+
create: input,
|
|
3887
|
+
latest,
|
|
3888
|
+
headSeq: headRow.seq,
|
|
3889
|
+
firstRetained: firstRetainedRow ? rowToMessageRecord(firstRetainedRow) : null,
|
|
3890
|
+
prefix: "SixbSqlite"
|
|
3891
|
+
});
|
|
3892
|
+
const createdAt = input.createdAt ?? new Date;
|
|
3893
|
+
this.db.query(`
|
|
3894
|
+
INSERT INTO agent_context_checkpoints (
|
|
3895
|
+
project_id,
|
|
3896
|
+
id,
|
|
3897
|
+
thread_id,
|
|
3898
|
+
created_by_run_id,
|
|
3899
|
+
previous_checkpoint_id,
|
|
3900
|
+
reason,
|
|
3901
|
+
summary,
|
|
3902
|
+
summary_format_version,
|
|
3903
|
+
summarized_through_seq,
|
|
3904
|
+
observed_head_seq,
|
|
3905
|
+
estimated_input_tokens_before,
|
|
3906
|
+
estimated_input_tokens_after,
|
|
3907
|
+
summary_model_id,
|
|
3908
|
+
created_at
|
|
3909
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3910
|
+
`).run(input.projectId, input.id, input.threadId, input.createdByRunId, input.expectedPreviousCheckpointId, input.reason, input.summary, input.summaryFormatVersion, input.summarizedThroughSeq, input.observedHeadSeq, input.estimatedInputTokensBefore, input.estimatedInputTokensAfter, input.summaryModelId, createdAt.toISOString());
|
|
3911
|
+
return this.requireById(input.projectId, input.id);
|
|
3912
|
+
})();
|
|
3913
|
+
}
|
|
3914
|
+
async getLatest(input) {
|
|
3915
|
+
return this.findLatest(input.projectId, input.threadId);
|
|
3916
|
+
}
|
|
3917
|
+
findLatest(projectId, threadId) {
|
|
3918
|
+
const row = this.db.query(`
|
|
3919
|
+
SELECT * FROM agent_context_checkpoints
|
|
3920
|
+
WHERE project_id = ? AND thread_id = ?
|
|
3921
|
+
ORDER BY summarized_through_seq DESC, created_at DESC, id DESC
|
|
3922
|
+
LIMIT 1
|
|
3923
|
+
`).get(projectId, threadId);
|
|
3924
|
+
return row ? rowToContextCheckpointRecord(row) : null;
|
|
3925
|
+
}
|
|
3926
|
+
requireById(projectId, id) {
|
|
3927
|
+
const row = this.db.query("SELECT * FROM agent_context_checkpoints WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
3928
|
+
if (!row) {
|
|
3929
|
+
throw new AgentStorageError("invalid_state", `[SixbSqlite] Failed to load agent context checkpoint '${id}' for project '${projectId}'.`);
|
|
3930
|
+
}
|
|
3931
|
+
return rowToContextCheckpointRecord(row);
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
// src/agents/messages.ts
|
|
3936
|
+
import { AGENT_MESSAGE_CONTENT_VERSION } from "@sixb/core/internal/agents";
|
|
3937
|
+
import {
|
|
3938
|
+
AgentStorageError as AgentStorageError2
|
|
3939
|
+
} from "@sixb/core/storage";
|
|
3940
|
+
|
|
3941
|
+
// src/storage-errors.ts
|
|
3942
|
+
function isUniqueConstraintError2(error) {
|
|
3943
|
+
return error instanceof Error && error.message.includes("UNIQUE constraint failed");
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3724
3946
|
// src/agents/messages.ts
|
|
3725
3947
|
class SqliteAgentMessageStore {
|
|
3726
3948
|
db;
|
|
@@ -3732,12 +3954,12 @@ class SqliteAgentMessageStore {
|
|
|
3732
3954
|
return this.db.transaction(() => {
|
|
3733
3955
|
const thread = this.db.query("SELECT 1 AS present FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.threadId);
|
|
3734
3956
|
if (!thread) {
|
|
3735
|
-
throw new
|
|
3957
|
+
throw new AgentStorageError2("thread_not_found", `[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`);
|
|
3736
3958
|
}
|
|
3737
3959
|
if (input.runId !== null) {
|
|
3738
3960
|
const run = this.db.query("SELECT 1 AS present FROM agent_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.runId);
|
|
3739
3961
|
if (!run) {
|
|
3740
|
-
throw new
|
|
3962
|
+
throw new AgentStorageError2("run_not_found", `[SixbSqlite] Agent run '${input.runId}' not found for project '${input.projectId}'.`);
|
|
3741
3963
|
}
|
|
3742
3964
|
}
|
|
3743
3965
|
const seqRow = this.db.query("SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM agent_messages WHERE project_id = ? AND thread_id = ?").get(input.projectId, input.threadId);
|
|
@@ -3761,7 +3983,7 @@ class SqliteAgentMessageStore {
|
|
|
3761
3983
|
`).run(input.projectId, input.id, input.threadId, input.runId, input.role, input.authorPrincipal?.type ?? null, input.authorPrincipal?.id ?? null, seqRow.next, JSON.stringify(input.parts), input.metadata === undefined ? null : JSON.stringify(input.metadata), AGENT_MESSAGE_CONTENT_VERSION, createdAt.toISOString(), input.completedAt ? input.completedAt.toISOString() : null);
|
|
3762
3984
|
} catch (error) {
|
|
3763
3985
|
if (isUniqueConstraintError2(error)) {
|
|
3764
|
-
throw new
|
|
3986
|
+
throw new AgentStorageError2("duplicate_id", `[SixbSqlite] Agent message '${input.id}' already exists for project '${input.projectId}'.`);
|
|
3765
3987
|
}
|
|
3766
3988
|
throw error;
|
|
3767
3989
|
}
|
|
@@ -3773,6 +3995,35 @@ class SqliteAgentMessageStore {
|
|
|
3773
3995
|
return this.requireMessage(input.projectId, input.id);
|
|
3774
3996
|
})();
|
|
3775
3997
|
}
|
|
3998
|
+
async deleteByRunId(input) {
|
|
3999
|
+
return this.db.transaction(() => {
|
|
4000
|
+
const thread = this.db.query("SELECT 1 AS present FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.threadId);
|
|
4001
|
+
if (!thread) {
|
|
4002
|
+
throw new AgentStorageError2("thread_not_found", `[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`);
|
|
4003
|
+
}
|
|
4004
|
+
const deleted = this.db.query("DELETE FROM agent_messages WHERE project_id = ? AND thread_id = ? AND run_id = ?").run(input.projectId, input.threadId, input.runId);
|
|
4005
|
+
if (deleted.changes === 0) {
|
|
4006
|
+
return 0;
|
|
4007
|
+
}
|
|
4008
|
+
const updatedAt = new Date().toISOString();
|
|
4009
|
+
this.db.query(`
|
|
4010
|
+
UPDATE agent_threads
|
|
4011
|
+
SET
|
|
4012
|
+
message_count = (
|
|
4013
|
+
SELECT COUNT(*) FROM agent_messages WHERE project_id = ? AND thread_id = ?
|
|
4014
|
+
),
|
|
4015
|
+
last_message_at = (
|
|
4016
|
+
SELECT created_at FROM agent_messages
|
|
4017
|
+
WHERE project_id = ? AND thread_id = ?
|
|
4018
|
+
ORDER BY seq DESC
|
|
4019
|
+
LIMIT 1
|
|
4020
|
+
),
|
|
4021
|
+
updated_at = ?
|
|
4022
|
+
WHERE project_id = ? AND id = ?
|
|
4023
|
+
`).run(input.projectId, input.threadId, input.projectId, input.threadId, updatedAt, input.projectId, input.threadId);
|
|
4024
|
+
return deleted.changes;
|
|
4025
|
+
})();
|
|
4026
|
+
}
|
|
3776
4027
|
async getById(params) {
|
|
3777
4028
|
const row = this.db.query("SELECT * FROM agent_messages WHERE project_id = ? AND id = ?").get(params.projectId, params.id);
|
|
3778
4029
|
return row ? rowToMessageRecord(row) : null;
|
|
@@ -3783,6 +4034,10 @@ class SqliteAgentMessageStore {
|
|
|
3783
4034
|
}
|
|
3784
4035
|
const whereClauses = ["project_id = ?", "thread_id = ?"];
|
|
3785
4036
|
const args = [input.projectId, input.threadId];
|
|
4037
|
+
if (input.afterSeq !== undefined) {
|
|
4038
|
+
whereClauses.push("seq > ?");
|
|
4039
|
+
args.push(input.afterSeq);
|
|
4040
|
+
}
|
|
3786
4041
|
if (input.roles) {
|
|
3787
4042
|
whereClauses.push(`role IN (${input.roles.map(() => "?").join(", ")})`);
|
|
3788
4043
|
args.push(...input.roles);
|
|
@@ -3802,7 +4057,7 @@ class SqliteAgentMessageStore {
|
|
|
3802
4057
|
requireMessage(projectId, id) {
|
|
3803
4058
|
const row = this.db.query("SELECT * FROM agent_messages WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
3804
4059
|
if (!row) {
|
|
3805
|
-
throw new
|
|
4060
|
+
throw new AgentStorageError2("invalid_state", `[SixbSqlite] Failed to load agent message '${id}' for project '${projectId}'.`);
|
|
3806
4061
|
}
|
|
3807
4062
|
return rowToMessageRecord(row);
|
|
3808
4063
|
}
|
|
@@ -3814,7 +4069,7 @@ import { normalizeRequesterGroupIds } from "@sixb/core/internal/auth";
|
|
|
3814
4069
|
import { serializeSixbFailure } from "@sixb/core/internal/errors";
|
|
3815
4070
|
import {
|
|
3816
4071
|
AGENT_RUN_FAILURE_CODES as AGENT_RUN_FAILURE_CODES2,
|
|
3817
|
-
AgentStorageError as
|
|
4072
|
+
AgentStorageError as AgentStorageError3
|
|
3818
4073
|
} from "@sixb/core/storage";
|
|
3819
4074
|
|
|
3820
4075
|
// src/run-list-query.ts
|
|
@@ -3890,10 +4145,10 @@ class SqliteAgentRunStore {
|
|
|
3890
4145
|
return this.db.transaction(() => {
|
|
3891
4146
|
const thread = this.db.query("SELECT active_run_id FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.threadId);
|
|
3892
4147
|
if (!thread) {
|
|
3893
|
-
throw new
|
|
4148
|
+
throw new AgentStorageError3("thread_not_found", `[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`);
|
|
3894
4149
|
}
|
|
3895
4150
|
if (thread.active_run_id !== null) {
|
|
3896
|
-
throw new
|
|
4151
|
+
throw new AgentStorageError3("active_run_exists", `[SixbSqlite] Agent thread '${input.threadId}' already has an active run '${thread.active_run_id}'.`);
|
|
3897
4152
|
}
|
|
3898
4153
|
try {
|
|
3899
4154
|
this.db.query(`
|
|
@@ -3912,7 +4167,7 @@ class SqliteAgentRunStore {
|
|
|
3912
4167
|
`).run(input.projectId, input.id, input.executionId, input.threadId, input.agentId, input.triggerMessageId, JSON.stringify(normalizeRequesterGroupIds(input.requesterGroupIds)), createdAt.toISOString());
|
|
3913
4168
|
} catch (error) {
|
|
3914
4169
|
if (isUniqueConstraintError2(error)) {
|
|
3915
|
-
throw new
|
|
4170
|
+
throw new AgentStorageError3("duplicate_id", `[SixbSqlite] Agent run '${input.id}' already exists for project '${input.projectId}'.`);
|
|
3916
4171
|
}
|
|
3917
4172
|
throw error;
|
|
3918
4173
|
}
|
|
@@ -3969,7 +4224,7 @@ class SqliteAgentRunStore {
|
|
|
3969
4224
|
return this.db.transaction(() => {
|
|
3970
4225
|
const run = this.requireRunning(input.projectId, input.id);
|
|
3971
4226
|
if (run.execution_token !== input.executionToken) {
|
|
3972
|
-
throw new
|
|
4227
|
+
throw new AgentStorageError3("execution_lost", `[SixbSqlite] Execution token is no longer current on agent run '${input.id}'.`);
|
|
3973
4228
|
}
|
|
3974
4229
|
this.db.query(`
|
|
3975
4230
|
UPDATE agent_runs
|
|
@@ -3985,7 +4240,7 @@ class SqliteAgentRunStore {
|
|
|
3985
4240
|
return this.db.transaction(() => {
|
|
3986
4241
|
const run = this.requireRunning(input.projectId, input.id);
|
|
3987
4242
|
if (run.execution_token !== input.executionToken) {
|
|
3988
|
-
throw new
|
|
4243
|
+
throw new AgentStorageError3("execution_lost", `[SixbSqlite] Execution token is no longer current on agent run '${input.id}'.`);
|
|
3989
4244
|
}
|
|
3990
4245
|
this.db.query(`
|
|
3991
4246
|
UPDATE agent_runs
|
|
@@ -4057,10 +4312,10 @@ class SqliteAgentRunStore {
|
|
|
4057
4312
|
requireStatus(projectId, id, status) {
|
|
4058
4313
|
const row = this.db.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
4059
4314
|
if (!row) {
|
|
4060
|
-
throw new
|
|
4315
|
+
throw new AgentStorageError3("run_not_found", `[SixbSqlite] Agent run '${id}' not found for project '${projectId}'.`);
|
|
4061
4316
|
}
|
|
4062
4317
|
if (row.status !== status) {
|
|
4063
|
-
throw new
|
|
4318
|
+
throw new AgentStorageError3("invalid_state", `[SixbSqlite] Agent run '${id}' is not ${status} (status '${row.status}').`);
|
|
4064
4319
|
}
|
|
4065
4320
|
return row;
|
|
4066
4321
|
}
|
|
@@ -4074,7 +4329,7 @@ class SqliteAgentRunStore {
|
|
|
4074
4329
|
requireRun(projectId, id) {
|
|
4075
4330
|
const row = this.db.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
4076
4331
|
if (!row) {
|
|
4077
|
-
throw new
|
|
4332
|
+
throw new AgentStorageError3("run_not_found", `[SixbSqlite] Agent run '${id}' not found for project '${projectId}'.`);
|
|
4078
4333
|
}
|
|
4079
4334
|
return rowToRunRecord(row);
|
|
4080
4335
|
}
|
|
@@ -4082,7 +4337,7 @@ class SqliteAgentRunStore {
|
|
|
4082
4337
|
|
|
4083
4338
|
// src/agents/threads.ts
|
|
4084
4339
|
import {
|
|
4085
|
-
AgentStorageError as
|
|
4340
|
+
AgentStorageError as AgentStorageError4
|
|
4086
4341
|
} from "@sixb/core/storage";
|
|
4087
4342
|
class SqliteAgentThreadStore {
|
|
4088
4343
|
db;
|
|
@@ -4111,7 +4366,7 @@ class SqliteAgentThreadStore {
|
|
|
4111
4366
|
`).run(input.projectId, input.id, input.agentId, input.ownerPrincipal.type, input.ownerPrincipal.id, input.title ?? null, input.status ?? "active", createdAt.toISOString(), updatedAt.toISOString());
|
|
4112
4367
|
} catch (error) {
|
|
4113
4368
|
if (isUniqueConstraintError2(error)) {
|
|
4114
|
-
throw new
|
|
4369
|
+
throw new AgentStorageError4("duplicate_id", `[SixbSqlite] Agent thread '${input.id}' already exists for project '${input.projectId}'.`);
|
|
4115
4370
|
}
|
|
4116
4371
|
throw error;
|
|
4117
4372
|
}
|
|
@@ -4158,7 +4413,7 @@ class SqliteAgentThreadStore {
|
|
|
4158
4413
|
requireThread(projectId, id) {
|
|
4159
4414
|
const row = this.db.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
4160
4415
|
if (!row) {
|
|
4161
|
-
throw new
|
|
4416
|
+
throw new AgentStorageError4("invalid_state", `[SixbSqlite] Failed to load agent thread '${id}' for project '${projectId}'.`);
|
|
4162
4417
|
}
|
|
4163
4418
|
return rowToThreadRecord(row);
|
|
4164
4419
|
}
|
|
@@ -4169,6 +4424,7 @@ class SqliteAgentStorage {
|
|
|
4169
4424
|
threads;
|
|
4170
4425
|
runs;
|
|
4171
4426
|
messages;
|
|
4427
|
+
checkpoints;
|
|
4172
4428
|
connection;
|
|
4173
4429
|
constructor(options) {
|
|
4174
4430
|
this.connection = openSqliteStoreConnection(options);
|
|
@@ -4178,11 +4434,497 @@ class SqliteAgentStorage {
|
|
|
4178
4434
|
this.threads = new SqliteAgentThreadStore(this.connection.db);
|
|
4179
4435
|
this.runs = new SqliteAgentRunStore(this.connection.db, options.executions);
|
|
4180
4436
|
this.messages = new SqliteAgentMessageStore(this.connection.db);
|
|
4437
|
+
this.checkpoints = new SqliteAgentContextCheckpointStore(this.connection.db);
|
|
4181
4438
|
}
|
|
4182
4439
|
close() {
|
|
4183
4440
|
closeSqliteStoreConnection(this.connection);
|
|
4184
4441
|
}
|
|
4185
4442
|
}
|
|
4443
|
+
// src/ai-cost-storage.ts
|
|
4444
|
+
import {
|
|
4445
|
+
aiModelCallCostDetails,
|
|
4446
|
+
aiModelCallCostMatchesUsage,
|
|
4447
|
+
buildAiAccountingOverviewFromFragments,
|
|
4448
|
+
normalizeAiAccountingQuery,
|
|
4449
|
+
normalizeAiModelCallAccountingQuery,
|
|
4450
|
+
normalizeAiModelCallCostRecord,
|
|
4451
|
+
parseAiModelCallCostDetails,
|
|
4452
|
+
toAccountingItem
|
|
4453
|
+
} from "@sixb/core/internal/ai-cost-storage-provider";
|
|
4454
|
+
import {
|
|
4455
|
+
AiCostStorageError
|
|
4456
|
+
} from "@sixb/core/storage";
|
|
4457
|
+
class SqliteAiCostStorage {
|
|
4458
|
+
connection;
|
|
4459
|
+
constructor(connection) {
|
|
4460
|
+
this.connection = connection;
|
|
4461
|
+
}
|
|
4462
|
+
async recordModelCallCost(input) {
|
|
4463
|
+
const record = normalizeAiModelCallCostRecord(input);
|
|
4464
|
+
runImmediateTransaction(this.connection.db, () => {
|
|
4465
|
+
const usage = this.findUsage(record.projectId, record.usageRecordId);
|
|
4466
|
+
if (!usage)
|
|
4467
|
+
throw missingUsage(record);
|
|
4468
|
+
assertCostMatchesUsage(record, usage);
|
|
4469
|
+
if (this.findCost(record.projectId, record.usageRecordId))
|
|
4470
|
+
return;
|
|
4471
|
+
try {
|
|
4472
|
+
this.insertCost(record);
|
|
4473
|
+
} catch (error) {
|
|
4474
|
+
if (!isUniqueConstraintError2(error))
|
|
4475
|
+
throw error;
|
|
4476
|
+
if (!this.findCost(record.projectId, record.usageRecordId))
|
|
4477
|
+
throw error;
|
|
4478
|
+
}
|
|
4479
|
+
});
|
|
4480
|
+
}
|
|
4481
|
+
async summarizeExecutions(input) {
|
|
4482
|
+
assertNonBlank(input.projectId, "cost summary projectId");
|
|
4483
|
+
for (const id of input.executionIds)
|
|
4484
|
+
assertNonBlank(id, "cost summary executionId");
|
|
4485
|
+
if (input.executionIds.length === 0)
|
|
4486
|
+
return [];
|
|
4487
|
+
const rows = this.connection.db.query(`
|
|
4488
|
+
WITH requested AS (SELECT DISTINCT value AS execution_id FROM json_each(?))
|
|
4489
|
+
SELECT usage.execution_id,
|
|
4490
|
+
cost.status,
|
|
4491
|
+
cost.currency,
|
|
4492
|
+
CAST(cost.amount_nanos AS TEXT) AS amount_nanos
|
|
4493
|
+
FROM requested
|
|
4494
|
+
JOIN ai_model_call_usage AS usage
|
|
4495
|
+
ON usage.project_id = ? AND usage.execution_id = requested.execution_id
|
|
4496
|
+
LEFT JOIN ai_model_call_valuations AS cost
|
|
4497
|
+
ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
|
|
4498
|
+
`).all(JSON.stringify([...new Set(input.executionIds)]), input.projectId);
|
|
4499
|
+
return aggregateSummaryRows(input.executionIds, rows);
|
|
4500
|
+
}
|
|
4501
|
+
async queryProjectOverview(input) {
|
|
4502
|
+
const query = normalizeAiAccountingQuery(input);
|
|
4503
|
+
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);
|
|
4504
|
+
return buildAiAccountingOverviewFromFragments(input, rows.map(aggregateFragmentFromRow));
|
|
4505
|
+
}
|
|
4506
|
+
async listModelCalls(input) {
|
|
4507
|
+
const query = normalizeAiModelCallAccountingQuery(input);
|
|
4508
|
+
const parameters = accountingListFilterParameters(query);
|
|
4509
|
+
const count = this.connection.db.query(SQLITE_ACCOUNTING_LIST_COUNT_SQL).get(...parameters);
|
|
4510
|
+
const rows = this.connection.db.query(SQLITE_ACCOUNTING_LIST_PAGE_SQL).all(...parameters, query.limit, query.offset);
|
|
4511
|
+
const items = rows.map((row) => toAccountingItem(accountingItemFromRow(row)));
|
|
4512
|
+
const total = safeSqlCount(count.total, "model-call list total");
|
|
4513
|
+
return structuredClone({
|
|
4514
|
+
items,
|
|
4515
|
+
total,
|
|
4516
|
+
hasMore: query.offset + items.length < total
|
|
4517
|
+
});
|
|
4518
|
+
}
|
|
4519
|
+
insertCost(record) {
|
|
4520
|
+
const rated = record.status === "rated" ? record : undefined;
|
|
4521
|
+
const details = aiModelCallCostDetails(record);
|
|
4522
|
+
this.connection.db.query(`INSERT INTO ai_model_call_valuations (
|
|
4523
|
+
project_id, usage_record_id, status, provider_id, model_id,
|
|
4524
|
+
currency, amount_nanos, reason, details, rated_at
|
|
4525
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(record.projectId, record.usageRecordId, record.status, record.billingIdentity?.providerId ?? null, record.billingIdentity?.modelId ?? null, rated?.money.currency ?? null, rated ? BigInt(rated.money.amountNanos) : null, record.status === "unpriceable" ? record.reason : null, JSON.stringify(details), record.ratedAt.toISOString());
|
|
4526
|
+
}
|
|
4527
|
+
findCost(projectId, usageId) {
|
|
4528
|
+
const row = this.connection.db.query(`SELECT *, CAST(amount_nanos AS TEXT) AS amount_nanos
|
|
4529
|
+
FROM ai_model_call_valuations
|
|
4530
|
+
WHERE project_id = ? AND usage_record_id = ?`).get(projectId, usageId);
|
|
4531
|
+
return row ? rowToCost(row) : null;
|
|
4532
|
+
}
|
|
4533
|
+
findUsage(projectId, id) {
|
|
4534
|
+
const row = this.connection.db.query("SELECT * FROM ai_model_call_usage WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
4535
|
+
return row ? usageFromRow(row, []) : null;
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
var SQLITE_ACCOUNTING_LIST_FILTER = `
|
|
4539
|
+
usage.project_id = ? AND usage.occurred_at >= ? AND usage.occurred_at < ?
|
|
4540
|
+
AND (? IS NULL OR usage.provider_id = ?)
|
|
4541
|
+
AND (? IS NULL OR usage.requested_model_id = ?)
|
|
4542
|
+
AND (? IS NULL OR usage.execution_id = ?)
|
|
4543
|
+
AND (
|
|
4544
|
+
? IS NULL OR ? = CASE
|
|
4545
|
+
WHEN cost.status = 'rated' THEN 'rated'
|
|
4546
|
+
WHEN cost.status = 'unpriceable' THEN 'unpriceable'
|
|
4547
|
+
ELSE 'unvalued'
|
|
4548
|
+
END
|
|
4549
|
+
)
|
|
4550
|
+
`;
|
|
4551
|
+
var SQLITE_ACCOUNTING_LIST_COUNT_SQL = `
|
|
4552
|
+
SELECT CAST(COUNT(*) AS TEXT) AS total
|
|
4553
|
+
FROM ai_model_call_usage AS usage
|
|
4554
|
+
LEFT JOIN ai_model_call_valuations AS cost
|
|
4555
|
+
ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
|
|
4556
|
+
WHERE ${SQLITE_ACCOUNTING_LIST_FILTER}
|
|
4557
|
+
`;
|
|
4558
|
+
var SQLITE_ACCOUNTING_LIST_PAGE_SQL = `
|
|
4559
|
+
SELECT usage.*,
|
|
4560
|
+
CASE
|
|
4561
|
+
WHEN direct_agent.id IS NOT NULL THEN 'agent'
|
|
4562
|
+
WHEN workflow_agent.node_run_id IS NOT NULL THEN 'workflowAgent'
|
|
4563
|
+
ELSE NULL
|
|
4564
|
+
END AS attribution_kind,
|
|
4565
|
+
COALESCE(direct_agent.agent_id, workflow_agent.agent_id) AS attribution_agent_id,
|
|
4566
|
+
direct_agent.id AS attribution_agent_run_id,
|
|
4567
|
+
direct_agent.thread_id AS attribution_thread_id,
|
|
4568
|
+
workflow_agent.node_run_id AS attribution_node_run_id,
|
|
4569
|
+
workflow_node.workflow_id AS attribution_workflow_id,
|
|
4570
|
+
workflow_node.workflow_run_id AS attribution_workflow_run_id,
|
|
4571
|
+
COALESCE((
|
|
4572
|
+
SELECT json_group_array(group_id) FROM (
|
|
4573
|
+
SELECT group_id FROM ai_model_call_usage_groups
|
|
4574
|
+
WHERE project_id = usage.project_id AND usage_record_id = usage.id
|
|
4575
|
+
ORDER BY group_id
|
|
4576
|
+
)
|
|
4577
|
+
), '[]') AS requester_group_ids,
|
|
4578
|
+
cost.status AS cost_status,
|
|
4579
|
+
cost.provider_id AS cost_provider_id,
|
|
4580
|
+
cost.model_id AS cost_model_id,
|
|
4581
|
+
cost.currency AS cost_currency,
|
|
4582
|
+
CAST(cost.amount_nanos AS TEXT) AS cost_amount_nanos,
|
|
4583
|
+
cost.reason AS cost_reason,
|
|
4584
|
+
cost.details AS cost_details,
|
|
4585
|
+
cost.rated_at AS cost_rated_at
|
|
4586
|
+
FROM ai_model_call_usage AS usage
|
|
4587
|
+
LEFT JOIN agent_runs AS direct_agent
|
|
4588
|
+
ON direct_agent.project_id = usage.project_id
|
|
4589
|
+
AND direct_agent.execution_id = usage.execution_id
|
|
4590
|
+
LEFT JOIN workflow_agent_node_runs AS workflow_agent
|
|
4591
|
+
ON workflow_agent.project_id = usage.project_id
|
|
4592
|
+
AND workflow_agent.execution_id = usage.execution_id
|
|
4593
|
+
LEFT JOIN workflow_node_runs AS workflow_node
|
|
4594
|
+
ON workflow_node.project_id = workflow_agent.project_id
|
|
4595
|
+
AND workflow_node.id = workflow_agent.node_run_id
|
|
4596
|
+
LEFT JOIN ai_model_call_valuations AS cost
|
|
4597
|
+
ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
|
|
4598
|
+
WHERE ${SQLITE_ACCOUNTING_LIST_FILTER}
|
|
4599
|
+
ORDER BY usage.occurred_at DESC, usage.id ASC
|
|
4600
|
+
LIMIT ? OFFSET ?
|
|
4601
|
+
`;
|
|
4602
|
+
var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
4603
|
+
WITH classified AS (
|
|
4604
|
+
SELECT usage.provider_id,
|
|
4605
|
+
usage.requested_model_id,
|
|
4606
|
+
usage.occurred_at,
|
|
4607
|
+
usage.input_tokens,
|
|
4608
|
+
usage.output_tokens,
|
|
4609
|
+
usage.uncached_input_tokens,
|
|
4610
|
+
usage.cache_read_input_tokens,
|
|
4611
|
+
usage.cache_write_input_tokens,
|
|
4612
|
+
usage.text_output_tokens,
|
|
4613
|
+
usage.reasoning_output_tokens,
|
|
4614
|
+
usage.reporting_status,
|
|
4615
|
+
COALESCE(direct_agent.agent_id, workflow_agent.agent_id) AS attribution_agent_id,
|
|
4616
|
+
workflow_node.workflow_id AS attribution_workflow_id,
|
|
4617
|
+
CASE
|
|
4618
|
+
WHEN cost.status = 'rated' THEN 'rated'
|
|
4619
|
+
WHEN cost.status = 'unpriceable' THEN 'unpriceable'
|
|
4620
|
+
ELSE 'unvalued'
|
|
4621
|
+
END AS valuation_status,
|
|
4622
|
+
CASE WHEN cost.status = 'rated' THEN cost.currency ELSE NULL END AS amount_currency,
|
|
4623
|
+
CASE WHEN cost.status = 'rated' THEN cost.amount_nanos ELSE NULL END AS amount_nanos
|
|
4624
|
+
FROM ai_model_call_usage AS usage
|
|
4625
|
+
LEFT JOIN agent_runs AS direct_agent
|
|
4626
|
+
ON direct_agent.project_id = usage.project_id
|
|
4627
|
+
AND direct_agent.execution_id = usage.execution_id
|
|
4628
|
+
LEFT JOIN workflow_agent_node_runs AS workflow_agent
|
|
4629
|
+
ON workflow_agent.project_id = usage.project_id
|
|
4630
|
+
AND workflow_agent.execution_id = usage.execution_id
|
|
4631
|
+
LEFT JOIN workflow_node_runs AS workflow_node
|
|
4632
|
+
ON workflow_node.project_id = workflow_agent.project_id
|
|
4633
|
+
AND workflow_node.id = workflow_agent.node_run_id
|
|
4634
|
+
LEFT JOIN ai_model_call_valuations AS cost
|
|
4635
|
+
ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
|
|
4636
|
+
WHERE usage.project_id = ? AND usage.occurred_at >= ? AND usage.occurred_at < ?
|
|
4637
|
+
AND (? IS NULL OR usage.provider_id = ?)
|
|
4638
|
+
AND (? IS NULL OR usage.requested_model_id = ?)
|
|
4639
|
+
), expanded AS (
|
|
4640
|
+
SELECT 'totals' AS dimension, NULL AS key_1, NULL AS key_2, NULL AS bucket_start,
|
|
4641
|
+
classified.*
|
|
4642
|
+
FROM classified
|
|
4643
|
+
UNION ALL
|
|
4644
|
+
SELECT 'series', NULL, NULL,
|
|
4645
|
+
CASE ?
|
|
4646
|
+
WHEN 'hour' THEN strftime('%Y-%m-%dT%H:00:00.000Z', occurred_at)
|
|
4647
|
+
WHEN 'day' THEN strftime('%Y-%m-%dT00:00:00.000Z', occurred_at)
|
|
4648
|
+
ELSE strftime(
|
|
4649
|
+
'%Y-%m-%dT00:00:00.000Z',
|
|
4650
|
+
occurred_at,
|
|
4651
|
+
printf('-%d days', (CAST(strftime('%w', occurred_at) AS INTEGER) + 6) % 7)
|
|
4652
|
+
)
|
|
4653
|
+
END,
|
|
4654
|
+
classified.*
|
|
4655
|
+
FROM classified
|
|
4656
|
+
UNION ALL
|
|
4657
|
+
SELECT 'model', provider_id, requested_model_id, NULL, classified.* FROM classified
|
|
4658
|
+
UNION ALL
|
|
4659
|
+
SELECT 'agent', attribution_agent_id, NULL, NULL, classified.*
|
|
4660
|
+
FROM classified WHERE attribution_agent_id IS NOT NULL
|
|
4661
|
+
UNION ALL
|
|
4662
|
+
SELECT 'workflow', attribution_workflow_id, NULL, NULL, classified.*
|
|
4663
|
+
FROM classified WHERE attribution_workflow_id IS NOT NULL
|
|
4664
|
+
)
|
|
4665
|
+
SELECT dimension,
|
|
4666
|
+
key_1,
|
|
4667
|
+
key_2,
|
|
4668
|
+
bucket_start,
|
|
4669
|
+
amount_currency,
|
|
4670
|
+
CAST(COUNT(*) AS TEXT) AS model_call_count,
|
|
4671
|
+
CAST(SUM(CASE WHEN reporting_status <> 'unavailable' THEN 1 ELSE 0 END) AS TEXT)
|
|
4672
|
+
AS reported_usage_call_count,
|
|
4673
|
+
CAST(COUNT(input_tokens) AS TEXT) AS input_tokens_count,
|
|
4674
|
+
CAST(SUM(input_tokens) AS TEXT) AS input_tokens_sum,
|
|
4675
|
+
CAST(COUNT(output_tokens) AS TEXT) AS output_tokens_count,
|
|
4676
|
+
CAST(SUM(output_tokens) AS TEXT) AS output_tokens_sum,
|
|
4677
|
+
CAST(COUNT(uncached_input_tokens) AS TEXT) AS uncached_input_tokens_count,
|
|
4678
|
+
CAST(SUM(uncached_input_tokens) AS TEXT) AS uncached_input_tokens_sum,
|
|
4679
|
+
CAST(COUNT(cache_read_input_tokens) AS TEXT) AS cache_read_input_tokens_count,
|
|
4680
|
+
CAST(SUM(cache_read_input_tokens) AS TEXT) AS cache_read_input_tokens_sum,
|
|
4681
|
+
CAST(COUNT(cache_write_input_tokens) AS TEXT) AS cache_write_input_tokens_count,
|
|
4682
|
+
CAST(SUM(cache_write_input_tokens) AS TEXT) AS cache_write_input_tokens_sum,
|
|
4683
|
+
CAST(COUNT(text_output_tokens) AS TEXT) AS text_output_tokens_count,
|
|
4684
|
+
CAST(SUM(text_output_tokens) AS TEXT) AS text_output_tokens_sum,
|
|
4685
|
+
CAST(COUNT(reasoning_output_tokens) AS TEXT) AS reasoning_output_tokens_count,
|
|
4686
|
+
CAST(SUM(reasoning_output_tokens) AS TEXT) AS reasoning_output_tokens_sum,
|
|
4687
|
+
CAST(SUM(CASE WHEN valuation_status = 'rated' THEN 1 ELSE 0 END) AS TEXT)
|
|
4688
|
+
AS rated_call_count,
|
|
4689
|
+
CAST(SUM(CASE WHEN valuation_status = 'unpriceable' THEN 1 ELSE 0 END) AS TEXT)
|
|
4690
|
+
AS unpriceable_call_count,
|
|
4691
|
+
CAST(SUM(CASE WHEN valuation_status = 'unvalued' THEN 1 ELSE 0 END) AS TEXT)
|
|
4692
|
+
AS unvalued_call_count,
|
|
4693
|
+
CAST(SUM(amount_nanos / 1000000000000) AS TEXT) AS amount_high,
|
|
4694
|
+
CAST(SUM((amount_nanos / 1000000) % 1000000) AS TEXT) AS amount_middle,
|
|
4695
|
+
CAST(SUM(amount_nanos % 1000000) AS TEXT) AS amount_low
|
|
4696
|
+
FROM expanded
|
|
4697
|
+
GROUP BY dimension, key_1, key_2, bucket_start, amount_currency
|
|
4698
|
+
`;
|
|
4699
|
+
function accountingListFilterParameters(query) {
|
|
4700
|
+
return [
|
|
4701
|
+
query.projectId,
|
|
4702
|
+
query.from.toISOString(),
|
|
4703
|
+
query.to.toISOString(),
|
|
4704
|
+
query.providerId ?? null,
|
|
4705
|
+
query.providerId ?? null,
|
|
4706
|
+
query.modelId ?? null,
|
|
4707
|
+
query.modelId ?? null,
|
|
4708
|
+
query.executionId ?? null,
|
|
4709
|
+
query.executionId ?? null,
|
|
4710
|
+
query.valuationStatus ?? null,
|
|
4711
|
+
query.valuationStatus ?? null
|
|
4712
|
+
];
|
|
4713
|
+
}
|
|
4714
|
+
function aggregateFragmentFromRow(row) {
|
|
4715
|
+
const dimensionKeys = row.dimension === "model" ? { providerId: required(row.key_1), modelId: required(row.key_2) } : row.dimension === "agent" ? { agentId: required(row.key_1) } : row.dimension === "workflow" ? { workflowId: required(row.key_1) } : row.dimension === "series" ? { start: new Date(required(row.bucket_start)) } : {};
|
|
4716
|
+
return {
|
|
4717
|
+
dimension: row.dimension,
|
|
4718
|
+
...dimensionKeys,
|
|
4719
|
+
modelCallCount: row.model_call_count,
|
|
4720
|
+
usage: {
|
|
4721
|
+
reportedCallCount: row.reported_usage_call_count,
|
|
4722
|
+
inputTokens: meterFragment(row.input_tokens_count, row.input_tokens_sum),
|
|
4723
|
+
outputTokens: meterFragment(row.output_tokens_count, row.output_tokens_sum),
|
|
4724
|
+
uncachedInputTokens: meterFragment(row.uncached_input_tokens_count, row.uncached_input_tokens_sum),
|
|
4725
|
+
cacheReadInputTokens: meterFragment(row.cache_read_input_tokens_count, row.cache_read_input_tokens_sum),
|
|
4726
|
+
cacheWriteInputTokens: meterFragment(row.cache_write_input_tokens_count, row.cache_write_input_tokens_sum),
|
|
4727
|
+
textOutputTokens: meterFragment(row.text_output_tokens_count, row.text_output_tokens_sum),
|
|
4728
|
+
reasoningOutputTokens: meterFragment(row.reasoning_output_tokens_count, row.reasoning_output_tokens_sum)
|
|
4729
|
+
},
|
|
4730
|
+
costs: {
|
|
4731
|
+
...row.amount_currency === null || row.amount_high === null || row.amount_middle === null || row.amount_low === null ? {} : {
|
|
4732
|
+
amount: {
|
|
4733
|
+
currency: row.amount_currency,
|
|
4734
|
+
amountNanos: sqliteAggregateAmount(row)
|
|
4735
|
+
}
|
|
4736
|
+
},
|
|
4737
|
+
ratedCallCount: row.rated_call_count,
|
|
4738
|
+
unpriceableCallCount: row.unpriceable_call_count,
|
|
4739
|
+
unvaluedCallCount: row.unvalued_call_count
|
|
4740
|
+
}
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
function meterFragment(presentCallCount, total) {
|
|
4744
|
+
return { presentCallCount, total };
|
|
4745
|
+
}
|
|
4746
|
+
function sqliteAggregateAmount(row) {
|
|
4747
|
+
return (BigInt(required(row.amount_high)) * 1000000000000n + BigInt(required(row.amount_middle)) * 1000000n + BigInt(required(row.amount_low))).toString();
|
|
4748
|
+
}
|
|
4749
|
+
function accountingItemFromRow(row) {
|
|
4750
|
+
const usage = usageFromRow(row, parseStringArray(row.requester_group_ids));
|
|
4751
|
+
const cost = row.cost_status === null ? undefined : costFromJoinedRow(row);
|
|
4752
|
+
const attribution = row.attribution_kind === null ? undefined : row.attribution_kind === "agent" ? {
|
|
4753
|
+
kind: "agent",
|
|
4754
|
+
agentId: required(row.attribution_agent_id),
|
|
4755
|
+
agentRunId: required(row.attribution_agent_run_id),
|
|
4756
|
+
threadId: required(row.attribution_thread_id)
|
|
4757
|
+
} : {
|
|
4758
|
+
kind: "workflowAgent",
|
|
4759
|
+
agentId: required(row.attribution_agent_id),
|
|
4760
|
+
nodeRunId: required(row.attribution_node_run_id),
|
|
4761
|
+
workflowId: required(row.attribution_workflow_id),
|
|
4762
|
+
workflowRunId: required(row.attribution_workflow_run_id)
|
|
4763
|
+
};
|
|
4764
|
+
return {
|
|
4765
|
+
usage,
|
|
4766
|
+
...attribution ? { attribution } : {},
|
|
4767
|
+
...cost ? { cost } : {}
|
|
4768
|
+
};
|
|
4769
|
+
}
|
|
4770
|
+
function usageFromRow(row, requesterGroupIds) {
|
|
4771
|
+
return {
|
|
4772
|
+
id: row.id,
|
|
4773
|
+
projectId: row.project_id,
|
|
4774
|
+
executionId: row.execution_id,
|
|
4775
|
+
attempt: row.attempt,
|
|
4776
|
+
callId: row.call_id,
|
|
4777
|
+
requesterGroupIds,
|
|
4778
|
+
providerId: row.provider_id,
|
|
4779
|
+
requestedModelId: row.requested_model_id,
|
|
4780
|
+
...row.response_model_id === null ? {} : { responseModelId: row.response_model_id },
|
|
4781
|
+
responseId: row.response_id,
|
|
4782
|
+
usage: {
|
|
4783
|
+
...row.input_tokens === null ? {} : { inputTokens: row.input_tokens },
|
|
4784
|
+
...row.output_tokens === null ? {} : { outputTokens: row.output_tokens },
|
|
4785
|
+
...row.total_tokens === null ? {} : { totalTokens: row.total_tokens },
|
|
4786
|
+
...row.uncached_input_tokens === null ? {} : { uncachedInputTokens: row.uncached_input_tokens },
|
|
4787
|
+
...row.cache_read_input_tokens === null ? {} : { cacheReadInputTokens: row.cache_read_input_tokens },
|
|
4788
|
+
...row.cache_write_input_tokens === null ? {} : { cacheWriteInputTokens: row.cache_write_input_tokens },
|
|
4789
|
+
...row.text_output_tokens === null ? {} : { textOutputTokens: row.text_output_tokens },
|
|
4790
|
+
...row.reasoning_output_tokens === null ? {} : { reasoningOutputTokens: row.reasoning_output_tokens },
|
|
4791
|
+
reportingStatus: row.reporting_status
|
|
4792
|
+
},
|
|
4793
|
+
...row.raw_usage === null ? {} : { rawUsage: JSON.parse(row.raw_usage) },
|
|
4794
|
+
occurredAt: new Date(row.occurred_at),
|
|
4795
|
+
recordedAt: new Date(row.recorded_at)
|
|
4796
|
+
};
|
|
4797
|
+
}
|
|
4798
|
+
function rowToCost(row) {
|
|
4799
|
+
return costRecord({
|
|
4800
|
+
projectId: row.project_id,
|
|
4801
|
+
usageId: row.usage_record_id,
|
|
4802
|
+
status: row.status,
|
|
4803
|
+
providerId: row.provider_id,
|
|
4804
|
+
modelId: row.model_id,
|
|
4805
|
+
currency: row.currency,
|
|
4806
|
+
amountNanos: row.amount_nanos,
|
|
4807
|
+
reason: row.reason,
|
|
4808
|
+
details: row.details,
|
|
4809
|
+
ratedAt: row.rated_at
|
|
4810
|
+
});
|
|
4811
|
+
}
|
|
4812
|
+
function costFromJoinedRow(row) {
|
|
4813
|
+
return costRecord({
|
|
4814
|
+
projectId: row.project_id,
|
|
4815
|
+
usageId: row.id,
|
|
4816
|
+
status: required(row.cost_status),
|
|
4817
|
+
providerId: row.cost_provider_id,
|
|
4818
|
+
modelId: row.cost_model_id,
|
|
4819
|
+
currency: row.cost_currency,
|
|
4820
|
+
amountNanos: row.cost_amount_nanos,
|
|
4821
|
+
reason: row.cost_reason,
|
|
4822
|
+
details: required(row.cost_details),
|
|
4823
|
+
ratedAt: required(row.cost_rated_at)
|
|
4824
|
+
});
|
|
4825
|
+
}
|
|
4826
|
+
function costRecord(input) {
|
|
4827
|
+
const details = parseAiModelCallCostDetails(input.details);
|
|
4828
|
+
const base = {
|
|
4829
|
+
projectId: input.projectId,
|
|
4830
|
+
usageRecordId: input.usageId,
|
|
4831
|
+
pricingContext: details.pricingContext,
|
|
4832
|
+
priceSource: {
|
|
4833
|
+
...details.priceSource,
|
|
4834
|
+
observedAt: new Date(details.priceSource.observedAt)
|
|
4835
|
+
},
|
|
4836
|
+
ratedAt: new Date(input.ratedAt)
|
|
4837
|
+
};
|
|
4838
|
+
if (input.status === "rated") {
|
|
4839
|
+
return normalizeAiModelCallCostRecord({
|
|
4840
|
+
...base,
|
|
4841
|
+
status: "rated",
|
|
4842
|
+
billingIdentity: { providerId: required(input.providerId), modelId: required(input.modelId) },
|
|
4843
|
+
money: { currency: required(input.currency), amountNanos: required(input.amountNanos) },
|
|
4844
|
+
components: required(details.components)
|
|
4845
|
+
});
|
|
4846
|
+
}
|
|
4847
|
+
return normalizeAiModelCallCostRecord({
|
|
4848
|
+
...base,
|
|
4849
|
+
status: "unpriceable",
|
|
4850
|
+
...input.providerId && input.modelId ? { billingIdentity: { providerId: input.providerId, modelId: input.modelId } } : {},
|
|
4851
|
+
reason: required(input.reason),
|
|
4852
|
+
...details.missingMeters ? { missingMeters: details.missingMeters } : {}
|
|
4853
|
+
});
|
|
4854
|
+
}
|
|
4855
|
+
function aggregateSummaryRows(ids, rows) {
|
|
4856
|
+
const map = new Map(ids.map((id) => [id, accumulator()]));
|
|
4857
|
+
for (const row of rows) {
|
|
4858
|
+
const value = map.get(row.execution_id);
|
|
4859
|
+
if (!value)
|
|
4860
|
+
continue;
|
|
4861
|
+
if (row.status === null)
|
|
4862
|
+
value.unvaluedCallCount += 1;
|
|
4863
|
+
else if (row.status === "unpriceable")
|
|
4864
|
+
value.unpriceableCallCount += 1;
|
|
4865
|
+
else {
|
|
4866
|
+
value.ratedCallCount += 1;
|
|
4867
|
+
add(value.amounts, required(row.currency), required(row.amount_nanos));
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
return ids.map((id) => finish(map.get(id)));
|
|
4871
|
+
}
|
|
4872
|
+
function accumulator() {
|
|
4873
|
+
return {
|
|
4874
|
+
amounts: new Map,
|
|
4875
|
+
ratedCallCount: 0,
|
|
4876
|
+
unpriceableCallCount: 0,
|
|
4877
|
+
unvaluedCallCount: 0
|
|
4878
|
+
};
|
|
4879
|
+
}
|
|
4880
|
+
function finish(value) {
|
|
4881
|
+
return {
|
|
4882
|
+
amounts: [...value.amounts].sort(([left], [right]) => left.localeCompare(right)).map(([currency, amount]) => ({ currency, amountNanos: amount.toString() })),
|
|
4883
|
+
ratedCallCount: value.ratedCallCount,
|
|
4884
|
+
unpriceableCallCount: value.unpriceableCallCount,
|
|
4885
|
+
unvaluedCallCount: value.unvaluedCallCount
|
|
4886
|
+
};
|
|
4887
|
+
}
|
|
4888
|
+
function add(amounts, currency, amount) {
|
|
4889
|
+
amounts.set(currency, (amounts.get(currency) ?? 0n) + BigInt(amount));
|
|
4890
|
+
}
|
|
4891
|
+
function assertCostMatchesUsage(record, usage) {
|
|
4892
|
+
if (!aiModelCallCostMatchesUsage(record, usage)) {
|
|
4893
|
+
throw new AiCostStorageError("cost_mismatch", `[SixbSqlite] AI valuation for usage '${record.usageRecordId}' does not match its usage record.`);
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
function missingUsage(record) {
|
|
4897
|
+
return new AiCostStorageError("missing_usage", `[SixbSqlite] AI usage '${record.usageRecordId}' does not exist.`);
|
|
4898
|
+
}
|
|
4899
|
+
function parseStringArray(value) {
|
|
4900
|
+
const parsed = JSON.parse(value);
|
|
4901
|
+
if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) {
|
|
4902
|
+
throw new Error("[SixbSqlite] AI requester group ids are invalid.");
|
|
4903
|
+
}
|
|
4904
|
+
return parsed;
|
|
4905
|
+
}
|
|
4906
|
+
function assertNonBlank(value, field) {
|
|
4907
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4908
|
+
throw new TypeError(`[Sixb] AI ${field} must be nonblank.`);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
function safeSqlCount(value, field) {
|
|
4912
|
+
if (!/^(0|[1-9]\d*)$/.test(value)) {
|
|
4913
|
+
throw new Error(`[SixbSqlite] AI accounting ${field} is invalid.`);
|
|
4914
|
+
}
|
|
4915
|
+
const count = Number(value);
|
|
4916
|
+
if (!Number.isSafeInteger(count)) {
|
|
4917
|
+
throw new TypeError(`[Sixb] AI accounting ${field} exceeds the safe integer range.`);
|
|
4918
|
+
}
|
|
4919
|
+
return count;
|
|
4920
|
+
}
|
|
4921
|
+
function required(value) {
|
|
4922
|
+
if (value === null || value === undefined) {
|
|
4923
|
+
throw new Error("[SixbSqlite] AI valuation column is unexpectedly null.");
|
|
4924
|
+
}
|
|
4925
|
+
return value;
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4186
4928
|
// src/ai-usage-storage.ts
|
|
4187
4929
|
import {
|
|
4188
4930
|
AiUsageStorageError,
|
|
@@ -4308,7 +5050,7 @@ class SqliteAiUsageStorage {
|
|
|
4308
5050
|
...row.response_model_id === null ? {} : { responseModelId: row.response_model_id },
|
|
4309
5051
|
responseId: row.response_id,
|
|
4310
5052
|
usage: {
|
|
4311
|
-
...
|
|
5053
|
+
...usageFromRow2(row),
|
|
4312
5054
|
...row.total_tokens === null ? {} : { totalTokens: row.total_tokens },
|
|
4313
5055
|
reportingStatus: row.reporting_status
|
|
4314
5056
|
},
|
|
@@ -4342,7 +5084,7 @@ function aggregateExecutionRows(executionIds, rows) {
|
|
|
4342
5084
|
for (const executionId of executionIds)
|
|
4343
5085
|
usageByExecutionId.set(executionId, []);
|
|
4344
5086
|
for (const row of rows) {
|
|
4345
|
-
usageByExecutionId.get(row.execution_id)?.push(
|
|
5087
|
+
usageByExecutionId.get(row.execution_id)?.push(usageFromRow2(row));
|
|
4346
5088
|
}
|
|
4347
5089
|
return executionIds.map((executionId) => {
|
|
4348
5090
|
const usages = usageByExecutionId.get(executionId) ?? [];
|
|
@@ -4352,7 +5094,7 @@ function aggregateExecutionRows(executionIds, rows) {
|
|
|
4352
5094
|
};
|
|
4353
5095
|
});
|
|
4354
5096
|
}
|
|
4355
|
-
function
|
|
5097
|
+
function usageFromRow2(row) {
|
|
4356
5098
|
return {
|
|
4357
5099
|
...row.input_tokens === null ? {} : { inputTokens: row.input_tokens },
|
|
4358
5100
|
...row.output_tokens === null ? {} : { outputTokens: row.output_tokens },
|
|
@@ -6582,7 +7324,7 @@ function connectionRunFromRow(row) {
|
|
|
6582
7324
|
...base,
|
|
6583
7325
|
status: "waiting",
|
|
6584
7326
|
waitingFor: "provider_authorization",
|
|
6585
|
-
expiresAt: new Date(
|
|
7327
|
+
expiresAt: new Date(required2(row.expires_at, "run expiry"))
|
|
6586
7328
|
};
|
|
6587
7329
|
}
|
|
6588
7330
|
if (row.status === "waiting" && row.waiting_for === "account_selection") {
|
|
@@ -6590,28 +7332,28 @@ function connectionRunFromRow(row) {
|
|
|
6590
7332
|
...base,
|
|
6591
7333
|
status: "waiting",
|
|
6592
7334
|
waitingFor: "account_selection",
|
|
6593
|
-
authorizationId:
|
|
6594
|
-
expiresAt: new Date(
|
|
7335
|
+
authorizationId: required2(row.authorization_id, "authorization id"),
|
|
7336
|
+
expiresAt: new Date(required2(row.expires_at, "run expiry"))
|
|
6595
7337
|
};
|
|
6596
7338
|
}
|
|
6597
7339
|
if (row.status === "running") {
|
|
6598
7340
|
return {
|
|
6599
7341
|
...base,
|
|
6600
7342
|
status: "running",
|
|
6601
|
-
processingId:
|
|
6602
|
-
callbackStartedAt: new Date(
|
|
6603
|
-
expiresAt: new Date(
|
|
7343
|
+
processingId: required2(row.processing_id, "processing id"),
|
|
7344
|
+
callbackStartedAt: new Date(required2(row.callback_started_at, "callback start")),
|
|
7345
|
+
expiresAt: new Date(required2(row.expires_at, "run expiry")),
|
|
6604
7346
|
...row.authorization_id === null ? {} : { authorizationId: row.authorization_id }
|
|
6605
7347
|
};
|
|
6606
7348
|
}
|
|
6607
|
-
const finishedAt = new Date(
|
|
7349
|
+
const finishedAt = new Date(required2(row.finished_at, "run finish"));
|
|
6608
7350
|
if (row.status === "succeeded") {
|
|
6609
7351
|
return {
|
|
6610
7352
|
...base,
|
|
6611
7353
|
status: "succeeded",
|
|
6612
|
-
authorizationId:
|
|
7354
|
+
authorizationId: required2(row.authorization_id, "authorization id"),
|
|
6613
7355
|
...row.cleanup_authorization_id === null ? {} : { cleanupAuthorizationId: row.cleanup_authorization_id },
|
|
6614
|
-
connections: parseJson(
|
|
7356
|
+
connections: parseJson(required2(row.connections, "connections")).map(connectionFromSerialized),
|
|
6615
7357
|
finishedAt
|
|
6616
7358
|
};
|
|
6617
7359
|
}
|
|
@@ -6619,7 +7361,7 @@ function connectionRunFromRow(row) {
|
|
|
6619
7361
|
return {
|
|
6620
7362
|
...base,
|
|
6621
7363
|
status: "failed",
|
|
6622
|
-
error: parseJson(
|
|
7364
|
+
error: parseJson(required2(row.error, "run error")),
|
|
6623
7365
|
...row.authorization_id === null ? {} : { authorizationId: row.authorization_id },
|
|
6624
7366
|
finishedAt
|
|
6625
7367
|
};
|
|
@@ -6689,20 +7431,20 @@ function authorizationFromRow(row) {
|
|
|
6689
7431
|
function mutationFromRow(row) {
|
|
6690
7432
|
if (row.mutation_id === null)
|
|
6691
7433
|
return;
|
|
6692
|
-
const kind =
|
|
6693
|
-
const phase =
|
|
7434
|
+
const kind = required2(row.mutation_kind, "credential mutation kind");
|
|
7435
|
+
const phase = required2(row.mutation_phase, "credential mutation phase");
|
|
6694
7436
|
const stagedCredentials = row.staged_credentials === null ? undefined : {
|
|
6695
7437
|
credentials: parseJson(row.staged_credentials),
|
|
6696
7438
|
...row.staged_credential_expires_at === null ? {} : { credentialExpiresAt: new Date(row.staged_credential_expires_at) },
|
|
6697
|
-
scopes: parseJson(
|
|
7439
|
+
scopes: parseJson(required2(row.staged_scopes, "staged credential scopes"))
|
|
6698
7440
|
};
|
|
6699
7441
|
return {
|
|
6700
7442
|
id: row.mutation_id,
|
|
6701
7443
|
kind,
|
|
6702
7444
|
phase,
|
|
6703
|
-
holderId:
|
|
6704
|
-
expiresAt: new Date(
|
|
6705
|
-
deadlineAt: new Date(
|
|
7445
|
+
holderId: required2(row.mutation_holder_id, "credential mutation holder"),
|
|
7446
|
+
expiresAt: new Date(required2(row.mutation_expires_at, "credential mutation expiry")),
|
|
7447
|
+
deadlineAt: new Date(required2(row.mutation_deadline_at, "credential mutation deadline")),
|
|
6706
7448
|
...row.mutation_expected_connection_ids === null ? {} : {
|
|
6707
7449
|
expectedConnectionIds: parseJson(row.mutation_expected_connection_ids)
|
|
6708
7450
|
},
|
|
@@ -6745,7 +7487,7 @@ function iso(value) {
|
|
|
6745
7487
|
function optionalIso(value) {
|
|
6746
7488
|
return value?.toISOString() ?? null;
|
|
6747
7489
|
}
|
|
6748
|
-
function
|
|
7490
|
+
function required2(value, label) {
|
|
6749
7491
|
if (value !== null)
|
|
6750
7492
|
return value;
|
|
6751
7493
|
throw new Error(`[SixbSqlite] Stored ${label} is missing.`);
|
|
@@ -8561,13 +9303,13 @@ class SqliteMaterializationStateReader {
|
|
|
8561
9303
|
ORDER BY requested.scope_sort_key, ${linkSortExpression("links")}
|
|
8562
9304
|
`).iterate(requestedJson, this.projectId);
|
|
8563
9305
|
for (const row of rows) {
|
|
8564
|
-
const
|
|
8565
|
-
if (!
|
|
9306
|
+
const accumulator2 = accumulators.get(row.scope_sort_key);
|
|
9307
|
+
if (!accumulator2) {
|
|
8566
9308
|
throw new MaterializationConflictError2("effective-state", `Unexpected link scope '${row.scope_sort_key}' returned by storage.`);
|
|
8567
9309
|
}
|
|
8568
9310
|
const snapshot = effectiveLinkSnapshot(row);
|
|
8569
|
-
appendScopeSnapshot(
|
|
8570
|
-
effective.set(row.scope_sort_key,
|
|
9311
|
+
appendScopeSnapshot(accumulator2, snapshot);
|
|
9312
|
+
effective.set(row.scope_sort_key, accumulator2.effectiveCount === 1 ? snapshot : null);
|
|
8571
9313
|
}
|
|
8572
9314
|
return scopes.map(({ source, linkId }) => {
|
|
8573
9315
|
const key = linkScopeSortKey(source, linkId);
|
|
@@ -8927,6 +9669,7 @@ function storedObjectOverride(row) {
|
|
|
8927
9669
|
return {
|
|
8928
9670
|
ref,
|
|
8929
9671
|
value: parseJson2(row.value),
|
|
9672
|
+
editedAt: parseJson2(row.edited_at),
|
|
8930
9673
|
lastCommitId: row.last_commit_id,
|
|
8931
9674
|
updatedAt: row.updated_at
|
|
8932
9675
|
};
|
|
@@ -9416,8 +10159,9 @@ class SqliteMaterializationWriter {
|
|
|
9416
10159
|
if (item.expectedLastCommitId === null) {
|
|
9417
10160
|
try {
|
|
9418
10161
|
this.db.query(`INSERT INTO ontology_object_overrides (
|
|
9419
|
-
project_id, object_type_id, primary_id, value,
|
|
9420
|
-
|
|
10162
|
+
project_id, object_type_id, primary_id, value, edited_at,
|
|
10163
|
+
last_commit_id, updated_at
|
|
10164
|
+
) VALUES (?, ?, ?, json(?), json(?), ?, ?)`).run(projectId, item.ref.objectTypeId, item.ref.primaryId, canonicalJson(item.value), canonicalJson(item.editedAt), item.lastCommitId, item.updatedAt);
|
|
9421
10165
|
} catch (error) {
|
|
9422
10166
|
if (isSqliteConstraintError(error)) {
|
|
9423
10167
|
throw effectiveConflict("Expected object override changed.");
|
|
@@ -9427,9 +10171,9 @@ class SqliteMaterializationWriter {
|
|
|
9427
10171
|
continue;
|
|
9428
10172
|
}
|
|
9429
10173
|
requireChanges(this.db.query(`UPDATE ontology_object_overrides
|
|
9430
|
-
SET value = json(?), last_commit_id = ?, updated_at = ?
|
|
10174
|
+
SET value = json(?), edited_at = json(?), last_commit_id = ?, updated_at = ?
|
|
9431
10175
|
WHERE project_id = ? AND object_type_id = ? AND primary_id = ?
|
|
9432
|
-
AND last_commit_id = ?`).run(canonicalJson(item.value), item.lastCommitId, item.updatedAt, projectId, item.ref.objectTypeId, item.ref.primaryId, item.expectedLastCommitId).changes, "effective-state", "Expected object override changed.");
|
|
10176
|
+
AND last_commit_id = ?`).run(canonicalJson(item.value), canonicalJson(item.editedAt), item.lastCommitId, item.updatedAt, projectId, item.ref.objectTypeId, item.ref.primaryId, item.expectedLastCommitId).changes, "effective-state", "Expected object override changed.");
|
|
9433
10177
|
}
|
|
9434
10178
|
for (const item of chunk.overrides.objects.deletes) {
|
|
9435
10179
|
requireChanges(this.db.query(`DELETE FROM ontology_object_overrides
|
|
@@ -12984,6 +13728,7 @@ class SqliteStorage {
|
|
|
12984
13728
|
executions;
|
|
12985
13729
|
agents;
|
|
12986
13730
|
aiUsage;
|
|
13731
|
+
aiCosts;
|
|
12987
13732
|
actionRuns;
|
|
12988
13733
|
pipelineRuns;
|
|
12989
13734
|
syncRuns;
|
|
@@ -13030,6 +13775,7 @@ class SqliteStorage {
|
|
|
13030
13775
|
this.executions = createOperationScopedFacade(stores.executions, scope);
|
|
13031
13776
|
this.agents = createAgentOperationScope(stores.agents, scope);
|
|
13032
13777
|
this.aiUsage = createOperationScopedFacade(stores.aiUsage, scope);
|
|
13778
|
+
this.aiCosts = createOperationScopedFacade(stores.aiCosts, scope);
|
|
13033
13779
|
this.actionRuns = createOperationScopedFacade(stores.actionRuns, scope);
|
|
13034
13780
|
this.pipelineRuns = createOperationScopedFacade(stores.pipelineRuns, scope);
|
|
13035
13781
|
this.timeseries = createOperationScopedFacade(readTimeseries, readScope);
|
|
@@ -13149,6 +13895,7 @@ function createSqliteStores(connection, options) {
|
|
|
13149
13895
|
executions,
|
|
13150
13896
|
agents: new SqliteAgentStorage({ connection, executions }),
|
|
13151
13897
|
aiUsage: new SqliteAiUsageStorage({ connection }),
|
|
13898
|
+
aiCosts: new SqliteAiCostStorage(connection),
|
|
13152
13899
|
actionRuns: new SqliteActionRunStorage({ connection, executions }),
|
|
13153
13900
|
pipelineRuns: new SqlitePipelineRunStorage({ connection, executions }),
|
|
13154
13901
|
timeseries: new SqliteTimeseriesStorage({ connection }),
|
|
@@ -13166,4 +13913,4 @@ export {
|
|
|
13166
13913
|
SqliteStorage
|
|
13167
13914
|
};
|
|
13168
13915
|
|
|
13169
|
-
//# debugId=
|
|
13916
|
+
//# debugId=3913310C430C57B764756E2164756E21
|