@sixb/sqlite 0.1.6 → 0.1.7
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/rows.d.ts +7 -4
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +3 -2
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/agents/threads.d.ts.map +1 -1
- package/dist/ai-cost-groups.d.ts +5 -0
- package/dist/ai-cost-groups.d.ts.map +1 -0
- package/dist/ai-cost-storage.d.ts +2 -1
- package/dist/ai-cost-storage.d.ts.map +1 -1
- package/dist/ai-limit-storage.d.ts +35 -0
- package/dist/ai-limit-storage.d.ts.map +1 -0
- package/dist/ai-usage-storage.d.ts +1 -0
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/auth-storage/access-tokens.d.ts +1 -1
- package/dist/auth-storage/access-tokens.d.ts.map +1 -1
- package/dist/auth-storage/auth-storage.d.ts +4 -1
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/device-authorizations.d.ts +29 -0
- package/dist/auth-storage/device-authorizations.d.ts.map +1 -0
- package/dist/auth-storage/rows.d.ts +19 -1
- package/dist/auth-storage/rows.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +1 -1
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1898 -356
- package/dist/index.js.map +18 -15
- package/dist/migrations.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agents/rows.ts +36 -9
- package/src/agents/runs.ts +152 -13
- package/src/agents/threads.ts +2 -17
- package/src/ai-cost-groups.ts +100 -0
- package/src/ai-cost-storage.ts +67 -25
- package/src/ai-limit-storage.ts +981 -0
- package/src/ai-usage-storage.ts +24 -1
- package/src/auth-storage/access-tokens.ts +77 -68
- package/src/auth-storage/auth-storage.ts +80 -1
- package/src/auth-storage/device-authorizations.ts +153 -0
- package/src/auth-storage/rows.ts +41 -0
- package/src/auth-storage/shared.ts +1 -0
- package/src/index.ts +6 -0
- package/src/migrations/029-model-accounting.sql +79 -0
- package/src/migrations/030-ai-usage-limits.sql +100 -0
- package/src/migrations/031-subagent-runs.sql +231 -0
- package/src/migrations/032-conversation-run-spec.sql +78 -0
- package/src/migrations/033-retire-agent-definitions.sql +89 -0
- package/src/migrations/034-device-authorizations.sql +22 -0
- package/src/migrations.ts +18 -0
- package/src/workflow-run-storage.ts +8 -8
package/dist/index.js
CHANGED
|
@@ -3015,6 +3015,623 @@ ADD COLUMN edited_at TEXT NOT NULL DEFAULT '{}'
|
|
|
3015
3015
|
CHECK (json_valid(edited_at) AND json_type(edited_at) = 'object');
|
|
3016
3016
|
`;
|
|
3017
3017
|
|
|
3018
|
+
// src/migrations/029-model-accounting.sql
|
|
3019
|
+
var _029_model_accounting_default = `-- Call-time model configuration and provider identity. Historical identity remains unknown.
|
|
3020
|
+
ALTER TABLE ai_model_call_usage
|
|
3021
|
+
ADD COLUMN requested_reasoning TEXT
|
|
3022
|
+
CHECK (
|
|
3023
|
+
requested_reasoning IS NULL OR (
|
|
3024
|
+
json_valid(requested_reasoning) AND
|
|
3025
|
+
json_type(requested_reasoning) IN ('text', 'object')
|
|
3026
|
+
)
|
|
3027
|
+
);
|
|
3028
|
+
ALTER TABLE ai_model_call_usage ADD COLUMN provider_ids TEXT
|
|
3029
|
+
CHECK (provider_ids IS NULL OR json_valid(provider_ids));
|
|
3030
|
+
|
|
3031
|
+
-- Upgrade the shipped valuation reason while retaining the selected-cost projection.
|
|
3032
|
+
CREATE TABLE ai_model_call_valuations_next (
|
|
3033
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
3034
|
+
usage_record_id TEXT NOT NULL CHECK (length(trim(usage_record_id)) > 0),
|
|
3035
|
+
status TEXT NOT NULL CHECK (status IN ('rated', 'unpriceable')),
|
|
3036
|
+
provider_id TEXT CHECK (provider_id IS NULL OR length(trim(provider_id)) > 0),
|
|
3037
|
+
model_id TEXT CHECK (model_id IS NULL OR length(trim(model_id)) > 0),
|
|
3038
|
+
currency TEXT CHECK (
|
|
3039
|
+
currency IS NULL OR (length(currency) = 3 AND currency GLOB '[A-Z][A-Z][A-Z]')
|
|
3040
|
+
),
|
|
3041
|
+
amount_nanos INTEGER CHECK (
|
|
3042
|
+
amount_nanos IS NULL OR (typeof(amount_nanos) = 'integer' AND amount_nanos >= 0)
|
|
3043
|
+
),
|
|
3044
|
+
reason TEXT CHECK (
|
|
3045
|
+
reason IS NULL OR reason IN (
|
|
3046
|
+
'missingBillingIdentity',
|
|
3047
|
+
'missingRateCard',
|
|
3048
|
+
'missingUsageMeter',
|
|
3049
|
+
'unsupportedPricingDimension',
|
|
3050
|
+
'invalidUsageForFormula'
|
|
3051
|
+
)
|
|
3052
|
+
),
|
|
3053
|
+
details TEXT NOT NULL CHECK (json_valid(details) AND json_type(details) = 'object'),
|
|
3054
|
+
rated_at TEXT NOT NULL CHECK (length(trim(rated_at)) > 0),
|
|
3055
|
+
PRIMARY KEY (project_id, usage_record_id),
|
|
3056
|
+
FOREIGN KEY (project_id, usage_record_id)
|
|
3057
|
+
REFERENCES ai_model_call_usage(project_id, id)
|
|
3058
|
+
ON DELETE RESTRICT,
|
|
3059
|
+
CHECK (
|
|
3060
|
+
(
|
|
3061
|
+
status = 'rated'
|
|
3062
|
+
AND provider_id IS NOT NULL
|
|
3063
|
+
AND model_id IS NOT NULL
|
|
3064
|
+
AND currency IS NOT NULL
|
|
3065
|
+
AND amount_nanos IS NOT NULL
|
|
3066
|
+
AND reason IS NULL
|
|
3067
|
+
)
|
|
3068
|
+
OR
|
|
3069
|
+
(
|
|
3070
|
+
status = 'unpriceable'
|
|
3071
|
+
AND currency IS NULL
|
|
3072
|
+
AND amount_nanos IS NULL
|
|
3073
|
+
AND reason IS NOT NULL
|
|
3074
|
+
AND (
|
|
3075
|
+
(reason = 'missingBillingIdentity' AND provider_id IS NULL AND model_id IS NULL)
|
|
3076
|
+
OR
|
|
3077
|
+
(reason <> 'missingBillingIdentity' AND provider_id IS NOT NULL AND model_id IS NOT NULL)
|
|
3078
|
+
)
|
|
3079
|
+
)
|
|
3080
|
+
)
|
|
3081
|
+
);
|
|
3082
|
+
|
|
3083
|
+
INSERT INTO ai_model_call_valuations_next (
|
|
3084
|
+
project_id, usage_record_id, status, provider_id, model_id,
|
|
3085
|
+
currency, amount_nanos, reason, details, rated_at
|
|
3086
|
+
)
|
|
3087
|
+
SELECT project_id, usage_record_id, status, provider_id, model_id,
|
|
3088
|
+
currency, amount_nanos,
|
|
3089
|
+
CASE WHEN reason = 'missingCatalogEntry' THEN 'missingRateCard' ELSE reason END,
|
|
3090
|
+
details, rated_at
|
|
3091
|
+
FROM ai_model_call_valuations;
|
|
3092
|
+
|
|
3093
|
+
DROP TABLE ai_model_call_valuations;
|
|
3094
|
+
ALTER TABLE ai_model_call_valuations_next RENAME TO ai_model_call_valuations;
|
|
3095
|
+
|
|
3096
|
+
CREATE INDEX idx_ai_model_call_valuations_summary
|
|
3097
|
+
ON ai_model_call_valuations (project_id, status, currency, usage_record_id);
|
|
3098
|
+
`;
|
|
3099
|
+
|
|
3100
|
+
// src/migrations/030-ai-usage-limits.sql
|
|
3101
|
+
var _030_ai_usage_limits_default = `CREATE TABLE ai_usage_limits (
|
|
3102
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
3103
|
+
id TEXT NOT NULL CHECK (length(trim(id)) > 0),
|
|
3104
|
+
subject_type TEXT NOT NULL CHECK (
|
|
3105
|
+
subject_type IN ('project', 'group', 'user', 'serviceAccount')
|
|
3106
|
+
),
|
|
3107
|
+
subject_id TEXT NOT NULL CHECK (
|
|
3108
|
+
(subject_type = 'project' AND subject_id = '')
|
|
3109
|
+
OR (subject_type <> 'project' AND length(trim(subject_id)) > 0)
|
|
3110
|
+
),
|
|
3111
|
+
meter TEXT NOT NULL CHECK (meter IN ('tokens.total', 'cost.catalogEstimated')),
|
|
3112
|
+
currency TEXT NOT NULL CHECK (
|
|
3113
|
+
(meter = 'tokens.total' AND currency = '')
|
|
3114
|
+
OR (meter = 'cost.catalogEstimated' AND currency = 'USD')
|
|
3115
|
+
),
|
|
3116
|
+
limit_amount TEXT NOT NULL CHECK (
|
|
3117
|
+
limit_amount = '0'
|
|
3118
|
+
OR (
|
|
3119
|
+
limit_amount NOT GLOB '*[^0-9]*'
|
|
3120
|
+
AND substr(limit_amount, 1, 1) BETWEEN '1' AND '9'
|
|
3121
|
+
)
|
|
3122
|
+
),
|
|
3123
|
+
period_kind TEXT NOT NULL CHECK (period_kind = 'calendarMonth'),
|
|
3124
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
|
3125
|
+
created_at TEXT NOT NULL,
|
|
3126
|
+
updated_at TEXT NOT NULL,
|
|
3127
|
+
PRIMARY KEY (project_id, id),
|
|
3128
|
+
UNIQUE (project_id, subject_type, subject_id, meter, currency)
|
|
3129
|
+
);
|
|
3130
|
+
|
|
3131
|
+
CREATE INDEX idx_ai_usage_limits_project_enabled
|
|
3132
|
+
ON ai_usage_limits (project_id, enabled, subject_type, subject_id, meter, currency);
|
|
3133
|
+
|
|
3134
|
+
CREATE TABLE ai_usage_limit_periods (
|
|
3135
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
3136
|
+
subject_type TEXT NOT NULL CHECK (
|
|
3137
|
+
subject_type IN ('project', 'group', 'user', 'serviceAccount')
|
|
3138
|
+
),
|
|
3139
|
+
subject_id TEXT NOT NULL CHECK (
|
|
3140
|
+
(subject_type = 'project' AND subject_id = '')
|
|
3141
|
+
OR (subject_type <> 'project' AND length(trim(subject_id)) > 0)
|
|
3142
|
+
),
|
|
3143
|
+
meter TEXT NOT NULL CHECK (meter IN ('tokens.total', 'cost.catalogEstimated')),
|
|
3144
|
+
currency TEXT NOT NULL CHECK (
|
|
3145
|
+
(meter = 'tokens.total' AND currency = '')
|
|
3146
|
+
OR (meter = 'cost.catalogEstimated' AND currency = 'USD')
|
|
3147
|
+
),
|
|
3148
|
+
period_start TEXT NOT NULL,
|
|
3149
|
+
period_end TEXT NOT NULL,
|
|
3150
|
+
actual_amount TEXT NOT NULL DEFAULT '0',
|
|
3151
|
+
reserved_amount TEXT NOT NULL DEFAULT '0',
|
|
3152
|
+
unknown_amount TEXT NOT NULL DEFAULT '0',
|
|
3153
|
+
accounting_status TEXT NOT NULL DEFAULT 'complete' CHECK (
|
|
3154
|
+
accounting_status IN ('complete', 'unavailable')
|
|
3155
|
+
),
|
|
3156
|
+
updated_at TEXT NOT NULL,
|
|
3157
|
+
PRIMARY KEY (
|
|
3158
|
+
project_id, subject_type, subject_id, meter, currency, period_start
|
|
3159
|
+
)
|
|
3160
|
+
);
|
|
3161
|
+
|
|
3162
|
+
CREATE INDEX idx_ai_usage_limit_periods_subject_period
|
|
3163
|
+
ON ai_usage_limit_periods (
|
|
3164
|
+
project_id, subject_type, subject_id, period_start, period_end, meter, currency
|
|
3165
|
+
);
|
|
3166
|
+
|
|
3167
|
+
CREATE TABLE ai_model_call_reservations (
|
|
3168
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
3169
|
+
execution_id TEXT NOT NULL CHECK (length(trim(execution_id)) > 0),
|
|
3170
|
+
attempt INTEGER NOT NULL CHECK (attempt >= 1),
|
|
3171
|
+
call_id TEXT NOT NULL CHECK (length(trim(call_id)) > 0),
|
|
3172
|
+
buckets TEXT NOT NULL CHECK (json_valid(buckets) AND json_type(buckets) = 'array'),
|
|
3173
|
+
request_key TEXT NOT NULL,
|
|
3174
|
+
period_start TEXT NOT NULL,
|
|
3175
|
+
period_end TEXT NOT NULL,
|
|
3176
|
+
state TEXT NOT NULL CHECK (state IN ('active', 'reconciled', 'unknown')),
|
|
3177
|
+
usage_record_id TEXT,
|
|
3178
|
+
reserved_at TEXT NOT NULL,
|
|
3179
|
+
updated_at TEXT NOT NULL,
|
|
3180
|
+
PRIMARY KEY (project_id, execution_id, attempt, call_id),
|
|
3181
|
+
FOREIGN KEY (project_id, execution_id)
|
|
3182
|
+
REFERENCES executions(project_id, id)
|
|
3183
|
+
ON DELETE RESTRICT,
|
|
3184
|
+
FOREIGN KEY (project_id, usage_record_id)
|
|
3185
|
+
REFERENCES ai_model_call_usage(project_id, id)
|
|
3186
|
+
ON DELETE RESTRICT,
|
|
3187
|
+
CHECK (
|
|
3188
|
+
(
|
|
3189
|
+
state = 'reconciled'
|
|
3190
|
+
AND usage_record_id IS NOT NULL
|
|
3191
|
+
)
|
|
3192
|
+
OR (
|
|
3193
|
+
state <> 'reconciled'
|
|
3194
|
+
AND usage_record_id IS NULL
|
|
3195
|
+
)
|
|
3196
|
+
)
|
|
3197
|
+
);
|
|
3198
|
+
|
|
3199
|
+
CREATE INDEX idx_ai_model_call_reservations_active
|
|
3200
|
+
ON ai_model_call_reservations (project_id, state, period_end, execution_id, attempt, call_id);
|
|
3201
|
+
`;
|
|
3202
|
+
|
|
3203
|
+
// src/migrations/031-subagent-runs.sql
|
|
3204
|
+
var _031_subagent_runs_default = `-- SQLite cannot alter a CHECK constraint in place. Rebuild the execution table to align SQL with
|
|
3205
|
+
-- the durable Agent authority model: managed service account or exact inherited user/disabled.
|
|
3206
|
+
CREATE TABLE executions_v3 (
|
|
3207
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
3208
|
+
id TEXT NOT NULL CHECK (length(trim(id)) > 0),
|
|
3209
|
+
executor_kind TEXT NOT NULL CHECK (
|
|
3210
|
+
executor_kind IN (
|
|
3211
|
+
'request', 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow',
|
|
3212
|
+
'agent', 'kernel'
|
|
3213
|
+
)
|
|
3214
|
+
),
|
|
3215
|
+
executor_id TEXT NOT NULL CHECK (length(trim(executor_id)) > 0),
|
|
3216
|
+
source_kind TEXT NOT NULL CHECK (
|
|
3217
|
+
source_kind IN ('http', 'webhook', 'schedule', 'event', 'datasetVersion', 'execution')
|
|
3218
|
+
),
|
|
3219
|
+
source_id TEXT NOT NULL CHECK (length(trim(source_id)) > 0),
|
|
3220
|
+
requested_by_user_id TEXT,
|
|
3221
|
+
requested_by_service_account_id TEXT,
|
|
3222
|
+
correlation_id TEXT NOT NULL CHECK (length(trim(correlation_id)) > 0),
|
|
3223
|
+
parent_execution_id TEXT,
|
|
3224
|
+
authority_kind TEXT NOT NULL CHECK (
|
|
3225
|
+
authority_kind IN ('principal', 'trustedPrimitive', 'kernel', 'disabled')
|
|
3226
|
+
),
|
|
3227
|
+
authority_user_id TEXT,
|
|
3228
|
+
authority_service_account_id TEXT,
|
|
3229
|
+
authority_session_id TEXT,
|
|
3230
|
+
authority_access_token_id TEXT,
|
|
3231
|
+
authority_primitive_kind TEXT CHECK (
|
|
3232
|
+
authority_primitive_kind IS NULL
|
|
3233
|
+
OR authority_primitive_kind IN (
|
|
3234
|
+
'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow'
|
|
3235
|
+
)
|
|
3236
|
+
),
|
|
3237
|
+
authority_primitive_id TEXT,
|
|
3238
|
+
authority_kernel_operation TEXT CHECK (
|
|
3239
|
+
authority_kernel_operation IS NULL OR authority_kernel_operation = 'ontology.recover'
|
|
3240
|
+
),
|
|
3241
|
+
created_at TEXT NOT NULL,
|
|
3242
|
+
PRIMARY KEY (project_id, id),
|
|
3243
|
+
FOREIGN KEY (project_id, parent_execution_id) REFERENCES executions (project_id, id),
|
|
3244
|
+
FOREIGN KEY (project_id, requested_by_user_id) REFERENCES auth_users (project_id, id),
|
|
3245
|
+
FOREIGN KEY (project_id, requested_by_service_account_id)
|
|
3246
|
+
REFERENCES auth_service_accounts (project_id, id),
|
|
3247
|
+
FOREIGN KEY (project_id, authority_user_id) REFERENCES auth_users (project_id, id),
|
|
3248
|
+
FOREIGN KEY (project_id, authority_service_account_id)
|
|
3249
|
+
REFERENCES auth_service_accounts (project_id, id),
|
|
3250
|
+
FOREIGN KEY (project_id, authority_session_id) REFERENCES auth_sessions (project_id, id),
|
|
3251
|
+
FOREIGN KEY (project_id, authority_access_token_id)
|
|
3252
|
+
REFERENCES auth_access_tokens (project_id, id),
|
|
3253
|
+
CHECK ((requested_by_user_id IS NOT NULL) + (requested_by_service_account_id IS NOT NULL) <= 1),
|
|
3254
|
+
CHECK (
|
|
3255
|
+
(source_kind = 'execution' AND parent_execution_id = source_id)
|
|
3256
|
+
OR (source_kind <> 'execution' AND parent_execution_id IS NULL)
|
|
3257
|
+
),
|
|
3258
|
+
CHECK (
|
|
3259
|
+
(
|
|
3260
|
+
authority_kind = 'principal'
|
|
3261
|
+
AND (authority_user_id IS NOT NULL) + (authority_service_account_id IS NOT NULL) = 1
|
|
3262
|
+
AND (authority_session_id IS NOT NULL) + (authority_access_token_id IS NOT NULL) <= 1
|
|
3263
|
+
AND (authority_session_id IS NULL OR authority_user_id IS NOT NULL)
|
|
3264
|
+
AND authority_primitive_kind IS NULL
|
|
3265
|
+
AND authority_primitive_id IS NULL
|
|
3266
|
+
AND authority_kernel_operation IS NULL
|
|
3267
|
+
)
|
|
3268
|
+
OR (
|
|
3269
|
+
authority_kind = 'trustedPrimitive'
|
|
3270
|
+
AND authority_user_id IS NULL
|
|
3271
|
+
AND authority_service_account_id IS NULL
|
|
3272
|
+
AND authority_session_id IS NULL
|
|
3273
|
+
AND authority_access_token_id IS NULL
|
|
3274
|
+
AND authority_primitive_kind IS NOT NULL
|
|
3275
|
+
AND authority_primitive_id IS NOT NULL
|
|
3276
|
+
AND length(trim(authority_primitive_id)) > 0
|
|
3277
|
+
AND authority_kernel_operation IS NULL
|
|
3278
|
+
)
|
|
3279
|
+
OR (
|
|
3280
|
+
authority_kind = 'kernel'
|
|
3281
|
+
AND authority_user_id IS NULL
|
|
3282
|
+
AND authority_service_account_id IS NULL
|
|
3283
|
+
AND authority_session_id IS NULL
|
|
3284
|
+
AND authority_access_token_id IS NULL
|
|
3285
|
+
AND authority_primitive_kind IS NULL
|
|
3286
|
+
AND authority_primitive_id IS NULL
|
|
3287
|
+
AND authority_kernel_operation IS NOT NULL
|
|
3288
|
+
)
|
|
3289
|
+
OR (
|
|
3290
|
+
authority_kind = 'disabled'
|
|
3291
|
+
AND authority_user_id IS NULL
|
|
3292
|
+
AND authority_service_account_id IS NULL
|
|
3293
|
+
AND authority_session_id IS NULL
|
|
3294
|
+
AND authority_access_token_id IS NULL
|
|
3295
|
+
AND authority_primitive_kind IS NULL
|
|
3296
|
+
AND authority_primitive_id IS NULL
|
|
3297
|
+
AND authority_kernel_operation IS NULL
|
|
3298
|
+
)
|
|
3299
|
+
),
|
|
3300
|
+
CHECK (
|
|
3301
|
+
(
|
|
3302
|
+
executor_kind = 'request'
|
|
3303
|
+
AND source_kind = 'http'
|
|
3304
|
+
AND executor_id = source_id
|
|
3305
|
+
AND (
|
|
3306
|
+
(
|
|
3307
|
+
authority_kind = 'principal'
|
|
3308
|
+
AND requested_by_user_id IS authority_user_id
|
|
3309
|
+
AND requested_by_service_account_id IS authority_service_account_id
|
|
3310
|
+
)
|
|
3311
|
+
OR (
|
|
3312
|
+
authority_kind = 'disabled'
|
|
3313
|
+
AND requested_by_user_id IS NULL
|
|
3314
|
+
AND requested_by_service_account_id IS NULL
|
|
3315
|
+
)
|
|
3316
|
+
)
|
|
3317
|
+
)
|
|
3318
|
+
OR (
|
|
3319
|
+
executor_kind IN ('action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow')
|
|
3320
|
+
AND authority_kind = 'trustedPrimitive'
|
|
3321
|
+
AND executor_kind = authority_primitive_kind
|
|
3322
|
+
)
|
|
3323
|
+
OR (
|
|
3324
|
+
executor_kind = 'agent'
|
|
3325
|
+
AND source_kind = 'execution'
|
|
3326
|
+
AND (
|
|
3327
|
+
(
|
|
3328
|
+
authority_kind = 'principal'
|
|
3329
|
+
AND authority_service_account_id IS NOT NULL
|
|
3330
|
+
AND authority_session_id IS NULL
|
|
3331
|
+
AND authority_access_token_id IS NULL
|
|
3332
|
+
)
|
|
3333
|
+
OR (
|
|
3334
|
+
authority_kind = 'principal'
|
|
3335
|
+
AND authority_user_id IS NOT NULL
|
|
3336
|
+
AND (authority_session_id IS NOT NULL) + (authority_access_token_id IS NOT NULL) = 1
|
|
3337
|
+
)
|
|
3338
|
+
OR authority_kind = 'disabled'
|
|
3339
|
+
)
|
|
3340
|
+
)
|
|
3341
|
+
OR (executor_kind = 'kernel' AND authority_kind = 'kernel')
|
|
3342
|
+
)
|
|
3343
|
+
);
|
|
3344
|
+
|
|
3345
|
+
INSERT INTO executions_v3 SELECT * FROM executions;
|
|
3346
|
+
DROP TABLE executions;
|
|
3347
|
+
ALTER TABLE executions_v3 RENAME TO executions;
|
|
3348
|
+
|
|
3349
|
+
CREATE TABLE agent_runs_v3 (
|
|
3350
|
+
project_id TEXT NOT NULL,
|
|
3351
|
+
id TEXT NOT NULL,
|
|
3352
|
+
execution_id TEXT NOT NULL,
|
|
3353
|
+
kind TEXT NOT NULL DEFAULT 'conversation' CHECK (kind IN ('conversation', 'subagent')),
|
|
3354
|
+
thread_id TEXT,
|
|
3355
|
+
agent_id TEXT,
|
|
3356
|
+
trigger_message_id TEXT,
|
|
3357
|
+
parent_run_id TEXT,
|
|
3358
|
+
spawn_key TEXT,
|
|
3359
|
+
spec TEXT CHECK (spec IS NULL OR json_valid(spec)),
|
|
3360
|
+
result TEXT CHECK (result IS NULL OR json_valid(result)),
|
|
3361
|
+
requester_group_ids TEXT NOT NULL DEFAULT '[]'
|
|
3362
|
+
CHECK (json_valid(requester_group_ids) AND json_type(requester_group_ids) = 'array'),
|
|
3363
|
+
status TEXT NOT NULL CHECK (
|
|
3364
|
+
status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled')
|
|
3365
|
+
),
|
|
3366
|
+
model_id TEXT,
|
|
3367
|
+
finish_reason TEXT,
|
|
3368
|
+
error TEXT CHECK (error IS NULL OR json_valid(error)),
|
|
3369
|
+
diagnostics TEXT,
|
|
3370
|
+
attempt INTEGER NOT NULL DEFAULT 0 CHECK (attempt >= 0),
|
|
3371
|
+
execution_token TEXT,
|
|
3372
|
+
execution_queue_lease_expires_at TEXT,
|
|
3373
|
+
created_at TEXT NOT NULL,
|
|
3374
|
+
started_at TEXT,
|
|
3375
|
+
completed_at TEXT,
|
|
3376
|
+
PRIMARY KEY (project_id, id),
|
|
3377
|
+
UNIQUE (project_id, execution_id),
|
|
3378
|
+
UNIQUE (project_id, parent_run_id, spawn_key),
|
|
3379
|
+
FOREIGN KEY (project_id, execution_id)
|
|
3380
|
+
REFERENCES executions (project_id, id)
|
|
3381
|
+
ON DELETE RESTRICT,
|
|
3382
|
+
FOREIGN KEY (project_id, parent_run_id)
|
|
3383
|
+
REFERENCES agent_runs_v3 (project_id, id)
|
|
3384
|
+
ON DELETE RESTRICT,
|
|
3385
|
+
CHECK ((execution_token IS NULL) = (execution_queue_lease_expires_at IS NULL)),
|
|
3386
|
+
CHECK (
|
|
3387
|
+
(
|
|
3388
|
+
kind = 'conversation'
|
|
3389
|
+
AND thread_id IS NOT NULL
|
|
3390
|
+
AND agent_id IS NOT NULL
|
|
3391
|
+
AND trigger_message_id IS NOT NULL
|
|
3392
|
+
AND parent_run_id IS NULL
|
|
3393
|
+
AND spawn_key IS NULL
|
|
3394
|
+
AND spec IS NULL
|
|
3395
|
+
AND result IS NULL
|
|
3396
|
+
)
|
|
3397
|
+
OR
|
|
3398
|
+
(
|
|
3399
|
+
kind = 'subagent'
|
|
3400
|
+
AND thread_id IS NULL
|
|
3401
|
+
AND agent_id IS NULL
|
|
3402
|
+
AND trigger_message_id IS NULL
|
|
3403
|
+
AND parent_run_id IS NOT NULL
|
|
3404
|
+
AND spawn_key IS NOT NULL
|
|
3405
|
+
AND spec IS NOT NULL
|
|
3406
|
+
)
|
|
3407
|
+
),
|
|
3408
|
+
CHECK (kind <> 'subagent' OR status <> 'succeeded' OR result IS NOT NULL)
|
|
3409
|
+
);
|
|
3410
|
+
|
|
3411
|
+
INSERT INTO agent_runs_v3 (
|
|
3412
|
+
project_id, id, execution_id, kind, thread_id, agent_id, trigger_message_id,
|
|
3413
|
+
requester_group_ids, status, model_id, finish_reason, error, diagnostics, attempt,
|
|
3414
|
+
execution_token, execution_queue_lease_expires_at, created_at, started_at, completed_at
|
|
3415
|
+
)
|
|
3416
|
+
SELECT
|
|
3417
|
+
project_id, id, execution_id, 'conversation', thread_id, agent_id, trigger_message_id,
|
|
3418
|
+
requester_group_ids, status, model_id, finish_reason, error, diagnostics, attempt,
|
|
3419
|
+
execution_token, execution_queue_lease_expires_at, created_at, started_at, completed_at
|
|
3420
|
+
FROM agent_runs;
|
|
3421
|
+
|
|
3422
|
+
DROP TABLE agent_runs;
|
|
3423
|
+
ALTER TABLE agent_runs_v3 RENAME TO agent_runs;
|
|
3424
|
+
|
|
3425
|
+
CREATE INDEX idx_agent_runs_project_started
|
|
3426
|
+
ON agent_runs(project_id, COALESCE(started_at, created_at) DESC);
|
|
3427
|
+
CREATE INDEX idx_agent_runs_project_thread_started
|
|
3428
|
+
ON agent_runs(project_id, thread_id, COALESCE(started_at, created_at) DESC);
|
|
3429
|
+
CREATE INDEX idx_agent_runs_project_agent_started
|
|
3430
|
+
ON agent_runs(project_id, agent_id, COALESCE(started_at, created_at) DESC);
|
|
3431
|
+
CREATE INDEX idx_agent_runs_project_status_started
|
|
3432
|
+
ON agent_runs(project_id, status, COALESCE(started_at, created_at) DESC);
|
|
3433
|
+
CREATE INDEX idx_agent_runs_project_parent_started
|
|
3434
|
+
ON agent_runs(project_id, parent_run_id, status, COALESCE(started_at, created_at) DESC);
|
|
3435
|
+
`;
|
|
3436
|
+
|
|
3437
|
+
// src/migrations/032-conversation-run-spec.sql
|
|
3438
|
+
var _032_conversation_run_spec_default = `CREATE TABLE agent_runs_v4 (
|
|
3439
|
+
project_id TEXT NOT NULL,
|
|
3440
|
+
id TEXT NOT NULL,
|
|
3441
|
+
execution_id TEXT NOT NULL,
|
|
3442
|
+
kind TEXT NOT NULL DEFAULT 'conversation' CHECK (kind IN ('conversation', 'subagent')),
|
|
3443
|
+
thread_id TEXT,
|
|
3444
|
+
agent_id TEXT,
|
|
3445
|
+
trigger_message_id TEXT,
|
|
3446
|
+
parent_run_id TEXT,
|
|
3447
|
+
spawn_key TEXT,
|
|
3448
|
+
spec TEXT CHECK (spec IS NULL OR json_valid(spec)),
|
|
3449
|
+
result TEXT CHECK (result IS NULL OR json_valid(result)),
|
|
3450
|
+
requester_group_ids TEXT NOT NULL DEFAULT '[]'
|
|
3451
|
+
CHECK (json_valid(requester_group_ids) AND json_type(requester_group_ids) = 'array'),
|
|
3452
|
+
status TEXT NOT NULL CHECK (
|
|
3453
|
+
status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled')
|
|
3454
|
+
),
|
|
3455
|
+
model_id TEXT,
|
|
3456
|
+
finish_reason TEXT,
|
|
3457
|
+
error TEXT CHECK (error IS NULL OR json_valid(error)),
|
|
3458
|
+
diagnostics TEXT,
|
|
3459
|
+
attempt INTEGER NOT NULL DEFAULT 0 CHECK (attempt >= 0),
|
|
3460
|
+
execution_token TEXT,
|
|
3461
|
+
execution_queue_lease_expires_at TEXT,
|
|
3462
|
+
created_at TEXT NOT NULL,
|
|
3463
|
+
started_at TEXT,
|
|
3464
|
+
completed_at TEXT,
|
|
3465
|
+
PRIMARY KEY (project_id, id),
|
|
3466
|
+
UNIQUE (project_id, execution_id),
|
|
3467
|
+
UNIQUE (project_id, parent_run_id, spawn_key),
|
|
3468
|
+
FOREIGN KEY (project_id, execution_id)
|
|
3469
|
+
REFERENCES executions (project_id, id)
|
|
3470
|
+
ON DELETE RESTRICT,
|
|
3471
|
+
FOREIGN KEY (project_id, parent_run_id)
|
|
3472
|
+
-- Use the final name: Bun's legacy ALTER mode preserves references when foreign keys are off.
|
|
3473
|
+
REFERENCES agent_runs (project_id, id)
|
|
3474
|
+
ON DELETE RESTRICT,
|
|
3475
|
+
CHECK ((execution_token IS NULL) = (execution_queue_lease_expires_at IS NULL)),
|
|
3476
|
+
CHECK (
|
|
3477
|
+
(
|
|
3478
|
+
kind = 'conversation'
|
|
3479
|
+
AND thread_id IS NOT NULL
|
|
3480
|
+
AND agent_id IS NOT NULL
|
|
3481
|
+
AND trigger_message_id IS NOT NULL
|
|
3482
|
+
AND parent_run_id IS NULL
|
|
3483
|
+
AND spawn_key IS NULL
|
|
3484
|
+
AND result IS NULL
|
|
3485
|
+
)
|
|
3486
|
+
OR
|
|
3487
|
+
(
|
|
3488
|
+
kind = 'subagent'
|
|
3489
|
+
AND thread_id IS NULL
|
|
3490
|
+
AND agent_id IS NULL
|
|
3491
|
+
AND trigger_message_id IS NULL
|
|
3492
|
+
AND parent_run_id IS NOT NULL
|
|
3493
|
+
AND spawn_key IS NOT NULL
|
|
3494
|
+
AND spec IS NOT NULL
|
|
3495
|
+
)
|
|
3496
|
+
),
|
|
3497
|
+
CHECK (kind <> 'subagent' OR status <> 'succeeded' OR result IS NOT NULL)
|
|
3498
|
+
);
|
|
3499
|
+
|
|
3500
|
+
INSERT INTO agent_runs_v4 SELECT * FROM agent_runs;
|
|
3501
|
+
DROP TABLE agent_runs;
|
|
3502
|
+
ALTER TABLE agent_runs_v4 RENAME TO agent_runs;
|
|
3503
|
+
|
|
3504
|
+
CREATE INDEX idx_agent_runs_project_thread_started
|
|
3505
|
+
ON agent_runs (project_id, thread_id, COALESCE(started_at, created_at) DESC, id DESC);
|
|
3506
|
+
CREATE INDEX idx_agent_runs_project_status_created
|
|
3507
|
+
ON agent_runs (project_id, status, created_at, id);
|
|
3508
|
+
CREATE INDEX idx_agent_runs_project_parent_started
|
|
3509
|
+
ON agent_runs (
|
|
3510
|
+
project_id,
|
|
3511
|
+
parent_run_id,
|
|
3512
|
+
status,
|
|
3513
|
+
COALESCE(started_at, created_at) DESC,
|
|
3514
|
+
id DESC
|
|
3515
|
+
);
|
|
3516
|
+
`;
|
|
3517
|
+
|
|
3518
|
+
// src/migrations/033-retire-agent-definitions.sql
|
|
3519
|
+
var _033_retire_agent_definitions_default = `DROP INDEX idx_agent_threads_project_agent_activity;
|
|
3520
|
+
ALTER TABLE workflow_agent_node_runs RENAME COLUMN agent_id TO actor_id;
|
|
3521
|
+
ALTER TABLE agent_threads DROP COLUMN agent_id;
|
|
3522
|
+
|
|
3523
|
+
CREATE TABLE agent_runs_v5 (
|
|
3524
|
+
project_id TEXT NOT NULL,
|
|
3525
|
+
id TEXT NOT NULL,
|
|
3526
|
+
execution_id TEXT NOT NULL,
|
|
3527
|
+
kind TEXT NOT NULL DEFAULT 'conversation' CHECK (kind IN ('conversation', 'subagent')),
|
|
3528
|
+
thread_id TEXT,
|
|
3529
|
+
trigger_message_id TEXT,
|
|
3530
|
+
parent_run_id TEXT,
|
|
3531
|
+
spawn_key TEXT,
|
|
3532
|
+
spec TEXT CHECK (spec IS NULL OR json_valid(spec)),
|
|
3533
|
+
result TEXT CHECK (result IS NULL OR json_valid(result)),
|
|
3534
|
+
requester_group_ids TEXT NOT NULL DEFAULT '[]'
|
|
3535
|
+
CHECK (json_valid(requester_group_ids) AND json_type(requester_group_ids) = 'array'),
|
|
3536
|
+
status TEXT NOT NULL CHECK (
|
|
3537
|
+
status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled')
|
|
3538
|
+
),
|
|
3539
|
+
model_id TEXT,
|
|
3540
|
+
finish_reason TEXT,
|
|
3541
|
+
error TEXT CHECK (error IS NULL OR json_valid(error)),
|
|
3542
|
+
diagnostics TEXT,
|
|
3543
|
+
attempt INTEGER NOT NULL DEFAULT 0 CHECK (attempt >= 0),
|
|
3544
|
+
execution_token TEXT,
|
|
3545
|
+
execution_queue_lease_expires_at TEXT,
|
|
3546
|
+
created_at TEXT NOT NULL,
|
|
3547
|
+
started_at TEXT,
|
|
3548
|
+
completed_at TEXT,
|
|
3549
|
+
PRIMARY KEY (project_id, id),
|
|
3550
|
+
UNIQUE (project_id, execution_id),
|
|
3551
|
+
UNIQUE (project_id, parent_run_id, spawn_key),
|
|
3552
|
+
FOREIGN KEY (project_id, execution_id)
|
|
3553
|
+
REFERENCES executions (project_id, id)
|
|
3554
|
+
ON DELETE RESTRICT,
|
|
3555
|
+
FOREIGN KEY (project_id, parent_run_id)
|
|
3556
|
+
-- Use the final name: Bun's legacy ALTER mode preserves references when foreign keys are off.
|
|
3557
|
+
REFERENCES agent_runs (project_id, id)
|
|
3558
|
+
ON DELETE RESTRICT,
|
|
3559
|
+
CHECK ((execution_token IS NULL) = (execution_queue_lease_expires_at IS NULL)),
|
|
3560
|
+
CHECK (
|
|
3561
|
+
(
|
|
3562
|
+
kind = 'conversation'
|
|
3563
|
+
AND thread_id IS NOT NULL
|
|
3564
|
+
AND trigger_message_id IS NOT NULL
|
|
3565
|
+
AND parent_run_id IS NULL
|
|
3566
|
+
AND spawn_key IS NULL
|
|
3567
|
+
AND result IS NULL
|
|
3568
|
+
)
|
|
3569
|
+
OR
|
|
3570
|
+
(
|
|
3571
|
+
kind = 'subagent'
|
|
3572
|
+
AND thread_id IS NULL
|
|
3573
|
+
AND trigger_message_id IS NULL
|
|
3574
|
+
AND parent_run_id IS NOT NULL
|
|
3575
|
+
AND spawn_key IS NOT NULL
|
|
3576
|
+
AND spec IS NOT NULL
|
|
3577
|
+
)
|
|
3578
|
+
),
|
|
3579
|
+
CHECK (kind <> 'subagent' OR status <> 'succeeded' OR result IS NOT NULL)
|
|
3580
|
+
);
|
|
3581
|
+
|
|
3582
|
+
INSERT INTO agent_runs_v5 (
|
|
3583
|
+
project_id, id, execution_id, kind, thread_id, trigger_message_id, parent_run_id, spawn_key,
|
|
3584
|
+
spec, result, requester_group_ids, status, model_id, finish_reason, error, diagnostics, attempt,
|
|
3585
|
+
execution_token, execution_queue_lease_expires_at, created_at, started_at, completed_at
|
|
3586
|
+
)
|
|
3587
|
+
SELECT
|
|
3588
|
+
project_id, id, execution_id, kind, thread_id, trigger_message_id, parent_run_id, spawn_key,
|
|
3589
|
+
spec, result, requester_group_ids, status, model_id, finish_reason, error, diagnostics, attempt,
|
|
3590
|
+
execution_token, execution_queue_lease_expires_at, created_at, started_at, completed_at
|
|
3591
|
+
FROM agent_runs;
|
|
3592
|
+
|
|
3593
|
+
DROP TABLE agent_runs;
|
|
3594
|
+
ALTER TABLE agent_runs_v5 RENAME TO agent_runs;
|
|
3595
|
+
|
|
3596
|
+
CREATE INDEX idx_agent_runs_project_thread_started
|
|
3597
|
+
ON agent_runs (project_id, thread_id, COALESCE(started_at, created_at) DESC, id DESC);
|
|
3598
|
+
CREATE INDEX idx_agent_runs_project_status_created
|
|
3599
|
+
ON agent_runs (project_id, status, created_at, id);
|
|
3600
|
+
CREATE INDEX idx_agent_runs_project_parent_started
|
|
3601
|
+
ON agent_runs (
|
|
3602
|
+
project_id,
|
|
3603
|
+
parent_run_id,
|
|
3604
|
+
status,
|
|
3605
|
+
COALESCE(started_at, created_at) DESC,
|
|
3606
|
+
id DESC
|
|
3607
|
+
);
|
|
3608
|
+
`;
|
|
3609
|
+
|
|
3610
|
+
// src/migrations/034-device-authorizations.sql
|
|
3611
|
+
var _034_device_authorizations_default = `CREATE TABLE auth_device_authorizations (
|
|
3612
|
+
project_id TEXT NOT NULL,
|
|
3613
|
+
id TEXT NOT NULL,
|
|
3614
|
+
device_code_hash TEXT NOT NULL,
|
|
3615
|
+
user_code TEXT NOT NULL,
|
|
3616
|
+
client_name TEXT NOT NULL,
|
|
3617
|
+
token_name TEXT NOT NULL,
|
|
3618
|
+
token_expires_at TEXT NOT NULL,
|
|
3619
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'denied', 'consumed')),
|
|
3620
|
+
approved_user_id TEXT,
|
|
3621
|
+
approved_session_id TEXT,
|
|
3622
|
+
created_at TEXT NOT NULL,
|
|
3623
|
+
expires_at TEXT NOT NULL,
|
|
3624
|
+
approved_at TEXT,
|
|
3625
|
+
denied_at TEXT,
|
|
3626
|
+
consumed_at TEXT,
|
|
3627
|
+
PRIMARY KEY (project_id, id),
|
|
3628
|
+
UNIQUE (project_id, user_code)
|
|
3629
|
+
);
|
|
3630
|
+
|
|
3631
|
+
CREATE INDEX idx_auth_device_authorizations_pending
|
|
3632
|
+
ON auth_device_authorizations(project_id, status, expires_at);
|
|
3633
|
+
`;
|
|
3634
|
+
|
|
3018
3635
|
// src/migrations.ts
|
|
3019
3636
|
var MIGRATIONS_TABLE_SQL = `
|
|
3020
3637
|
CREATE TABLE IF NOT EXISTS sixb_migrations (
|
|
@@ -3077,7 +3694,13 @@ var sqliteStorageMigrations = defineMigrations({
|
|
|
3077
3694
|
sqliteSql("025-connector-connections", _025_connector_connections_default),
|
|
3078
3695
|
sqliteSql("026-ai-cost-accounting", _026_ai_cost_accounting_default),
|
|
3079
3696
|
sqliteSql("027-agent-context-checkpoints", _027_agent_context_checkpoints_default),
|
|
3080
|
-
sqliteSql("028-object-override-edit-times", _028_object_override_edit_times_default)
|
|
3697
|
+
sqliteSql("028-object-override-edit-times", _028_object_override_edit_times_default),
|
|
3698
|
+
sqliteSql("029-model-accounting", _029_model_accounting_default),
|
|
3699
|
+
sqliteSql("030-ai-usage-limits", _030_ai_usage_limits_default),
|
|
3700
|
+
sqliteSql("031-subagent-runs", _031_subagent_runs_default),
|
|
3701
|
+
sqliteSql("032-conversation-run-spec", _032_conversation_run_spec_default),
|
|
3702
|
+
sqliteSql("033-retire-agent-definitions", _033_retire_agent_definitions_default),
|
|
3703
|
+
sqliteSql("034-device-authorizations", _034_device_authorizations_default)
|
|
3081
3704
|
]
|
|
3082
3705
|
});
|
|
3083
3706
|
function sqliteStoragePath(basePath) {
|
|
@@ -3750,7 +4373,6 @@ function rowToThreadRecord(row) {
|
|
|
3750
4373
|
return {
|
|
3751
4374
|
id: row.id,
|
|
3752
4375
|
projectId: row.project_id,
|
|
3753
|
-
agentId: row.agent_id,
|
|
3754
4376
|
ownerPrincipal: { type: row.owner_principal_type, id: row.owner_principal_id },
|
|
3755
4377
|
title: row.title ?? undefined,
|
|
3756
4378
|
status: row.status,
|
|
@@ -3762,13 +4384,10 @@ function rowToThreadRecord(row) {
|
|
|
3762
4384
|
};
|
|
3763
4385
|
}
|
|
3764
4386
|
function rowToRunRecord(row) {
|
|
3765
|
-
|
|
4387
|
+
const base = {
|
|
3766
4388
|
id: row.id,
|
|
3767
4389
|
projectId: row.project_id,
|
|
3768
4390
|
executionId: row.execution_id,
|
|
3769
|
-
threadId: row.thread_id,
|
|
3770
|
-
agentId: row.agent_id,
|
|
3771
|
-
triggerMessageId: row.trigger_message_id,
|
|
3772
4391
|
requesterGroupIds: JSON.parse(row.requester_group_ids),
|
|
3773
4392
|
status: row.status,
|
|
3774
4393
|
modelId: row.model_id ?? undefined,
|
|
@@ -3784,6 +4403,29 @@ function rowToRunRecord(row) {
|
|
|
3784
4403
|
startedAt: row.started_at ? new Date(row.started_at) : undefined,
|
|
3785
4404
|
completedAt: row.completed_at ? new Date(row.completed_at) : undefined
|
|
3786
4405
|
};
|
|
4406
|
+
if (row.kind === "conversation") {
|
|
4407
|
+
if (!row.thread_id || !row.trigger_message_id) {
|
|
4408
|
+
throw new Error(`[SixbSqlite] Conversational Agent run '${row.id}' has invalid persisted fields.`);
|
|
4409
|
+
}
|
|
4410
|
+
return {
|
|
4411
|
+
...base,
|
|
4412
|
+
kind: "conversation",
|
|
4413
|
+
threadId: row.thread_id,
|
|
4414
|
+
triggerMessageId: row.trigger_message_id,
|
|
4415
|
+
...row.spec === null ? {} : { spec: JSON.parse(row.spec) }
|
|
4416
|
+
};
|
|
4417
|
+
}
|
|
4418
|
+
if (!row.parent_run_id || !row.spawn_key || row.spec === null) {
|
|
4419
|
+
throw new Error(`[SixbSqlite] Subagent run '${row.id}' has invalid persisted fields.`);
|
|
4420
|
+
}
|
|
4421
|
+
return {
|
|
4422
|
+
...base,
|
|
4423
|
+
kind: "subagent",
|
|
4424
|
+
parentRunId: row.parent_run_id,
|
|
4425
|
+
spawnKey: row.spawn_key,
|
|
4426
|
+
spec: JSON.parse(row.spec),
|
|
4427
|
+
...row.result === null ? {} : { result: JSON.parse(row.result) }
|
|
4428
|
+
};
|
|
3787
4429
|
}
|
|
3788
4430
|
function rowToMessageRecord(row) {
|
|
3789
4431
|
return {
|
|
@@ -4082,7 +4724,13 @@ class SqliteAgentMessageStore {
|
|
|
4082
4724
|
}
|
|
4083
4725
|
|
|
4084
4726
|
// src/agents/runs.ts
|
|
4085
|
-
import {
|
|
4727
|
+
import {
|
|
4728
|
+
assertAgentRunExecution,
|
|
4729
|
+
assertConversationAgentRunSpec,
|
|
4730
|
+
assertCreateSubagentRunInput,
|
|
4731
|
+
assertSubagentRunResult,
|
|
4732
|
+
subagentRunMatchesCreateInput
|
|
4733
|
+
} from "@sixb/core/internal/agent-run-storage-provider";
|
|
4086
4734
|
import { normalizeRequesterGroupIds } from "@sixb/core/internal/auth";
|
|
4087
4735
|
import { serializeSixbFailure } from "@sixb/core/internal/errors";
|
|
4088
4736
|
import {
|
|
@@ -4152,13 +4800,13 @@ class SqliteAgentRunStore {
|
|
|
4152
4800
|
this.executions = executions;
|
|
4153
4801
|
}
|
|
4154
4802
|
async create(input) {
|
|
4803
|
+
assertConversationAgentRunSpec(input.spec, "SixbSqlite");
|
|
4155
4804
|
const createdAt = input.createdAt ?? new Date;
|
|
4156
4805
|
await assertAgentRunExecution({
|
|
4157
4806
|
executions: this.executions,
|
|
4158
4807
|
projectId: input.projectId,
|
|
4159
4808
|
executionId: input.executionId,
|
|
4160
|
-
runId: input.id
|
|
4161
|
-
agentId: input.agentId
|
|
4809
|
+
runId: input.id
|
|
4162
4810
|
});
|
|
4163
4811
|
return this.db.transaction(() => {
|
|
4164
4812
|
const thread = this.db.query("SELECT active_run_id FROM agent_threads WHERE project_id = ? AND id = ?").get(input.projectId, input.threadId);
|
|
@@ -4174,15 +4822,16 @@ class SqliteAgentRunStore {
|
|
|
4174
4822
|
project_id,
|
|
4175
4823
|
id,
|
|
4176
4824
|
execution_id,
|
|
4825
|
+
kind,
|
|
4177
4826
|
thread_id,
|
|
4178
|
-
agent_id,
|
|
4179
4827
|
trigger_message_id,
|
|
4828
|
+
spec,
|
|
4180
4829
|
requester_group_ids,
|
|
4181
4830
|
status,
|
|
4182
4831
|
attempt,
|
|
4183
4832
|
created_at
|
|
4184
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, 'queued', 0, ?)
|
|
4185
|
-
`).run(input.projectId, input.id, input.executionId, input.threadId, input.
|
|
4833
|
+
) VALUES (?, ?, ?, 'conversation', ?, ?, ?, ?, 'queued', 0, ?)
|
|
4834
|
+
`).run(input.projectId, input.id, input.executionId, input.threadId, input.triggerMessageId, JSON.stringify(input.spec), JSON.stringify(normalizeRequesterGroupIds(input.requesterGroupIds)), createdAt.toISOString());
|
|
4186
4835
|
} catch (error) {
|
|
4187
4836
|
if (isUniqueConstraintError2(error)) {
|
|
4188
4837
|
throw new AgentStorageError3("duplicate_id", `[SixbSqlite] Agent run '${input.id}' already exists for project '${input.projectId}'.`);
|
|
@@ -4190,7 +4839,65 @@ class SqliteAgentRunStore {
|
|
|
4190
4839
|
throw error;
|
|
4191
4840
|
}
|
|
4192
4841
|
this.db.query("UPDATE agent_threads SET active_run_id = ?, updated_at = ? WHERE project_id = ? AND id = ?").run(input.id, createdAt.toISOString(), input.projectId, input.threadId);
|
|
4193
|
-
|
|
4842
|
+
const created = this.requireRun(input.projectId, input.id);
|
|
4843
|
+
if (created.kind !== "conversation") {
|
|
4844
|
+
throw new Error(`[SixbSqlite] Agent run insert '${input.id}' returned the wrong run kind.`);
|
|
4845
|
+
}
|
|
4846
|
+
return created;
|
|
4847
|
+
})();
|
|
4848
|
+
}
|
|
4849
|
+
async createSubagent(input) {
|
|
4850
|
+
assertCreateSubagentRunInput(input, "SixbSqlite");
|
|
4851
|
+
const execution = await assertAgentRunExecution({
|
|
4852
|
+
executions: this.executions,
|
|
4853
|
+
projectId: input.projectId,
|
|
4854
|
+
executionId: input.executionId,
|
|
4855
|
+
runId: input.id
|
|
4856
|
+
});
|
|
4857
|
+
return this.db.transaction(() => {
|
|
4858
|
+
const parentRow = this.db.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.parentRunId);
|
|
4859
|
+
if (!parentRow || parentRow.kind !== "conversation") {
|
|
4860
|
+
throw new AgentStorageError3("run_not_found", `[SixbSqlite] Parent Agent run '${input.parentRunId}' was not found.`);
|
|
4861
|
+
}
|
|
4862
|
+
const existingRow = this.db.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
|
|
4863
|
+
if (existingRow) {
|
|
4864
|
+
const existing = rowToRunRecord(existingRow);
|
|
4865
|
+
if (subagentRunMatchesCreateInput(existing, input))
|
|
4866
|
+
return existing;
|
|
4867
|
+
throw new AgentStorageError3("duplicate_id", `[SixbSqlite] Subagent run '${input.id}' already exists with different immutable inputs.`);
|
|
4868
|
+
}
|
|
4869
|
+
if (parentRow.status !== "running") {
|
|
4870
|
+
throw new AgentStorageError3("invalid_state", `[SixbSqlite] Parent Agent run '${parentRow.id}' is not running (status '${parentRow.status}').`);
|
|
4871
|
+
}
|
|
4872
|
+
if (execution.source.type !== "execution" || execution.source.executionId !== parentRow.execution_id) {
|
|
4873
|
+
throw new AgentStorageError3("invalid_input", `[SixbSqlite] Subagent execution '${execution.id}' is not a child of parent execution '${parentRow.execution_id}'.`);
|
|
4874
|
+
}
|
|
4875
|
+
if (parentRow.execution_token !== input.parentExecutionToken) {
|
|
4876
|
+
throw new AgentStorageError3("execution_lost", `[SixbSqlite] Parent Agent run '${parentRow.id}' is no longer owned by this execution.`);
|
|
4877
|
+
}
|
|
4878
|
+
const count = this.db.query(`SELECT COUNT(*) AS count FROM agent_runs
|
|
4879
|
+
WHERE project_id = ? AND parent_run_id = ? AND status IN ('queued', 'running')`).get(input.projectId, input.parentRunId);
|
|
4880
|
+
if (count.count >= input.maxActiveChildren) {
|
|
4881
|
+
throw new AgentStorageError3("active_child_limit", `[SixbSqlite] Agent run '${parentRow.id}' already has ${count.count} active subagents.`);
|
|
4882
|
+
}
|
|
4883
|
+
try {
|
|
4884
|
+
this.db.query(`
|
|
4885
|
+
INSERT INTO agent_runs (
|
|
4886
|
+
project_id, id, execution_id, kind, parent_run_id, spawn_key, spec,
|
|
4887
|
+
requester_group_ids, status, attempt, created_at
|
|
4888
|
+
) VALUES (?, ?, ?, 'subagent', ?, ?, ?, ?, 'queued', 0, ?)
|
|
4889
|
+
`).run(input.projectId, input.id, input.executionId, input.parentRunId, input.spawnKey, JSON.stringify(input.spec), parentRow.requester_group_ids, (input.createdAt ?? new Date).toISOString());
|
|
4890
|
+
} catch (error) {
|
|
4891
|
+
if (isUniqueConstraintError2(error)) {
|
|
4892
|
+
throw new AgentStorageError3("duplicate_id", `[SixbSqlite] Subagent run '${input.id}' conflicts with an existing durable spawn.`);
|
|
4893
|
+
}
|
|
4894
|
+
throw error;
|
|
4895
|
+
}
|
|
4896
|
+
const created = this.requireRun(input.projectId, input.id);
|
|
4897
|
+
if (created.kind !== "subagent") {
|
|
4898
|
+
throw new Error(`[SixbSqlite] Subagent insert '${input.id}' returned the wrong run kind.`);
|
|
4899
|
+
}
|
|
4900
|
+
return created;
|
|
4194
4901
|
})();
|
|
4195
4902
|
}
|
|
4196
4903
|
async start(input) {
|
|
@@ -4260,6 +4967,12 @@ class SqliteAgentRunStore {
|
|
|
4260
4967
|
if (run.execution_token !== input.executionToken) {
|
|
4261
4968
|
throw new AgentStorageError3("execution_lost", `[SixbSqlite] Execution token is no longer current on agent run '${input.id}'.`);
|
|
4262
4969
|
}
|
|
4970
|
+
if (input.status === "succeeded" && run.kind === "subagent") {
|
|
4971
|
+
assertSubagentRunResult(input.result, run.id, "SixbSqlite");
|
|
4972
|
+
}
|
|
4973
|
+
if (input.status === "succeeded" && run.kind === "conversation" && input.result !== undefined) {
|
|
4974
|
+
throw new AgentStorageError3("invalid_input", `[SixbSqlite] Conversational Agent run '${run.id}' cannot persist a subagent result.`);
|
|
4975
|
+
}
|
|
4263
4976
|
this.db.query(`
|
|
4264
4977
|
UPDATE agent_runs
|
|
4265
4978
|
SET
|
|
@@ -4268,11 +4981,12 @@ class SqliteAgentRunStore {
|
|
|
4268
4981
|
finish_reason = ?,
|
|
4269
4982
|
error = ?,
|
|
4270
4983
|
diagnostics = ?,
|
|
4984
|
+
result = ?,
|
|
4271
4985
|
execution_token = NULL,
|
|
4272
4986
|
execution_queue_lease_expires_at = NULL,
|
|
4273
4987
|
completed_at = ?
|
|
4274
4988
|
WHERE project_id = ? AND id = ?
|
|
4275
|
-
`).run(input.status, input.modelId ?? null, input.finishReason ?? null, errorValue, input.diagnostics === undefined ? null : JSON.stringify(input.diagnostics), completedAt.toISOString(), input.projectId, input.id);
|
|
4989
|
+
`).run(input.status, input.modelId ?? null, input.finishReason ?? null, errorValue, input.diagnostics === undefined ? null : JSON.stringify(input.diagnostics), input.status === "succeeded" && input.result !== undefined ? JSON.stringify(input.result) : null, completedAt.toISOString(), input.projectId, input.id);
|
|
4276
4990
|
this.releaseThread(run, completedAt);
|
|
4277
4991
|
return this.requireRun(input.projectId, input.id);
|
|
4278
4992
|
})();
|
|
@@ -4299,18 +5013,22 @@ class SqliteAgentRunStore {
|
|
|
4299
5013
|
});
|
|
4300
5014
|
}
|
|
4301
5015
|
async list(input) {
|
|
4302
|
-
if (hasEmptyStatuses(input)) {
|
|
5016
|
+
if (hasEmptyStatuses(input) || input.kinds?.length === 0) {
|
|
4303
5017
|
return { runs: [], hasMore: false, total: 0 };
|
|
4304
5018
|
}
|
|
4305
5019
|
const whereClauses = ["project_id = ?"];
|
|
4306
5020
|
const args = [input.projectId];
|
|
5021
|
+
if (input.kinds) {
|
|
5022
|
+
whereClauses.push(`kind IN (${input.kinds.map(() => "?").join(", ")})`);
|
|
5023
|
+
args.push(...input.kinds);
|
|
5024
|
+
}
|
|
4307
5025
|
if (input.threadId) {
|
|
4308
5026
|
whereClauses.push("thread_id = ?");
|
|
4309
5027
|
args.push(input.threadId);
|
|
4310
5028
|
}
|
|
4311
|
-
if (input.
|
|
4312
|
-
whereClauses.push("
|
|
4313
|
-
args.push(input.
|
|
5029
|
+
if (input.parentRunId) {
|
|
5030
|
+
whereClauses.push("parent_run_id = ?");
|
|
5031
|
+
args.push(input.parentRunId);
|
|
4314
5032
|
}
|
|
4315
5033
|
appendRunListFilters(whereClauses, args, input, "COALESCE(started_at, created_at)");
|
|
4316
5034
|
const { rows, total, hasMore } = queryRunList({
|
|
@@ -4338,6 +5056,8 @@ class SqliteAgentRunStore {
|
|
|
4338
5056
|
return row;
|
|
4339
5057
|
}
|
|
4340
5058
|
releaseThread(run, completedAt) {
|
|
5059
|
+
if (run.kind !== "conversation")
|
|
5060
|
+
return;
|
|
4341
5061
|
this.db.query(`
|
|
4342
5062
|
UPDATE agent_threads
|
|
4343
5063
|
SET active_run_id = NULL, updated_at = ?
|
|
@@ -4370,7 +5090,6 @@ class SqliteAgentThreadStore {
|
|
|
4370
5090
|
INSERT INTO agent_threads (
|
|
4371
5091
|
project_id,
|
|
4372
5092
|
id,
|
|
4373
|
-
agent_id,
|
|
4374
5093
|
owner_principal_type,
|
|
4375
5094
|
owner_principal_id,
|
|
4376
5095
|
title,
|
|
@@ -4380,8 +5099,8 @@ class SqliteAgentThreadStore {
|
|
|
4380
5099
|
message_count,
|
|
4381
5100
|
created_at,
|
|
4382
5101
|
updated_at
|
|
4383
|
-
) VALUES (?, ?, ?, ?, ?, ?,
|
|
4384
|
-
`).run(input.projectId, input.id, input.
|
|
5102
|
+
) VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, 0, ?, ?)
|
|
5103
|
+
`).run(input.projectId, input.id, input.ownerPrincipal.type, input.ownerPrincipal.id, input.title ?? null, input.status ?? "active", createdAt.toISOString(), updatedAt.toISOString());
|
|
4385
5104
|
} catch (error) {
|
|
4386
5105
|
if (isUniqueConstraintError2(error)) {
|
|
4387
5106
|
throw new AgentStorageError4("duplicate_id", `[SixbSqlite] Agent thread '${input.id}' already exists for project '${input.projectId}'.`);
|
|
@@ -4390,93 +5109,391 @@ class SqliteAgentThreadStore {
|
|
|
4390
5109
|
}
|
|
4391
5110
|
return this.requireThread(input.projectId, input.id);
|
|
4392
5111
|
}
|
|
4393
|
-
async getById(params) {
|
|
4394
|
-
const row = this.db.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?").get(params.projectId, params.id);
|
|
4395
|
-
return row ? rowToThreadRecord(row) : null;
|
|
5112
|
+
async getById(params) {
|
|
5113
|
+
const row = this.db.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?").get(params.projectId, params.id);
|
|
5114
|
+
return row ? rowToThreadRecord(row) : null;
|
|
5115
|
+
}
|
|
5116
|
+
async list(input) {
|
|
5117
|
+
if (input.statuses !== undefined && input.statuses.length === 0) {
|
|
5118
|
+
return { threads: [], hasMore: false, total: 0 };
|
|
5119
|
+
}
|
|
5120
|
+
const whereClauses = ["project_id = ?"];
|
|
5121
|
+
const args = [input.projectId];
|
|
5122
|
+
if (input.statuses) {
|
|
5123
|
+
whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`);
|
|
5124
|
+
args.push(...input.statuses);
|
|
5125
|
+
}
|
|
5126
|
+
if (input.ownerPrincipal) {
|
|
5127
|
+
whereClauses.push("owner_principal_type = ? AND owner_principal_id = ?");
|
|
5128
|
+
args.push(input.ownerPrincipal.type, input.ownerPrincipal.id);
|
|
5129
|
+
}
|
|
5130
|
+
const dir = input.order === "asc" ? "ASC" : "DESC";
|
|
5131
|
+
const { rows, total, hasMore } = queryAgentList({
|
|
5132
|
+
db: this.db,
|
|
5133
|
+
table: "agent_threads",
|
|
5134
|
+
whereClauses,
|
|
5135
|
+
args,
|
|
5136
|
+
orderBy: `COALESCE(last_message_at, created_at) ${dir}, id ${dir}`,
|
|
5137
|
+
limit: input.limit,
|
|
5138
|
+
offset: input.offset
|
|
5139
|
+
});
|
|
5140
|
+
return { threads: rows.map(rowToThreadRecord), hasMore, total };
|
|
5141
|
+
}
|
|
5142
|
+
requireThread(projectId, id) {
|
|
5143
|
+
const row = this.db.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
5144
|
+
if (!row) {
|
|
5145
|
+
throw new AgentStorageError4("invalid_state", `[SixbSqlite] Failed to load agent thread '${id}' for project '${projectId}'.`);
|
|
5146
|
+
}
|
|
5147
|
+
return rowToThreadRecord(row);
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
// src/agents/agent-storage.ts
|
|
5152
|
+
class SqliteAgentStorage {
|
|
5153
|
+
threads;
|
|
5154
|
+
runs;
|
|
5155
|
+
messages;
|
|
5156
|
+
checkpoints;
|
|
5157
|
+
connection;
|
|
5158
|
+
constructor(options) {
|
|
5159
|
+
this.connection = openSqliteStoreConnection(options);
|
|
5160
|
+
if (this.connection.installFreshSchema) {
|
|
5161
|
+
installFreshSqliteSchema(this.connection.db);
|
|
5162
|
+
}
|
|
5163
|
+
this.threads = new SqliteAgentThreadStore(this.connection.db);
|
|
5164
|
+
this.runs = new SqliteAgentRunStore(this.connection.db, options.executions);
|
|
5165
|
+
this.messages = new SqliteAgentMessageStore(this.connection.db);
|
|
5166
|
+
this.checkpoints = new SqliteAgentContextCheckpointStore(this.connection.db);
|
|
5167
|
+
}
|
|
5168
|
+
close() {
|
|
5169
|
+
closeSqliteStoreConnection(this.connection);
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
// src/ai-cost-storage.ts
|
|
5173
|
+
import {
|
|
5174
|
+
aiModelCallCostDetails,
|
|
5175
|
+
aiModelCallCostMatchesUsage,
|
|
5176
|
+
buildAiAccountingOverviewFromFragments,
|
|
5177
|
+
normalizeAiAccountingQuery,
|
|
5178
|
+
normalizeAiModelCallAccountingQuery as normalizeAiModelCallAccountingQuery2,
|
|
5179
|
+
normalizeAiModelCallCostRecord,
|
|
5180
|
+
parseAiModelCallCostDetails,
|
|
5181
|
+
toAccountingItem
|
|
5182
|
+
} from "@sixb/core/internal/ai-cost-storage-provider";
|
|
5183
|
+
import { normalizeModelProviderIds as normalizeModelProviderIds2 } from "@sixb/core/models";
|
|
5184
|
+
import {
|
|
5185
|
+
AiCostStorageError
|
|
5186
|
+
} from "@sixb/core/storage";
|
|
5187
|
+
|
|
5188
|
+
// src/ai-cost-groups.ts
|
|
5189
|
+
import {
|
|
5190
|
+
aiModelCallGroupFragmentFromRow,
|
|
5191
|
+
buildAiModelCallGroupsFromFragments,
|
|
5192
|
+
normalizeAiModelCallAccountingQuery
|
|
5193
|
+
} from "@sixb/core/internal/ai-cost-storage-provider";
|
|
5194
|
+
async function listAiModelCallGroups(connection, input) {
|
|
5195
|
+
const query = normalizeAiModelCallAccountingQuery(input);
|
|
5196
|
+
const parameters = [
|
|
5197
|
+
query.projectId,
|
|
5198
|
+
query.from.toISOString(),
|
|
5199
|
+
query.to.toISOString(),
|
|
5200
|
+
query.providerId ?? null,
|
|
5201
|
+
query.modelId ?? null,
|
|
5202
|
+
query.valuationStatus ?? null
|
|
5203
|
+
];
|
|
5204
|
+
const count = connection.db.query(COUNT_SQL).get(...parameters);
|
|
5205
|
+
const rows = connection.db.query(PAGE_SQL).all(...parameters, query.limit, query.offset);
|
|
5206
|
+
const total = Number(count?.total);
|
|
5207
|
+
if (!Number.isSafeInteger(total) || total < 0)
|
|
5208
|
+
throw new RangeError("[Sixb] Invalid model-call group count.");
|
|
5209
|
+
const items = buildAiModelCallGroupsFromFragments(rows.map(aiModelCallGroupFragmentFromRow));
|
|
5210
|
+
return { items, total, hasMore: query.offset + items.length < total };
|
|
5211
|
+
}
|
|
5212
|
+
var FILTERED_SQL = `
|
|
5213
|
+
WITH filtered AS (
|
|
5214
|
+
SELECT COALESCE(root_agent.execution_id, usage.execution_id) AS root_execution_id,
|
|
5215
|
+
usage.execution_id, usage.provider_id, usage.requested_model_id, usage.occurred_at,
|
|
5216
|
+
usage.total_tokens,
|
|
5217
|
+
COALESCE(cost.status, 'unvalued') AS valuation_status,
|
|
5218
|
+
cost.currency AS amount_currency, cost.amount_nanos,
|
|
5219
|
+
root_agent.id AS root_agent_run_id,
|
|
5220
|
+
root_agent.thread_id AS root_thread_id,
|
|
5221
|
+
direct_agent.kind AS direct_kind,
|
|
5222
|
+
direct_agent.id AS direct_run_id,
|
|
5223
|
+
direct_agent.parent_run_id AS parent_run_id,
|
|
5224
|
+
workflow_node.node_id AS workflow_agent_step_id,
|
|
5225
|
+
workflow_agent.node_run_id AS workflow_node_run_id,
|
|
5226
|
+
workflow_node.workflow_id AS workflow_id,
|
|
5227
|
+
workflow_node.workflow_run_id AS workflow_run_id
|
|
5228
|
+
FROM ai_model_call_usage AS usage
|
|
5229
|
+
LEFT JOIN agent_runs AS direct_agent
|
|
5230
|
+
ON direct_agent.project_id = usage.project_id AND direct_agent.execution_id = usage.execution_id
|
|
5231
|
+
LEFT JOIN agent_runs AS root_agent
|
|
5232
|
+
ON root_agent.project_id = usage.project_id
|
|
5233
|
+
AND root_agent.id = COALESCE(direct_agent.parent_run_id, direct_agent.id)
|
|
5234
|
+
LEFT JOIN workflow_agent_node_runs AS workflow_agent
|
|
5235
|
+
ON workflow_agent.project_id = usage.project_id AND workflow_agent.execution_id = usage.execution_id
|
|
5236
|
+
LEFT JOIN workflow_node_runs AS workflow_node
|
|
5237
|
+
ON workflow_node.project_id = workflow_agent.project_id AND workflow_node.id = workflow_agent.node_run_id
|
|
5238
|
+
LEFT JOIN ai_model_call_valuations AS cost
|
|
5239
|
+
ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
|
|
5240
|
+
WHERE usage.project_id = ?1
|
|
5241
|
+
AND usage.occurred_at >= ?2
|
|
5242
|
+
AND usage.occurred_at < ?3
|
|
5243
|
+
AND (?4 IS NULL OR usage.provider_id = ?4)
|
|
5244
|
+
AND (?5 IS NULL OR usage.requested_model_id = ?5)
|
|
5245
|
+
AND (?6 IS NULL OR COALESCE(cost.status, 'unvalued') = ?6)
|
|
5246
|
+
), roots AS (
|
|
5247
|
+
SELECT root_execution_id, MAX(occurred_at) AS last_call_at
|
|
5248
|
+
FROM filtered GROUP BY root_execution_id
|
|
5249
|
+
)
|
|
5250
|
+
`;
|
|
5251
|
+
var COUNT_SQL = `${FILTERED_SQL} SELECT CAST(COUNT(*) AS TEXT) AS total FROM roots`;
|
|
5252
|
+
var PAGE_SQL = `${FILTERED_SQL}, page AS (
|
|
5253
|
+
SELECT root_execution_id FROM roots
|
|
5254
|
+
ORDER BY last_call_at DESC, root_execution_id ASC LIMIT ?7 OFFSET ?8
|
|
5255
|
+
)
|
|
5256
|
+
SELECT filtered.root_execution_id, execution_id, provider_id, requested_model_id,
|
|
5257
|
+
MIN(occurred_at) AS first_call_at, MAX(occurred_at) AS last_call_at,
|
|
5258
|
+
CAST(COUNT(*) AS TEXT) AS call_count,
|
|
5259
|
+
CAST(COUNT(total_tokens) AS TEXT) AS token_count,
|
|
5260
|
+
CAST(SUM(total_tokens) AS TEXT) AS total_tokens,
|
|
5261
|
+
valuation_status, amount_currency,
|
|
5262
|
+
CAST(SUM(amount_nanos / 1000000000000) AS TEXT) AS amount_high,
|
|
5263
|
+
CAST(SUM((amount_nanos / 1000000) % 1000000) AS TEXT) AS amount_middle,
|
|
5264
|
+
CAST(SUM(amount_nanos % 1000000) AS TEXT) AS amount_low,
|
|
5265
|
+
MAX(root_agent_run_id) AS root_agent_run_id,
|
|
5266
|
+
MAX(root_thread_id) AS root_thread_id,
|
|
5267
|
+
MAX(direct_kind) AS direct_kind,
|
|
5268
|
+
MAX(direct_run_id) AS direct_run_id,
|
|
5269
|
+
MAX(parent_run_id) AS parent_run_id,
|
|
5270
|
+
MAX(workflow_agent_step_id) AS workflow_agent_step_id,
|
|
5271
|
+
MAX(workflow_node_run_id) AS workflow_node_run_id,
|
|
5272
|
+
MAX(workflow_id) AS workflow_id,
|
|
5273
|
+
MAX(workflow_run_id) AS workflow_run_id
|
|
5274
|
+
FROM filtered JOIN page ON page.root_execution_id = filtered.root_execution_id
|
|
5275
|
+
GROUP BY filtered.root_execution_id, execution_id, provider_id, requested_model_id,
|
|
5276
|
+
valuation_status, amount_currency
|
|
5277
|
+
`;
|
|
5278
|
+
|
|
5279
|
+
// src/ai-usage-storage.ts
|
|
5280
|
+
import { isModelReasoning, normalizeModelProviderIds } from "@sixb/core/models";
|
|
5281
|
+
import {
|
|
5282
|
+
AiUsageStorageError,
|
|
5283
|
+
aggregateAiModelCallUsage,
|
|
5284
|
+
assertAiUsageExecutionId,
|
|
5285
|
+
normalizeAiModelCallRecord
|
|
5286
|
+
} from "@sixb/core/storage";
|
|
5287
|
+
class SqliteAiUsageStorage {
|
|
5288
|
+
connection;
|
|
5289
|
+
constructor(options = {}) {
|
|
5290
|
+
this.connection = openSqliteStoreConnection(options);
|
|
5291
|
+
if (this.connection.installFreshSchema) {
|
|
5292
|
+
installFreshSqliteSchema(this.connection.db);
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
async recordModelCall(input) {
|
|
5296
|
+
const record = normalizeAiModelCallRecord(input);
|
|
5297
|
+
return runImmediateTransaction(this.connection.db, () => {
|
|
5298
|
+
const existing = this.findByIdempotencyKey(record);
|
|
5299
|
+
if (existing)
|
|
5300
|
+
return { record: existing, created: false };
|
|
5301
|
+
this.assertExecutionExists(record.projectId, record.executionId);
|
|
5302
|
+
try {
|
|
5303
|
+
this.insertRecord(record);
|
|
5304
|
+
} catch (error) {
|
|
5305
|
+
if (!isUniqueConstraintError2(error))
|
|
5306
|
+
throw error;
|
|
5307
|
+
const concurrent = this.findByIdempotencyKey(record);
|
|
5308
|
+
if (concurrent)
|
|
5309
|
+
return { record: concurrent, created: false };
|
|
5310
|
+
throw new AiUsageStorageError("duplicate_id", `[SixbSqlite] AI usage record '${record.id}' already exists for project '${record.projectId}'.`);
|
|
5311
|
+
}
|
|
5312
|
+
for (const groupId of record.requesterGroupIds) {
|
|
5313
|
+
this.connection.db.query(`
|
|
5314
|
+
INSERT INTO ai_model_call_usage_groups (
|
|
5315
|
+
project_id, usage_record_id, group_id, occurred_at
|
|
5316
|
+
) VALUES (?, ?, ?, ?)
|
|
5317
|
+
`).run(record.projectId, record.id, groupId, record.occurredAt.toISOString());
|
|
5318
|
+
}
|
|
5319
|
+
return { record: this.requireById(record.projectId, record.id), created: true };
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
async summarizeExecution(input) {
|
|
5323
|
+
const [summary] = await this.summarizeExecutions({
|
|
5324
|
+
projectId: input.projectId,
|
|
5325
|
+
executionIds: [input.executionId]
|
|
5326
|
+
});
|
|
5327
|
+
return summary;
|
|
5328
|
+
}
|
|
5329
|
+
async getLatestForExecution(input) {
|
|
5330
|
+
assertNonBlankProjectId(input.projectId);
|
|
5331
|
+
assertAiUsageExecutionId(input.executionId);
|
|
5332
|
+
const row = this.connection.db.query(`
|
|
5333
|
+
SELECT * FROM ai_model_call_usage
|
|
5334
|
+
WHERE project_id = ? AND execution_id = ?
|
|
5335
|
+
ORDER BY occurred_at DESC, id DESC
|
|
5336
|
+
LIMIT 1
|
|
5337
|
+
`).get(input.projectId, input.executionId);
|
|
5338
|
+
return row ? this.rowToRecord(row) : null;
|
|
5339
|
+
}
|
|
5340
|
+
async summarizeExecutions(input) {
|
|
5341
|
+
assertNonBlankProjectId(input.projectId);
|
|
5342
|
+
for (const executionId of input.executionIds)
|
|
5343
|
+
assertAiUsageExecutionId(executionId);
|
|
5344
|
+
if (input.executionIds.length === 0)
|
|
5345
|
+
return [];
|
|
5346
|
+
const rows = this.executionRows(input);
|
|
5347
|
+
return aggregateExecutionRows(input.executionIds, rows);
|
|
5348
|
+
}
|
|
5349
|
+
close() {
|
|
5350
|
+
closeSqliteStoreConnection(this.connection);
|
|
5351
|
+
}
|
|
5352
|
+
insertRecord(record) {
|
|
5353
|
+
this.connection.db.query(`
|
|
5354
|
+
INSERT INTO ai_model_call_usage (
|
|
5355
|
+
project_id,
|
|
5356
|
+
id,
|
|
5357
|
+
execution_id,
|
|
5358
|
+
attempt,
|
|
5359
|
+
call_id,
|
|
5360
|
+
provider_id,
|
|
5361
|
+
provider_ids,
|
|
5362
|
+
requested_model_id,
|
|
5363
|
+
requested_reasoning,
|
|
5364
|
+
response_model_id,
|
|
5365
|
+
response_id,
|
|
5366
|
+
input_tokens,
|
|
5367
|
+
output_tokens,
|
|
5368
|
+
total_tokens,
|
|
5369
|
+
uncached_input_tokens,
|
|
5370
|
+
cache_read_input_tokens,
|
|
5371
|
+
cache_write_input_tokens,
|
|
5372
|
+
text_output_tokens,
|
|
5373
|
+
reasoning_output_tokens,
|
|
5374
|
+
reporting_status,
|
|
5375
|
+
raw_usage,
|
|
5376
|
+
occurred_at,
|
|
5377
|
+
recorded_at
|
|
5378
|
+
) VALUES (
|
|
5379
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
5380
|
+
)
|
|
5381
|
+
`).run(record.projectId, record.id, record.executionId, record.attempt, record.callId, record.providerId, record.providerIds === undefined ? null : JSON.stringify(record.providerIds), record.requestedModelId, record.requestedReasoning === undefined ? null : JSON.stringify(record.requestedReasoning), record.responseModelId ?? null, record.responseId, record.usage.inputTokens ?? null, record.usage.outputTokens ?? null, record.usage.totalTokens ?? null, record.usage.uncachedInputTokens ?? null, record.usage.cacheReadInputTokens ?? null, record.usage.cacheWriteInputTokens ?? null, record.usage.textOutputTokens ?? null, record.usage.reasoningOutputTokens ?? null, record.usage.reportingStatus, record.rawUsage === undefined ? null : JSON.stringify(record.rawUsage), record.occurredAt.toISOString(), record.recordedAt.toISOString());
|
|
5382
|
+
}
|
|
5383
|
+
findByIdempotencyKey(record) {
|
|
5384
|
+
const row = this.connection.db.query(`
|
|
5385
|
+
SELECT * FROM ai_model_call_usage
|
|
5386
|
+
WHERE project_id = ?
|
|
5387
|
+
AND execution_id = ?
|
|
5388
|
+
AND attempt = ?
|
|
5389
|
+
AND call_id = ?
|
|
5390
|
+
AND response_id = ?
|
|
5391
|
+
`).get(record.projectId, record.executionId, record.attempt, record.callId, record.responseId);
|
|
5392
|
+
return row ? this.rowToRecord(row) : null;
|
|
4396
5393
|
}
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
const args = [input.projectId];
|
|
4403
|
-
if (input.agentId) {
|
|
4404
|
-
whereClauses.push("agent_id = ?");
|
|
4405
|
-
args.push(input.agentId);
|
|
4406
|
-
}
|
|
4407
|
-
if (input.agentIds) {
|
|
4408
|
-
whereClauses.push(`agent_id IN (${input.agentIds.map(() => "?").join(", ")})`);
|
|
4409
|
-
args.push(...input.agentIds);
|
|
4410
|
-
}
|
|
4411
|
-
if (input.statuses) {
|
|
4412
|
-
whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`);
|
|
4413
|
-
args.push(...input.statuses);
|
|
4414
|
-
}
|
|
4415
|
-
if (input.ownerPrincipal) {
|
|
4416
|
-
whereClauses.push("owner_principal_type = ? AND owner_principal_id = ?");
|
|
4417
|
-
args.push(input.ownerPrincipal.type, input.ownerPrincipal.id);
|
|
4418
|
-
}
|
|
4419
|
-
const dir = input.order === "asc" ? "ASC" : "DESC";
|
|
4420
|
-
const { rows, total, hasMore } = queryAgentList({
|
|
4421
|
-
db: this.db,
|
|
4422
|
-
table: "agent_threads",
|
|
4423
|
-
whereClauses,
|
|
4424
|
-
args,
|
|
4425
|
-
orderBy: `COALESCE(last_message_at, created_at) ${dir}, id ${dir}`,
|
|
4426
|
-
limit: input.limit,
|
|
4427
|
-
offset: input.offset
|
|
4428
|
-
});
|
|
4429
|
-
return { threads: rows.map(rowToThreadRecord), hasMore, total };
|
|
5394
|
+
requireById(projectId, id) {
|
|
5395
|
+
const row = this.connection.db.query("SELECT * FROM ai_model_call_usage WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
5396
|
+
if (!row)
|
|
5397
|
+
throw new Error(`[SixbSqlite] AI usage record '${id}' disappeared after insert.`);
|
|
5398
|
+
return this.rowToRecord(row);
|
|
4430
5399
|
}
|
|
4431
|
-
|
|
4432
|
-
const
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
5400
|
+
rowToRecord(row) {
|
|
5401
|
+
const groupRows = this.connection.db.query(`
|
|
5402
|
+
SELECT group_id FROM ai_model_call_usage_groups
|
|
5403
|
+
WHERE project_id = ? AND usage_record_id = ?
|
|
5404
|
+
ORDER BY group_id
|
|
5405
|
+
`).all(row.project_id, row.id);
|
|
5406
|
+
return {
|
|
5407
|
+
id: row.id,
|
|
5408
|
+
projectId: row.project_id,
|
|
5409
|
+
executionId: row.execution_id,
|
|
5410
|
+
attempt: row.attempt,
|
|
5411
|
+
callId: row.call_id,
|
|
5412
|
+
requesterGroupIds: groupRows.map((group) => group.group_id),
|
|
5413
|
+
providerId: row.provider_id,
|
|
5414
|
+
...row.provider_ids === null ? {} : { providerIds: normalizeModelProviderIds(JSON.parse(row.provider_ids)) },
|
|
5415
|
+
requestedModelId: row.requested_model_id,
|
|
5416
|
+
...row.requested_reasoning === null ? {} : { requestedReasoning: requestedReasoningFromRow(row.requested_reasoning) },
|
|
5417
|
+
...row.response_model_id === null ? {} : { responseModelId: row.response_model_id },
|
|
5418
|
+
responseId: row.response_id,
|
|
5419
|
+
usage: {
|
|
5420
|
+
...usageFromRow(row),
|
|
5421
|
+
...row.total_tokens === null ? {} : { totalTokens: row.total_tokens },
|
|
5422
|
+
reportingStatus: row.reporting_status
|
|
5423
|
+
},
|
|
5424
|
+
...row.raw_usage === null ? {} : { rawUsage: JSON.parse(row.raw_usage) },
|
|
5425
|
+
occurredAt: new Date(row.occurred_at),
|
|
5426
|
+
recordedAt: new Date(row.recorded_at)
|
|
5427
|
+
};
|
|
5428
|
+
}
|
|
5429
|
+
executionRows(input) {
|
|
5430
|
+
const requested = JSON.stringify([...new Set(input.executionIds)]);
|
|
5431
|
+
return this.connection.db.query(`
|
|
5432
|
+
WITH requested AS (
|
|
5433
|
+
SELECT DISTINCT value AS execution_id
|
|
5434
|
+
FROM json_each(?)
|
|
5435
|
+
)
|
|
5436
|
+
SELECT usage.* FROM requested
|
|
5437
|
+
JOIN ai_model_call_usage AS usage
|
|
5438
|
+
ON usage.project_id = ?
|
|
5439
|
+
AND usage.execution_id = requested.execution_id
|
|
5440
|
+
`).all(requested, input.projectId);
|
|
5441
|
+
}
|
|
5442
|
+
assertExecutionExists(projectId, executionId) {
|
|
5443
|
+
const execution = this.connection.db.query("SELECT 1 FROM executions WHERE project_id = ? AND id = ?").get(projectId, executionId);
|
|
5444
|
+
if (execution)
|
|
5445
|
+
return;
|
|
5446
|
+
throw new AiUsageStorageError("missing_execution", `[SixbSqlite] AI usage execution '${executionId}' does not exist in project '${projectId}'.`);
|
|
4437
5447
|
}
|
|
4438
5448
|
}
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
checkpoints;
|
|
4446
|
-
connection;
|
|
4447
|
-
constructor(options) {
|
|
4448
|
-
this.connection = openSqliteStoreConnection(options);
|
|
4449
|
-
if (this.connection.installFreshSchema) {
|
|
4450
|
-
installFreshSqliteSchema(this.connection.db);
|
|
4451
|
-
}
|
|
4452
|
-
this.threads = new SqliteAgentThreadStore(this.connection.db);
|
|
4453
|
-
this.runs = new SqliteAgentRunStore(this.connection.db, options.executions);
|
|
4454
|
-
this.messages = new SqliteAgentMessageStore(this.connection.db);
|
|
4455
|
-
this.checkpoints = new SqliteAgentContextCheckpointStore(this.connection.db);
|
|
5449
|
+
function aggregateExecutionRows(executionIds, rows) {
|
|
5450
|
+
const usageByExecutionId = new Map;
|
|
5451
|
+
for (const executionId of executionIds)
|
|
5452
|
+
usageByExecutionId.set(executionId, []);
|
|
5453
|
+
for (const row of rows) {
|
|
5454
|
+
usageByExecutionId.get(row.execution_id)?.push(usageFromRow(row));
|
|
4456
5455
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
5456
|
+
return executionIds.map((executionId) => {
|
|
5457
|
+
const usages = usageByExecutionId.get(executionId) ?? [];
|
|
5458
|
+
return {
|
|
5459
|
+
modelCallCount: usages.length,
|
|
5460
|
+
usage: aggregateAiModelCallUsage(usages)
|
|
5461
|
+
};
|
|
5462
|
+
});
|
|
5463
|
+
}
|
|
5464
|
+
function usageFromRow(row) {
|
|
5465
|
+
return {
|
|
5466
|
+
...row.input_tokens === null ? {} : { inputTokens: row.input_tokens },
|
|
5467
|
+
...row.output_tokens === null ? {} : { outputTokens: row.output_tokens },
|
|
5468
|
+
...row.uncached_input_tokens === null ? {} : { uncachedInputTokens: row.uncached_input_tokens },
|
|
5469
|
+
...row.cache_read_input_tokens === null ? {} : { cacheReadInputTokens: row.cache_read_input_tokens },
|
|
5470
|
+
...row.cache_write_input_tokens === null ? {} : { cacheWriteInputTokens: row.cache_write_input_tokens },
|
|
5471
|
+
...row.text_output_tokens === null ? {} : { textOutputTokens: row.text_output_tokens },
|
|
5472
|
+
...row.reasoning_output_tokens === null ? {} : { reasoningOutputTokens: row.reasoning_output_tokens }
|
|
5473
|
+
};
|
|
5474
|
+
}
|
|
5475
|
+
function requestedReasoningFromRow(value) {
|
|
5476
|
+
const parsed = JSON.parse(value);
|
|
5477
|
+
if (!isModelReasoning(parsed)) {
|
|
5478
|
+
throw new Error("[SixbSqlite] Stored AI usage requested reasoning is invalid.");
|
|
5479
|
+
}
|
|
5480
|
+
return parsed;
|
|
5481
|
+
}
|
|
5482
|
+
function assertNonBlankProjectId(projectId) {
|
|
5483
|
+
if (typeof projectId !== "string" || projectId.trim().length === 0) {
|
|
5484
|
+
throw new TypeError("[Sixb] AI usage projectId must be nonblank.");
|
|
4459
5485
|
}
|
|
4460
5486
|
}
|
|
5487
|
+
|
|
4461
5488
|
// src/ai-cost-storage.ts
|
|
4462
|
-
import {
|
|
4463
|
-
aiModelCallCostDetails,
|
|
4464
|
-
aiModelCallCostMatchesUsage,
|
|
4465
|
-
buildAiAccountingOverviewFromFragments,
|
|
4466
|
-
normalizeAiAccountingQuery,
|
|
4467
|
-
normalizeAiModelCallAccountingQuery,
|
|
4468
|
-
normalizeAiModelCallCostRecord,
|
|
4469
|
-
parseAiModelCallCostDetails,
|
|
4470
|
-
toAccountingItem
|
|
4471
|
-
} from "@sixb/core/internal/ai-cost-storage-provider";
|
|
4472
|
-
import {
|
|
4473
|
-
AiCostStorageError
|
|
4474
|
-
} from "@sixb/core/storage";
|
|
4475
5489
|
class SqliteAiCostStorage {
|
|
4476
5490
|
connection;
|
|
4477
5491
|
constructor(connection) {
|
|
4478
5492
|
this.connection = connection;
|
|
4479
5493
|
}
|
|
5494
|
+
async listModelCallGroups(input) {
|
|
5495
|
+
return listAiModelCallGroups(this.connection, input);
|
|
5496
|
+
}
|
|
4480
5497
|
async recordModelCallCost(input) {
|
|
4481
5498
|
const record = normalizeAiModelCallCostRecord(input);
|
|
4482
5499
|
runImmediateTransaction(this.connection.db, () => {
|
|
@@ -4522,7 +5539,7 @@ class SqliteAiCostStorage {
|
|
|
4522
5539
|
return buildAiAccountingOverviewFromFragments(input, rows.map(aggregateFragmentFromRow));
|
|
4523
5540
|
}
|
|
4524
5541
|
async listModelCalls(input) {
|
|
4525
|
-
const query =
|
|
5542
|
+
const query = normalizeAiModelCallAccountingQuery2(input);
|
|
4526
5543
|
const parameters = accountingListFilterParameters(query);
|
|
4527
5544
|
const count = this.connection.db.query(SQLITE_ACCOUNTING_LIST_COUNT_SQL).get(...parameters);
|
|
4528
5545
|
const rows = this.connection.db.query(SQLITE_ACCOUNTING_LIST_PAGE_SQL).all(...parameters, query.limit, query.offset);
|
|
@@ -4550,7 +5567,7 @@ class SqliteAiCostStorage {
|
|
|
4550
5567
|
}
|
|
4551
5568
|
findUsage(projectId, id) {
|
|
4552
5569
|
const row = this.connection.db.query("SELECT * FROM ai_model_call_usage WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
4553
|
-
return row ?
|
|
5570
|
+
return row ? usageFromRow2(row, []) : null;
|
|
4554
5571
|
}
|
|
4555
5572
|
}
|
|
4556
5573
|
var SQLITE_ACCOUNTING_LIST_FILTER = `
|
|
@@ -4576,13 +5593,15 @@ var SQLITE_ACCOUNTING_LIST_COUNT_SQL = `
|
|
|
4576
5593
|
var SQLITE_ACCOUNTING_LIST_PAGE_SQL = `
|
|
4577
5594
|
SELECT usage.*,
|
|
4578
5595
|
CASE
|
|
4579
|
-
WHEN direct_agent.
|
|
5596
|
+
WHEN direct_agent.kind = 'conversation' THEN 'agent'
|
|
5597
|
+
WHEN direct_agent.kind = 'subagent' THEN 'subagent'
|
|
4580
5598
|
WHEN workflow_agent.node_run_id IS NOT NULL THEN 'workflowAgent'
|
|
4581
5599
|
ELSE NULL
|
|
4582
5600
|
END AS attribution_kind,
|
|
4583
|
-
|
|
5601
|
+
workflow_node.node_id AS attribution_agent_step_id,
|
|
4584
5602
|
direct_agent.id AS attribution_agent_run_id,
|
|
4585
5603
|
direct_agent.thread_id AS attribution_thread_id,
|
|
5604
|
+
direct_agent.parent_run_id AS attribution_parent_run_id,
|
|
4586
5605
|
workflow_agent.node_run_id AS attribution_node_run_id,
|
|
4587
5606
|
workflow_node.workflow_id AS attribution_workflow_id,
|
|
4588
5607
|
workflow_node.workflow_run_id AS attribution_workflow_run_id,
|
|
@@ -4630,7 +5649,12 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
|
4630
5649
|
usage.text_output_tokens,
|
|
4631
5650
|
usage.reasoning_output_tokens,
|
|
4632
5651
|
usage.reporting_status,
|
|
4633
|
-
|
|
5652
|
+
CASE
|
|
5653
|
+
WHEN direct_agent.kind = 'conversation' THEN 'agent'
|
|
5654
|
+
WHEN workflow_agent.node_run_id IS NOT NULL THEN 'workflowAgent'
|
|
5655
|
+
ELSE NULL
|
|
5656
|
+
END AS attribution_agent_kind,
|
|
5657
|
+
workflow_node.node_id AS attribution_agent_step_id,
|
|
4634
5658
|
workflow_node.workflow_id AS attribution_workflow_id,
|
|
4635
5659
|
CASE
|
|
4636
5660
|
WHEN cost.status = 'rated' THEN 'rated'
|
|
@@ -4655,11 +5679,11 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
|
4655
5679
|
AND (? IS NULL OR usage.provider_id = ?)
|
|
4656
5680
|
AND (? IS NULL OR usage.requested_model_id = ?)
|
|
4657
5681
|
), expanded AS (
|
|
4658
|
-
SELECT 'totals' AS dimension, NULL AS key_1, NULL AS key_2, NULL AS bucket_start,
|
|
5682
|
+
SELECT 'totals' AS dimension, NULL AS key_1, NULL AS key_2, NULL AS key_3, NULL AS bucket_start,
|
|
4659
5683
|
classified.*
|
|
4660
5684
|
FROM classified
|
|
4661
5685
|
UNION ALL
|
|
4662
|
-
SELECT 'series', NULL, NULL,
|
|
5686
|
+
SELECT 'series', NULL, NULL, NULL,
|
|
4663
5687
|
CASE ?
|
|
4664
5688
|
WHEN 'hour' THEN strftime('%Y-%m-%dT%H:00:00.000Z', occurred_at)
|
|
4665
5689
|
WHEN 'day' THEN strftime('%Y-%m-%dT00:00:00.000Z', occurred_at)
|
|
@@ -4672,17 +5696,18 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
|
4672
5696
|
classified.*
|
|
4673
5697
|
FROM classified
|
|
4674
5698
|
UNION ALL
|
|
4675
|
-
SELECT 'model', provider_id, requested_model_id, NULL, classified.* FROM classified
|
|
5699
|
+
SELECT 'model', provider_id, requested_model_id, NULL, NULL, classified.* FROM classified
|
|
4676
5700
|
UNION ALL
|
|
4677
|
-
SELECT 'agent',
|
|
4678
|
-
FROM classified WHERE
|
|
5701
|
+
SELECT 'agent', attribution_agent_kind, attribution_workflow_id, attribution_agent_step_id, NULL, classified.*
|
|
5702
|
+
FROM classified WHERE attribution_agent_kind IS NOT NULL
|
|
4679
5703
|
UNION ALL
|
|
4680
|
-
SELECT 'workflow', attribution_workflow_id, NULL, NULL, classified.*
|
|
5704
|
+
SELECT 'workflow', attribution_workflow_id, NULL, NULL, NULL, classified.*
|
|
4681
5705
|
FROM classified WHERE attribution_workflow_id IS NOT NULL
|
|
4682
5706
|
)
|
|
4683
5707
|
SELECT dimension,
|
|
4684
5708
|
key_1,
|
|
4685
5709
|
key_2,
|
|
5710
|
+
key_3,
|
|
4686
5711
|
bucket_start,
|
|
4687
5712
|
amount_currency,
|
|
4688
5713
|
CAST(COUNT(*) AS TEXT) AS model_call_count,
|
|
@@ -4712,7 +5737,7 @@ var SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
|
4712
5737
|
CAST(SUM((amount_nanos / 1000000) % 1000000) AS TEXT) AS amount_middle,
|
|
4713
5738
|
CAST(SUM(amount_nanos % 1000000) AS TEXT) AS amount_low
|
|
4714
5739
|
FROM expanded
|
|
4715
|
-
GROUP BY dimension, key_1, key_2, bucket_start, amount_currency
|
|
5740
|
+
GROUP BY dimension, key_1, key_2, key_3, bucket_start, amount_currency
|
|
4716
5741
|
`;
|
|
4717
5742
|
function accountingListFilterParameters(query) {
|
|
4718
5743
|
return [
|
|
@@ -4730,7 +5755,10 @@ function accountingListFilterParameters(query) {
|
|
|
4730
5755
|
];
|
|
4731
5756
|
}
|
|
4732
5757
|
function aggregateFragmentFromRow(row) {
|
|
4733
|
-
const dimensionKeys = row.dimension === "model" ? { providerId: required(row.key_1), modelId: required(row.key_2) } : row.dimension === "agent" ? {
|
|
5758
|
+
const dimensionKeys = row.dimension === "model" ? { providerId: required(row.key_1), modelId: required(row.key_2) } : row.dimension === "agent" ? {
|
|
5759
|
+
agentKind: required(row.key_1),
|
|
5760
|
+
...row.key_1 === "workflowAgent" ? { workflowId: required(row.key_2), agentStepId: required(row.key_3) } : {}
|
|
5761
|
+
} : row.dimension === "workflow" ? { workflowId: required(row.key_1) } : row.dimension === "series" ? { start: new Date(required(row.bucket_start)) } : {};
|
|
4734
5762
|
return {
|
|
4735
5763
|
dimension: row.dimension,
|
|
4736
5764
|
...dimensionKeys,
|
|
@@ -4765,16 +5793,19 @@ function sqliteAggregateAmount(row) {
|
|
|
4765
5793
|
return (BigInt(required(row.amount_high)) * 1000000000000n + BigInt(required(row.amount_middle)) * 1000000n + BigInt(required(row.amount_low))).toString();
|
|
4766
5794
|
}
|
|
4767
5795
|
function accountingItemFromRow(row) {
|
|
4768
|
-
const usage =
|
|
5796
|
+
const usage = usageFromRow2(row, parseStringArray(row.requester_group_ids));
|
|
4769
5797
|
const cost = row.cost_status === null ? undefined : costFromJoinedRow(row);
|
|
4770
5798
|
const attribution = row.attribution_kind === null ? undefined : row.attribution_kind === "agent" ? {
|
|
4771
5799
|
kind: "agent",
|
|
4772
|
-
agentId: required(row.attribution_agent_id),
|
|
4773
5800
|
agentRunId: required(row.attribution_agent_run_id),
|
|
4774
5801
|
threadId: required(row.attribution_thread_id)
|
|
5802
|
+
} : row.attribution_kind === "subagent" ? {
|
|
5803
|
+
kind: "subagent",
|
|
5804
|
+
subagentRunId: required(row.attribution_agent_run_id),
|
|
5805
|
+
parentRunId: required(row.attribution_parent_run_id)
|
|
4775
5806
|
} : {
|
|
4776
5807
|
kind: "workflowAgent",
|
|
4777
|
-
|
|
5808
|
+
agentStepId: required(row.attribution_agent_step_id),
|
|
4778
5809
|
nodeRunId: required(row.attribution_node_run_id),
|
|
4779
5810
|
workflowId: required(row.attribution_workflow_id),
|
|
4780
5811
|
workflowRunId: required(row.attribution_workflow_run_id)
|
|
@@ -4785,7 +5816,7 @@ function accountingItemFromRow(row) {
|
|
|
4785
5816
|
...cost ? { cost } : {}
|
|
4786
5817
|
};
|
|
4787
5818
|
}
|
|
4788
|
-
function
|
|
5819
|
+
function usageFromRow2(row, requesterGroupIds) {
|
|
4789
5820
|
return {
|
|
4790
5821
|
id: row.id,
|
|
4791
5822
|
projectId: row.project_id,
|
|
@@ -4794,7 +5825,9 @@ function usageFromRow(row, requesterGroupIds) {
|
|
|
4794
5825
|
callId: row.call_id,
|
|
4795
5826
|
requesterGroupIds,
|
|
4796
5827
|
providerId: row.provider_id,
|
|
5828
|
+
...row.provider_ids === null ? {} : { providerIds: normalizeModelProviderIds2(JSON.parse(row.provider_ids)) },
|
|
4797
5829
|
requestedModelId: row.requested_model_id,
|
|
5830
|
+
...row.requested_reasoning === null ? {} : { requestedReasoning: requestedReasoningFromRow(row.requested_reasoning) },
|
|
4798
5831
|
...row.response_model_id === null ? {} : { responseModelId: row.response_model_id },
|
|
4799
5832
|
responseId: row.response_id,
|
|
4800
5833
|
usage: {
|
|
@@ -4843,14 +5876,12 @@ function costFromJoinedRow(row) {
|
|
|
4843
5876
|
}
|
|
4844
5877
|
function costRecord(input) {
|
|
4845
5878
|
const details = parseAiModelCallCostDetails(input.details);
|
|
5879
|
+
const priceSource = details.priceSource === undefined ? undefined : { ...details.priceSource, observedAt: new Date(details.priceSource.observedAt) };
|
|
4846
5880
|
const base = {
|
|
5881
|
+
...details.estimate === undefined ? {} : { estimate: details.estimate },
|
|
4847
5882
|
projectId: input.projectId,
|
|
4848
5883
|
usageRecordId: input.usageId,
|
|
4849
5884
|
pricingContext: details.pricingContext,
|
|
4850
|
-
priceSource: {
|
|
4851
|
-
...details.priceSource,
|
|
4852
|
-
observedAt: new Date(details.priceSource.observedAt)
|
|
4853
|
-
},
|
|
4854
5885
|
ratedAt: new Date(input.ratedAt)
|
|
4855
5886
|
};
|
|
4856
5887
|
if (input.status === "rated") {
|
|
@@ -4858,6 +5889,7 @@ function costRecord(input) {
|
|
|
4858
5889
|
...base,
|
|
4859
5890
|
status: "rated",
|
|
4860
5891
|
billingIdentity: { providerId: required(input.providerId), modelId: required(input.modelId) },
|
|
5892
|
+
priceSource: required(priceSource),
|
|
4861
5893
|
money: { currency: required(input.currency), amountNanos: required(input.amountNanos) },
|
|
4862
5894
|
components: required(details.components)
|
|
4863
5895
|
});
|
|
@@ -4866,6 +5898,7 @@ function costRecord(input) {
|
|
|
4866
5898
|
...base,
|
|
4867
5899
|
status: "unpriceable",
|
|
4868
5900
|
...input.providerId && input.modelId ? { billingIdentity: { providerId: input.providerId, modelId: input.modelId } } : {},
|
|
5901
|
+
...priceSource === undefined ? {} : { priceSource },
|
|
4869
5902
|
reason: required(input.reason),
|
|
4870
5903
|
...details.missingMeters ? { missingMeters: details.missingMeters } : {}
|
|
4871
5904
|
});
|
|
@@ -4943,205 +5976,565 @@ function required(value) {
|
|
|
4943
5976
|
return value;
|
|
4944
5977
|
}
|
|
4945
5978
|
|
|
4946
|
-
// src/ai-
|
|
5979
|
+
// src/ai-limit-storage.ts
|
|
4947
5980
|
import {
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
5981
|
+
aiLimitAmountKey,
|
|
5982
|
+
aiLimitQuantityFromAmount,
|
|
5983
|
+
aiLimitReservationBuckets,
|
|
5984
|
+
aiLimitReservationRequestKey,
|
|
5985
|
+
aiLimitReservationRequestMatches,
|
|
5986
|
+
aiLimitSubjectFromParts,
|
|
5987
|
+
aiLimitSubjectKey,
|
|
5988
|
+
aiLimitSubjectParts,
|
|
5989
|
+
aiLimitSubjectsFromAccountingEntry,
|
|
5990
|
+
assertAiLimitAccountingMatchesReservation,
|
|
5991
|
+
assertNonBlank as assertNonBlank2,
|
|
5992
|
+
cloneValidDate,
|
|
5993
|
+
normalizeAiLimitAmount,
|
|
5994
|
+
normalizeAiLimitQuantities,
|
|
5995
|
+
normalizeAiModelCallReservationIdentity,
|
|
5996
|
+
normalizeCreateAiLimitPolicy,
|
|
5997
|
+
normalizeReserveAiModelCall,
|
|
5998
|
+
normalizeUpdateAiLimitPolicy,
|
|
5999
|
+
resolveAiLimitActual
|
|
6000
|
+
} from "@sixb/core/internal/ai-limit-storage-provider";
|
|
6001
|
+
import { AiLimitStorageError, aiLimitCalendarMonth } from "@sixb/core/storage";
|
|
6002
|
+
class SqliteAiLimitStorage {
|
|
4954
6003
|
connection;
|
|
4955
6004
|
constructor(options = {}) {
|
|
4956
6005
|
this.connection = openSqliteStoreConnection(options);
|
|
4957
|
-
if (this.connection.installFreshSchema)
|
|
6006
|
+
if (this.connection.installFreshSchema)
|
|
4958
6007
|
installFreshSqliteSchema(this.connection.db);
|
|
4959
|
-
}
|
|
4960
6008
|
}
|
|
4961
|
-
async
|
|
4962
|
-
const
|
|
6009
|
+
async createPolicy(input) {
|
|
6010
|
+
const policy = normalizeCreateAiLimitPolicy(input);
|
|
4963
6011
|
return runImmediateTransaction(this.connection.db, () => {
|
|
4964
|
-
const
|
|
4965
|
-
|
|
4966
|
-
return { record: existing, created: false };
|
|
4967
|
-
this.assertExecutionExists(record.projectId, record.executionId);
|
|
6012
|
+
const subject = aiLimitSubjectParts(policy.subject);
|
|
6013
|
+
const limit = normalizeAiLimitAmount(policy.limit);
|
|
4968
6014
|
try {
|
|
4969
|
-
this.
|
|
6015
|
+
this.connection.db.query(`
|
|
6016
|
+
INSERT INTO ai_usage_limits (
|
|
6017
|
+
project_id, id, subject_type, subject_id, meter, currency, limit_amount,
|
|
6018
|
+
period_kind, enabled, created_at, updated_at
|
|
6019
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6020
|
+
`).run(policy.projectId, policy.id, subject.type, subject.id, limit.meter, limit.currency, limit.amount.toString(), policy.period, policy.enabled ? 1 : 0, policy.createdAt.toISOString(), policy.updatedAt.toISOString());
|
|
4970
6021
|
} catch (error) {
|
|
4971
|
-
if (!
|
|
6022
|
+
if (!isConstraintError(error))
|
|
4972
6023
|
throw error;
|
|
4973
|
-
|
|
4974
|
-
if (concurrent)
|
|
4975
|
-
return { record: concurrent, created: false };
|
|
4976
|
-
throw new AiUsageStorageError("duplicate_id", `[SixbSqlite] AI usage record '${record.id}' already exists for project '${record.projectId}'.`);
|
|
6024
|
+
throw duplicatePolicy(policy);
|
|
4977
6025
|
}
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
6026
|
+
return this.requirePolicy(policy.projectId, policy.id);
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
6029
|
+
async updatePolicy(input) {
|
|
6030
|
+
const update = normalizeUpdateAiLimitPolicy(input);
|
|
6031
|
+
return runImmediateTransaction(this.connection.db, () => {
|
|
6032
|
+
const existing = this.requirePolicy(update.projectId, update.id);
|
|
6033
|
+
if (update.limit !== undefined)
|
|
6034
|
+
assertSameDimension(existing.limit, update.limit);
|
|
6035
|
+
const limit = normalizeAiLimitAmount(update.limit ?? existing.limit);
|
|
6036
|
+
this.connection.db.query(`
|
|
6037
|
+
UPDATE ai_usage_limits
|
|
6038
|
+
SET limit_amount = ?, enabled = ?, updated_at = ?
|
|
6039
|
+
WHERE project_id = ? AND id = ?
|
|
6040
|
+
`).run(limit.amount.toString(), update.enabled ?? existing.enabled ? 1 : 0, update.updatedAt.toISOString(), update.projectId, update.id);
|
|
6041
|
+
return this.requirePolicy(update.projectId, update.id);
|
|
6042
|
+
});
|
|
6043
|
+
}
|
|
6044
|
+
async deletePolicy(input) {
|
|
6045
|
+
validatePolicyIdentity(input);
|
|
6046
|
+
return runImmediateTransaction(this.connection.db, () => {
|
|
6047
|
+
const result = this.connection.db.query("DELETE FROM ai_usage_limits WHERE project_id = ? AND id = ?").run(input.projectId, input.id);
|
|
6048
|
+
return result.changes > 0;
|
|
6049
|
+
});
|
|
6050
|
+
}
|
|
6051
|
+
async getPolicy(input) {
|
|
6052
|
+
validatePolicyIdentity(input);
|
|
6053
|
+
const row = this.connection.db.query("SELECT * FROM ai_usage_limits WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
|
|
6054
|
+
return row ? policyFromRow(row) : null;
|
|
6055
|
+
}
|
|
6056
|
+
async listPolicies(input) {
|
|
6057
|
+
assertNonBlank2(input.projectId, "projectId");
|
|
6058
|
+
return this.policyRows(input.projectId, input.includeDisabled ?? false).map(policyFromRow);
|
|
6059
|
+
}
|
|
6060
|
+
async listPolicyStatuses(input) {
|
|
6061
|
+
assertNonBlank2(input.projectId, "projectId");
|
|
6062
|
+
const period = aiLimitCalendarMonth(input.at ?? new Date);
|
|
6063
|
+
const existingGroups = input.existingGroupIds === undefined ? undefined : new Set(input.existingGroupIds);
|
|
6064
|
+
return runImmediateTransaction(this.connection.db, () => this.policyRows(input.projectId, input.includeDisabled ?? false).map((row) => this.statusForPolicy(policyFromRow(row), period, existingGroups)));
|
|
6065
|
+
}
|
|
6066
|
+
async reserveModelCall(input) {
|
|
6067
|
+
const request = normalizeReserveAiModelCall(input);
|
|
6068
|
+
return runImmediateTransaction(this.connection.db, () => {
|
|
6069
|
+
const existing = this.findReservation(request.identity);
|
|
6070
|
+
if (existing) {
|
|
6071
|
+
if (!aiLimitReservationRequestMatches(existing, request)) {
|
|
6072
|
+
throw new AiLimitStorageError("reservation_conflict", `[SixbSqlite] AI model-call reservation '${input.callId}' was replayed with different subjects, estimates, or period.`);
|
|
6073
|
+
}
|
|
6074
|
+
if (existing.state !== "active") {
|
|
6075
|
+
return { status: "terminal", reservation: existing, created: false };
|
|
6076
|
+
}
|
|
6077
|
+
return { status: "reserved", reservation: existing, created: false };
|
|
4984
6078
|
}
|
|
4985
|
-
|
|
6079
|
+
this.assertExecutionExists(request.identity.projectId, request.identity.executionId);
|
|
6080
|
+
const subjectKeys = new Set(request.subjects.map(aiLimitSubjectKey));
|
|
6081
|
+
const estimates = new Map(request.estimates.map((quantity) => {
|
|
6082
|
+
const amount = normalizeAiLimitAmount(quantity);
|
|
6083
|
+
return [aiLimitAmountKey(amount), amount];
|
|
6084
|
+
}));
|
|
6085
|
+
const enabledPolicies = this.policyRows(request.identity.projectId, false).map(policyFromRow);
|
|
6086
|
+
const exhaustedPolicies = [];
|
|
6087
|
+
const unavailablePolicies = [];
|
|
6088
|
+
const unavailableReasons = new Set;
|
|
6089
|
+
for (const policy of enabledPolicies) {
|
|
6090
|
+
if (!subjectKeys.has(aiLimitSubjectKey(policy.subject)))
|
|
6091
|
+
continue;
|
|
6092
|
+
const limit = normalizeAiLimitAmount(policy.limit);
|
|
6093
|
+
const estimate = estimates.get(aiLimitAmountKey(limit));
|
|
6094
|
+
const status = this.statusForPolicy(policy, request.period);
|
|
6095
|
+
if (!estimate || status.accountingStatus === "unavailable") {
|
|
6096
|
+
unavailablePolicies.push(status);
|
|
6097
|
+
if (!estimate)
|
|
6098
|
+
unavailableReasons.add("missingEstimate");
|
|
6099
|
+
if (status.accountingStatus === "unavailable") {
|
|
6100
|
+
unavailableReasons.add("incompleteAccounting");
|
|
6101
|
+
}
|
|
6102
|
+
continue;
|
|
6103
|
+
}
|
|
6104
|
+
const consumption = status.consumption;
|
|
6105
|
+
const consumed = normalizeAiLimitAmount(consumption.actual).amount + normalizeAiLimitAmount(consumption.reserved).amount + normalizeAiLimitAmount(consumption.unknown).amount;
|
|
6106
|
+
if (consumed + estimate.amount > limit.amount)
|
|
6107
|
+
exhaustedPolicies.push(status);
|
|
6108
|
+
}
|
|
6109
|
+
if (unavailablePolicies.length > 0) {
|
|
6110
|
+
unavailablePolicies.sort((left, right) => left.policy.id.localeCompare(right.policy.id));
|
|
6111
|
+
return {
|
|
6112
|
+
status: "unavailable",
|
|
6113
|
+
unavailablePolicies,
|
|
6114
|
+
reasons: [...unavailableReasons].sort()
|
|
6115
|
+
};
|
|
6116
|
+
}
|
|
6117
|
+
if (exhaustedPolicies.length > 0) {
|
|
6118
|
+
exhaustedPolicies.sort((left, right) => left.policy.id.localeCompare(right.policy.id));
|
|
6119
|
+
return {
|
|
6120
|
+
status: "denied",
|
|
6121
|
+
exhaustedPolicies,
|
|
6122
|
+
resetAt: new Date(Math.min(...exhaustedPolicies.map((status) => status.period.resetAt.getTime())))
|
|
6123
|
+
};
|
|
6124
|
+
}
|
|
6125
|
+
const buckets = aiLimitReservationBuckets(enabledPolicies, request.subjects, request.estimates);
|
|
6126
|
+
if (buckets.length === 0)
|
|
6127
|
+
return { status: "notRequired" };
|
|
6128
|
+
for (const bucket of buckets) {
|
|
6129
|
+
const amount = normalizeAiLimitAmount(bucket.estimate);
|
|
6130
|
+
this.changeCounters(request.identity.projectId, bucket.subject, amount, request.period, { reserved: amount.amount }, request.reservedAt);
|
|
6131
|
+
}
|
|
6132
|
+
const reservation = {
|
|
6133
|
+
...request.identity,
|
|
6134
|
+
buckets,
|
|
6135
|
+
requestKey: aiLimitReservationRequestKey(request),
|
|
6136
|
+
period: request.period,
|
|
6137
|
+
state: "active",
|
|
6138
|
+
reservedAt: request.reservedAt,
|
|
6139
|
+
updatedAt: new Date(request.reservedAt)
|
|
6140
|
+
};
|
|
6141
|
+
this.insertReservation(reservation);
|
|
6142
|
+
return { status: "reserved", reservation: structuredClone(reservation), created: true };
|
|
4986
6143
|
});
|
|
4987
6144
|
}
|
|
4988
|
-
async
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
6145
|
+
async recordModelCallActuals(input) {
|
|
6146
|
+
assertNonBlank2(input.projectId, "projectId");
|
|
6147
|
+
assertNonBlank2(input.usageRecordId, "usageRecordId");
|
|
6148
|
+
const recordedAt = cloneValidDate(input.recordedAt ?? new Date, "recordedAt");
|
|
6149
|
+
runImmediateTransaction(this.connection.db, () => {
|
|
6150
|
+
const entry = this.requireAccountingEntry(input.projectId, input.usageRecordId);
|
|
6151
|
+
const period = aiLimitCalendarMonth(entry.occurredAt);
|
|
6152
|
+
for (const subjectValue of aiLimitSubjectsFromAccountingEntry(entry)) {
|
|
6153
|
+
const subject = aiLimitSubjectParts(subjectValue);
|
|
6154
|
+
const rows = this.connection.db.query(`
|
|
6155
|
+
SELECT meter, currency, actual_amount, accounting_status
|
|
6156
|
+
FROM ai_usage_limit_periods
|
|
6157
|
+
WHERE project_id = ? AND subject_type = ? AND subject_id = ?
|
|
6158
|
+
AND period_start = ?
|
|
6159
|
+
`).all(input.projectId, subject.type, subject.id, period.start.toISOString());
|
|
6160
|
+
for (const row of rows) {
|
|
6161
|
+
const actual = resolveAiLimitActual([entry], row);
|
|
6162
|
+
this.connection.db.query(`
|
|
6163
|
+
UPDATE ai_usage_limit_periods
|
|
6164
|
+
SET actual_amount = ?, accounting_status = ?, updated_at = ?
|
|
6165
|
+
WHERE project_id = ? AND subject_type = ? AND subject_id = ?
|
|
6166
|
+
AND meter = ? AND currency = ? AND period_start = ?
|
|
6167
|
+
`).run((BigInt(row.actual_amount) + actual.amount).toString(), row.accounting_status === "unavailable" || actual.accountingStatus === "unavailable" ? "unavailable" : "complete", recordedAt.toISOString(), input.projectId, subject.type, subject.id, row.meter, row.currency, period.start.toISOString());
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
4992
6170
|
});
|
|
4993
|
-
return summary;
|
|
4994
6171
|
}
|
|
4995
|
-
async
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
const
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
6172
|
+
async reconcileModelCall(input) {
|
|
6173
|
+
const identity = normalizeAiModelCallReservationIdentity(input);
|
|
6174
|
+
assertNonBlank2(input.usageRecordId, "usageRecordId");
|
|
6175
|
+
const reconciledAt = cloneValidDate(input.reconciledAt ?? new Date, "reconciledAt");
|
|
6176
|
+
return runImmediateTransaction(this.connection.db, () => {
|
|
6177
|
+
const reservation = this.findReservation(identity);
|
|
6178
|
+
if (!reservation)
|
|
6179
|
+
throw missingReservation(identity);
|
|
6180
|
+
if (reservation.state === "reconciled") {
|
|
6181
|
+
if (reservation.usageRecordId === input.usageRecordId) {
|
|
6182
|
+
return reservation;
|
|
6183
|
+
}
|
|
6184
|
+
throw reconciliationConflict(identity.callId);
|
|
6185
|
+
}
|
|
6186
|
+
const accounting = this.requireAccountingEntry(identity.projectId, input.usageRecordId);
|
|
6187
|
+
assertAiLimitAccountingMatchesReservation(reservation, accounting);
|
|
6188
|
+
this.reconcileEstimateCounters(reservation, accounting, reconciledAt);
|
|
6189
|
+
const reconciled = {
|
|
6190
|
+
...reservation,
|
|
6191
|
+
state: "reconciled",
|
|
6192
|
+
usageRecordId: input.usageRecordId,
|
|
6193
|
+
updatedAt: reconciledAt
|
|
6194
|
+
};
|
|
6195
|
+
this.updateReservation(reconciled);
|
|
6196
|
+
return structuredClone(reconciled);
|
|
6197
|
+
});
|
|
5005
6198
|
}
|
|
5006
|
-
async
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
6199
|
+
async markReservationUnknown(input) {
|
|
6200
|
+
const identity = normalizeAiModelCallReservationIdentity(input);
|
|
6201
|
+
const markedAt = cloneValidDate(input.markedAt ?? new Date, "markedAt");
|
|
6202
|
+
return runImmediateTransaction(this.connection.db, () => {
|
|
6203
|
+
const reservation = this.findReservation(identity);
|
|
6204
|
+
if (!reservation)
|
|
6205
|
+
throw missingReservation(identity);
|
|
6206
|
+
if (reservation.state === "unknown")
|
|
6207
|
+
return reservation;
|
|
6208
|
+
if (reservation.state !== "active")
|
|
6209
|
+
throw invalidState(reservation, "mark unknown");
|
|
6210
|
+
this.moveEstimateCounters(reservation, { reserved: -1n, unknown: 1n }, markedAt);
|
|
6211
|
+
const unknown = { ...reservation, state: "unknown", updatedAt: markedAt };
|
|
6212
|
+
this.updateReservation(unknown);
|
|
6213
|
+
return structuredClone(unknown);
|
|
6214
|
+
});
|
|
5014
6215
|
}
|
|
5015
6216
|
close() {
|
|
5016
6217
|
closeSqliteStoreConnection(this.connection);
|
|
5017
6218
|
}
|
|
5018
|
-
|
|
6219
|
+
policyRows(projectId, includeDisabled) {
|
|
6220
|
+
return this.connection.db.query(`
|
|
6221
|
+
SELECT * FROM ai_usage_limits
|
|
6222
|
+
WHERE project_id = ? AND (? = 1 OR enabled = 1)
|
|
6223
|
+
ORDER BY id
|
|
6224
|
+
`).all(projectId, includeDisabled ? 1 : 0);
|
|
6225
|
+
}
|
|
6226
|
+
requirePolicy(projectId, id) {
|
|
6227
|
+
const row = this.connection.db.query("SELECT * FROM ai_usage_limits WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
6228
|
+
if (row)
|
|
6229
|
+
return policyFromRow(row);
|
|
6230
|
+
throw new AiLimitStorageError("missing_policy", `[SixbSqlite] AI limit policy '${id}' does not exist in project '${projectId}'.`);
|
|
6231
|
+
}
|
|
6232
|
+
statusForPolicy(policy, period, existingGroups) {
|
|
6233
|
+
const subject = aiLimitSubjectParts(policy.subject);
|
|
6234
|
+
const limit = normalizeAiLimitAmount(policy.limit);
|
|
6235
|
+
let row = this.connection.db.query(`
|
|
6236
|
+
SELECT actual_amount, reserved_amount, unknown_amount, accounting_status
|
|
6237
|
+
FROM ai_usage_limit_periods
|
|
6238
|
+
WHERE project_id = ? AND subject_type = ? AND subject_id = ?
|
|
6239
|
+
AND meter = ? AND currency = ? AND period_start = ?
|
|
6240
|
+
`).get(policy.projectId, subject.type, subject.id, limit.meter, limit.currency, period.start.toISOString());
|
|
6241
|
+
if (!row || row.accounting_status === "unavailable") {
|
|
6242
|
+
const actual = resolveAiLimitActual(this.accountingEntries(policy, period), limit);
|
|
6243
|
+
row = {
|
|
6244
|
+
actual_amount: actual.amount.toString(),
|
|
6245
|
+
reserved_amount: row?.reserved_amount ?? "0",
|
|
6246
|
+
unknown_amount: row?.unknown_amount ?? "0",
|
|
6247
|
+
accounting_status: actual.accountingStatus
|
|
6248
|
+
};
|
|
6249
|
+
this.connection.db.query(`
|
|
6250
|
+
INSERT INTO ai_usage_limit_periods (
|
|
6251
|
+
project_id, subject_type, subject_id, meter, currency, period_start, period_end,
|
|
6252
|
+
actual_amount, reserved_amount, unknown_amount, accounting_status, updated_at
|
|
6253
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, '0', '0', ?, ?)
|
|
6254
|
+
ON CONFLICT (project_id, subject_type, subject_id, meter, currency, period_start)
|
|
6255
|
+
DO UPDATE SET
|
|
6256
|
+
actual_amount = excluded.actual_amount,
|
|
6257
|
+
accounting_status = excluded.accounting_status,
|
|
6258
|
+
updated_at = excluded.updated_at
|
|
6259
|
+
`).run(policy.projectId, subject.type, subject.id, limit.meter, limit.currency, period.start.toISOString(), period.end.toISOString(), row.actual_amount, row.accounting_status, new Date().toISOString());
|
|
6260
|
+
}
|
|
6261
|
+
return policyStatus(policy, period, row, existingGroups);
|
|
6262
|
+
}
|
|
6263
|
+
changeCounters(projectId, subjectValue, amount, period, changes, updatedAt) {
|
|
6264
|
+
const subject = aiLimitSubjectParts(subjectValue);
|
|
6265
|
+
const row = this.connection.db.query(`
|
|
6266
|
+
SELECT actual_amount, reserved_amount, unknown_amount, accounting_status
|
|
6267
|
+
FROM ai_usage_limit_periods
|
|
6268
|
+
WHERE project_id = ? AND subject_type = ? AND subject_id = ?
|
|
6269
|
+
AND meter = ? AND currency = ? AND period_start = ?
|
|
6270
|
+
`).get(projectId, subject.type, subject.id, amount.meter, amount.currency, period.start.toISOString());
|
|
6271
|
+
const reserved = BigInt(row?.reserved_amount ?? "0") + (changes.reserved ?? 0n);
|
|
6272
|
+
const unknown = BigInt(row?.unknown_amount ?? "0") + (changes.unknown ?? 0n);
|
|
6273
|
+
if (reserved < 0n || unknown < 0n) {
|
|
6274
|
+
throw new Error("[SixbSqlite] AI limit period counter became negative.");
|
|
6275
|
+
}
|
|
6276
|
+
this.connection.db.query(`
|
|
6277
|
+
INSERT INTO ai_usage_limit_periods (
|
|
6278
|
+
project_id, subject_type, subject_id, meter, currency, period_start, period_end,
|
|
6279
|
+
actual_amount, reserved_amount, unknown_amount, accounting_status, updated_at
|
|
6280
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6281
|
+
ON CONFLICT (project_id, subject_type, subject_id, meter, currency, period_start)
|
|
6282
|
+
DO UPDATE SET
|
|
6283
|
+
period_end = excluded.period_end,
|
|
6284
|
+
actual_amount = excluded.actual_amount,
|
|
6285
|
+
reserved_amount = excluded.reserved_amount,
|
|
6286
|
+
unknown_amount = excluded.unknown_amount,
|
|
6287
|
+
accounting_status = excluded.accounting_status,
|
|
6288
|
+
updated_at = excluded.updated_at
|
|
6289
|
+
`).run(projectId, subject.type, subject.id, amount.meter, amount.currency, period.start.toISOString(), period.end.toISOString(), row?.actual_amount ?? "0", reserved.toString(), unknown.toString(), row?.accounting_status ?? "complete", updatedAt.toISOString());
|
|
6290
|
+
}
|
|
6291
|
+
moveEstimateCounters(reservation, directions, updatedAt) {
|
|
6292
|
+
for (const bucket of reservation.buckets) {
|
|
6293
|
+
const amount = normalizeAiLimitAmount(bucket.estimate);
|
|
6294
|
+
this.changeCounters(reservation.projectId, bucket.subject, amount, reservation.period, {
|
|
6295
|
+
...directions.reserved === undefined ? {} : { reserved: directions.reserved * amount.amount },
|
|
6296
|
+
...directions.unknown === undefined ? {} : { unknown: directions.unknown * amount.amount }
|
|
6297
|
+
}, updatedAt);
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
reconcileEstimateCounters(reservation, entry, updatedAt) {
|
|
6301
|
+
for (const bucket of reservation.buckets) {
|
|
6302
|
+
const amount = normalizeAiLimitAmount(bucket.estimate);
|
|
6303
|
+
const actual = resolveAiLimitActual([entry], amount);
|
|
6304
|
+
if (actual.accountingStatus === "complete") {
|
|
6305
|
+
this.changeCounters(reservation.projectId, bucket.subject, amount, reservation.period, reservation.state === "active" ? { reserved: -amount.amount } : { unknown: -amount.amount }, updatedAt);
|
|
6306
|
+
} else if (reservation.state === "active") {
|
|
6307
|
+
this.changeCounters(reservation.projectId, bucket.subject, amount, reservation.period, { reserved: -amount.amount, unknown: amount.amount }, updatedAt);
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
insertReservation(reservation) {
|
|
6312
|
+
this.connection.db.query(`
|
|
6313
|
+
INSERT INTO ai_model_call_reservations (
|
|
6314
|
+
project_id, execution_id, attempt, call_id, buckets, request_key,
|
|
6315
|
+
period_start, period_end, state, usage_record_id, reserved_at, updated_at
|
|
6316
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6317
|
+
`).run(...reservationSqlValues(reservation));
|
|
6318
|
+
}
|
|
6319
|
+
updateReservation(reservation) {
|
|
5019
6320
|
this.connection.db.query(`
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
attempt,
|
|
5025
|
-
call_id,
|
|
5026
|
-
provider_id,
|
|
5027
|
-
requested_model_id,
|
|
5028
|
-
response_model_id,
|
|
5029
|
-
response_id,
|
|
5030
|
-
input_tokens,
|
|
5031
|
-
output_tokens,
|
|
5032
|
-
total_tokens,
|
|
5033
|
-
uncached_input_tokens,
|
|
5034
|
-
cache_read_input_tokens,
|
|
5035
|
-
cache_write_input_tokens,
|
|
5036
|
-
text_output_tokens,
|
|
5037
|
-
reasoning_output_tokens,
|
|
5038
|
-
reporting_status,
|
|
5039
|
-
raw_usage,
|
|
5040
|
-
occurred_at,
|
|
5041
|
-
recorded_at
|
|
5042
|
-
) VALUES (
|
|
5043
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
5044
|
-
)
|
|
5045
|
-
`).run(record.projectId, record.id, record.executionId, record.attempt, record.callId, record.providerId, record.requestedModelId, record.responseModelId ?? null, record.responseId, record.usage.inputTokens ?? null, record.usage.outputTokens ?? null, record.usage.totalTokens ?? null, record.usage.uncachedInputTokens ?? null, record.usage.cacheReadInputTokens ?? null, record.usage.cacheWriteInputTokens ?? null, record.usage.textOutputTokens ?? null, record.usage.reasoningOutputTokens ?? null, record.usage.reportingStatus, record.rawUsage === undefined ? null : JSON.stringify(record.rawUsage), record.occurredAt.toISOString(), record.recordedAt.toISOString());
|
|
6321
|
+
UPDATE ai_model_call_reservations
|
|
6322
|
+
SET state = ?, usage_record_id = ?, updated_at = ?
|
|
6323
|
+
WHERE project_id = ? AND execution_id = ? AND attempt = ? AND call_id = ?
|
|
6324
|
+
`).run(reservation.state, reservation.usageRecordId ?? null, reservation.updatedAt.toISOString(), reservation.projectId, reservation.executionId, reservation.attempt, reservation.callId);
|
|
5046
6325
|
}
|
|
5047
|
-
|
|
6326
|
+
findReservation(input) {
|
|
5048
6327
|
const row = this.connection.db.query(`
|
|
5049
|
-
SELECT * FROM
|
|
5050
|
-
WHERE project_id = ?
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
AND call_id = ?
|
|
5054
|
-
AND response_id = ?
|
|
5055
|
-
`).get(record.projectId, record.executionId, record.attempt, record.callId, record.responseId);
|
|
5056
|
-
return row ? this.rowToRecord(row) : null;
|
|
5057
|
-
}
|
|
5058
|
-
requireById(projectId, id) {
|
|
5059
|
-
const row = this.connection.db.query("SELECT * FROM ai_model_call_usage WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
5060
|
-
if (!row)
|
|
5061
|
-
throw new Error(`[SixbSqlite] AI usage record '${id}' disappeared after insert.`);
|
|
5062
|
-
return this.rowToRecord(row);
|
|
6328
|
+
SELECT * FROM ai_model_call_reservations
|
|
6329
|
+
WHERE project_id = ? AND execution_id = ? AND attempt = ? AND call_id = ?
|
|
6330
|
+
`).get(input.projectId, input.executionId, input.attempt, input.callId);
|
|
6331
|
+
return row ? reservationFromRow(row) : null;
|
|
5063
6332
|
}
|
|
5064
|
-
|
|
5065
|
-
const
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
6333
|
+
accountingEntries(policy, period) {
|
|
6334
|
+
const subject = aiLimitSubjectParts(policy.subject);
|
|
6335
|
+
const rows = this.connection.db.query(`
|
|
6336
|
+
SELECT usage.project_id, usage.id AS usage_record_id, usage.execution_id,
|
|
6337
|
+
usage.attempt, usage.call_id, usage.occurred_at,
|
|
6338
|
+
CAST(usage.total_tokens AS TEXT) AS total_tokens,
|
|
6339
|
+
executions.requested_by_user_id,
|
|
6340
|
+
executions.requested_by_service_account_id,
|
|
6341
|
+
valuations.status AS valuation_status,
|
|
6342
|
+
valuations.currency AS valuation_currency,
|
|
6343
|
+
CAST(valuations.amount_nanos AS TEXT) AS valuation_amount_nanos
|
|
6344
|
+
FROM ai_model_call_usage AS usage
|
|
6345
|
+
JOIN executions
|
|
6346
|
+
ON executions.project_id = usage.project_id AND executions.id = usage.execution_id
|
|
6347
|
+
LEFT JOIN ai_model_call_valuations AS valuations
|
|
6348
|
+
ON valuations.project_id = usage.project_id
|
|
6349
|
+
AND valuations.usage_record_id = usage.id
|
|
6350
|
+
WHERE usage.project_id = ? AND usage.occurred_at >= ? AND usage.occurred_at < ?
|
|
6351
|
+
AND (
|
|
6352
|
+
? = 'project'
|
|
6353
|
+
OR (
|
|
6354
|
+
? = 'group'
|
|
6355
|
+
AND EXISTS (
|
|
6356
|
+
SELECT 1 FROM ai_model_call_usage_groups AS usage_groups
|
|
6357
|
+
WHERE usage_groups.project_id = usage.project_id
|
|
6358
|
+
AND usage_groups.usage_record_id = usage.id
|
|
6359
|
+
AND usage_groups.group_id = ?
|
|
6360
|
+
)
|
|
6361
|
+
)
|
|
6362
|
+
OR (? = 'user' AND executions.requested_by_user_id = ?)
|
|
6363
|
+
OR (
|
|
6364
|
+
? = 'serviceAccount'
|
|
6365
|
+
AND executions.requested_by_service_account_id = ?
|
|
6366
|
+
)
|
|
6367
|
+
)
|
|
6368
|
+
ORDER BY usage.occurred_at, usage.id
|
|
6369
|
+
`).all(policy.projectId, period.start.toISOString(), period.end.toISOString(), subject.type, subject.type, subject.id, subject.type, subject.id, subject.type, subject.id);
|
|
6370
|
+
return rows.map((row) => accountingEntryFromRow(row, policy.subject));
|
|
5090
6371
|
}
|
|
5091
|
-
|
|
5092
|
-
const
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
6372
|
+
requireAccountingEntry(projectId, usageRecordId) {
|
|
6373
|
+
const row = this.connection.db.query(`
|
|
6374
|
+
SELECT usage.project_id, usage.id AS usage_record_id, usage.execution_id,
|
|
6375
|
+
usage.attempt, usage.call_id, usage.occurred_at,
|
|
6376
|
+
CAST(usage.total_tokens AS TEXT) AS total_tokens,
|
|
6377
|
+
executions.requested_by_user_id,
|
|
6378
|
+
executions.requested_by_service_account_id,
|
|
6379
|
+
valuations.status AS valuation_status,
|
|
6380
|
+
valuations.currency AS valuation_currency,
|
|
6381
|
+
CAST(valuations.amount_nanos AS TEXT) AS valuation_amount_nanos
|
|
6382
|
+
FROM ai_model_call_usage AS usage
|
|
6383
|
+
JOIN executions
|
|
6384
|
+
ON executions.project_id = usage.project_id AND executions.id = usage.execution_id
|
|
6385
|
+
LEFT JOIN ai_model_call_valuations AS valuations
|
|
6386
|
+
ON valuations.project_id = usage.project_id
|
|
6387
|
+
AND valuations.usage_record_id = usage.id
|
|
6388
|
+
WHERE usage.project_id = ? AND usage.id = ?
|
|
6389
|
+
`).get(projectId, usageRecordId);
|
|
6390
|
+
if (row) {
|
|
6391
|
+
const groups = this.connection.db.query(`
|
|
6392
|
+
SELECT group_id FROM ai_model_call_usage_groups
|
|
6393
|
+
WHERE project_id = ? AND usage_record_id = ?
|
|
6394
|
+
ORDER BY group_id
|
|
6395
|
+
`).all(projectId, usageRecordId);
|
|
6396
|
+
return accountingEntryFromRow(row, undefined, groups.map((group) => group.group_id));
|
|
6397
|
+
}
|
|
6398
|
+
throw new AiLimitStorageError("missing_usage_record", `[SixbSqlite] AI usage record '${usageRecordId}' does not exist in project '${projectId}'.`);
|
|
5103
6399
|
}
|
|
5104
6400
|
assertExecutionExists(projectId, executionId) {
|
|
5105
|
-
const
|
|
5106
|
-
if (
|
|
6401
|
+
const row = this.connection.db.query("SELECT 1 FROM executions WHERE project_id = ? AND id = ?").get(projectId, executionId);
|
|
6402
|
+
if (row)
|
|
5107
6403
|
return;
|
|
5108
|
-
throw new
|
|
6404
|
+
throw new AiLimitStorageError("missing_execution", `[SixbSqlite] AI limit execution '${executionId}' does not exist in project '${projectId}'.`);
|
|
5109
6405
|
}
|
|
5110
6406
|
}
|
|
5111
|
-
function
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
6407
|
+
function policyFromRow(row) {
|
|
6408
|
+
return {
|
|
6409
|
+
id: row.id,
|
|
6410
|
+
projectId: row.project_id,
|
|
6411
|
+
subject: aiLimitSubjectFromParts(row.subject_type, row.subject_id),
|
|
6412
|
+
limit: aiLimitQuantityFromAmount({
|
|
6413
|
+
meter: row.meter,
|
|
6414
|
+
currency: row.currency,
|
|
6415
|
+
amount: BigInt(row.limit_amount)
|
|
6416
|
+
}),
|
|
6417
|
+
period: row.period_kind,
|
|
6418
|
+
enabled: row.enabled === 1,
|
|
6419
|
+
createdAt: new Date(row.created_at),
|
|
6420
|
+
updatedAt: new Date(row.updated_at)
|
|
6421
|
+
};
|
|
5125
6422
|
}
|
|
5126
|
-
function
|
|
6423
|
+
function policyStatus(policy, period, row, existingGroups) {
|
|
6424
|
+
const limit = normalizeAiLimitAmount(policy.limit);
|
|
6425
|
+
const actual = BigInt(row.actual_amount);
|
|
6426
|
+
const reserved = BigInt(row.reserved_amount);
|
|
6427
|
+
const unknown = BigInt(row.unknown_amount);
|
|
6428
|
+
const consumed = actual + reserved + unknown;
|
|
6429
|
+
const quantity = (amount) => aiLimitQuantityFromAmount({ ...limit, amount });
|
|
5127
6430
|
return {
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
6431
|
+
policy,
|
|
6432
|
+
period: structuredClone(period),
|
|
6433
|
+
consumption: {
|
|
6434
|
+
actual: quantity(actual),
|
|
6435
|
+
reserved: quantity(reserved),
|
|
6436
|
+
unknown: quantity(unknown),
|
|
6437
|
+
remaining: quantity(limit.amount > consumed ? limit.amount - consumed : 0n)
|
|
6438
|
+
},
|
|
6439
|
+
accountingStatus: row.accounting_status,
|
|
6440
|
+
exhausted: consumed >= limit.amount,
|
|
6441
|
+
orphaned: policy.subject.type === "group" && existingGroups !== undefined && !existingGroups.has(policy.subject.id)
|
|
5135
6442
|
};
|
|
5136
6443
|
}
|
|
5137
|
-
function
|
|
5138
|
-
|
|
5139
|
-
|
|
6444
|
+
function accountingEntryFromRow(row, subject, requesterGroupIds = subject?.type === "group" ? [subject.id] : []) {
|
|
6445
|
+
const totalTokens = row.total_tokens === null ? undefined : Number(row.total_tokens);
|
|
6446
|
+
const requester = row.requested_by_user_id ? { type: "user", id: row.requested_by_user_id } : row.requested_by_service_account_id ? { type: "serviceAccount", id: row.requested_by_service_account_id } : undefined;
|
|
6447
|
+
return {
|
|
6448
|
+
projectId: row.project_id,
|
|
6449
|
+
usageRecordId: row.usage_record_id,
|
|
6450
|
+
executionId: row.execution_id,
|
|
6451
|
+
attempt: row.attempt,
|
|
6452
|
+
callId: row.call_id,
|
|
6453
|
+
occurredAt: new Date(row.occurred_at),
|
|
6454
|
+
...totalTokens !== undefined && Number.isSafeInteger(totalTokens) ? { totalTokens } : {},
|
|
6455
|
+
requesterGroupIds,
|
|
6456
|
+
...requester ? { requester } : {},
|
|
6457
|
+
...row.valuation_status === "rated" && row.valuation_currency !== null && row.valuation_amount_nanos !== null ? {
|
|
6458
|
+
valuation: {
|
|
6459
|
+
status: "rated",
|
|
6460
|
+
money: {
|
|
6461
|
+
currency: row.valuation_currency,
|
|
6462
|
+
amountNanos: row.valuation_amount_nanos
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6465
|
+
} : row.valuation_status === "unpriceable" ? { valuation: { status: "unavailable" } } : {}
|
|
6466
|
+
};
|
|
6467
|
+
}
|
|
6468
|
+
function reservationFromRow(row) {
|
|
6469
|
+
return {
|
|
6470
|
+
projectId: row.project_id,
|
|
6471
|
+
executionId: row.execution_id,
|
|
6472
|
+
attempt: row.attempt,
|
|
6473
|
+
callId: row.call_id,
|
|
6474
|
+
buckets: normalizeBucketsJson(row.buckets),
|
|
6475
|
+
requestKey: row.request_key,
|
|
6476
|
+
period: {
|
|
6477
|
+
kind: "calendarMonth",
|
|
6478
|
+
start: new Date(row.period_start),
|
|
6479
|
+
end: new Date(row.period_end),
|
|
6480
|
+
resetAt: new Date(row.period_end)
|
|
6481
|
+
},
|
|
6482
|
+
state: row.state,
|
|
6483
|
+
...row.usage_record_id === null ? {} : { usageRecordId: row.usage_record_id },
|
|
6484
|
+
reservedAt: new Date(row.reserved_at),
|
|
6485
|
+
updatedAt: new Date(row.updated_at)
|
|
6486
|
+
};
|
|
6487
|
+
}
|
|
6488
|
+
function normalizeBucketsJson(value) {
|
|
6489
|
+
const buckets = JSON.parse(value);
|
|
6490
|
+
return buckets.map((bucket) => ({
|
|
6491
|
+
subject: structuredClone(bucket.subject),
|
|
6492
|
+
estimate: normalizeAiLimitQuantities([bucket.estimate], "stored bucket")[0]
|
|
6493
|
+
}));
|
|
6494
|
+
}
|
|
6495
|
+
function reservationSqlValues(reservation) {
|
|
6496
|
+
return [
|
|
6497
|
+
reservation.projectId,
|
|
6498
|
+
reservation.executionId,
|
|
6499
|
+
reservation.attempt,
|
|
6500
|
+
reservation.callId,
|
|
6501
|
+
JSON.stringify(reservation.buckets),
|
|
6502
|
+
reservation.requestKey,
|
|
6503
|
+
reservation.period.start.toISOString(),
|
|
6504
|
+
reservation.period.end.toISOString(),
|
|
6505
|
+
reservation.state,
|
|
6506
|
+
reservation.usageRecordId ?? null,
|
|
6507
|
+
reservation.reservedAt.toISOString(),
|
|
6508
|
+
reservation.updatedAt.toISOString()
|
|
6509
|
+
];
|
|
6510
|
+
}
|
|
6511
|
+
function assertSameDimension(previous, next) {
|
|
6512
|
+
if (aiLimitAmountKey(normalizeAiLimitAmount(previous)) !== aiLimitAmountKey(normalizeAiLimitAmount(next))) {
|
|
6513
|
+
throw new TypeError("[Sixb] AI limit policy meter and cost currency are immutable.");
|
|
5140
6514
|
}
|
|
5141
6515
|
}
|
|
6516
|
+
function validatePolicyIdentity(input) {
|
|
6517
|
+
assertNonBlank2(input.projectId, "projectId");
|
|
6518
|
+
assertNonBlank2(input.id, "policy id");
|
|
6519
|
+
}
|
|
6520
|
+
function duplicatePolicy(policy) {
|
|
6521
|
+
return new AiLimitStorageError("duplicate_policy", `[SixbSqlite] An AI limit policy already exists for policy ID or subject-meter dimension '${policy.id}'.`);
|
|
6522
|
+
}
|
|
6523
|
+
function missingReservation(input) {
|
|
6524
|
+
return new AiLimitStorageError("missing_reservation", `[SixbSqlite] AI model-call reservation '${input.callId}' does not exist for execution '${input.executionId}' attempt ${input.attempt}.`);
|
|
6525
|
+
}
|
|
6526
|
+
function reconciliationConflict(callId) {
|
|
6527
|
+
return new AiLimitStorageError("reconciliation_conflict", `[SixbSqlite] AI model-call reservation '${callId}' was reconciled with a different usage record.`);
|
|
6528
|
+
}
|
|
6529
|
+
function invalidState(reservation, operation) {
|
|
6530
|
+
return new AiLimitStorageError("invalid_reservation_state", `[SixbSqlite] Cannot ${operation} AI model-call reservation '${reservation.callId}' in state '${reservation.state}'.`);
|
|
6531
|
+
}
|
|
6532
|
+
function isConstraintError(error) {
|
|
6533
|
+
return error instanceof Error && /constraint/i.test(error.message);
|
|
6534
|
+
}
|
|
5142
6535
|
|
|
5143
6536
|
// src/auth-storage/auth-storage.ts
|
|
5144
|
-
import { AuthStorageError as
|
|
6537
|
+
import { AuthStorageError as AuthStorageError11 } from "@sixb/core/storage";
|
|
5145
6538
|
|
|
5146
6539
|
// src/auth-storage/access-tokens.ts
|
|
5147
6540
|
import { AuthStorageError as AuthStorageError2 } from "@sixb/core/storage";
|
|
@@ -5293,6 +6686,25 @@ function rowToOidcAuthorizationAttemptRecord(row) {
|
|
|
5293
6686
|
consumedAt: row.consumed_at ? new Date(row.consumed_at) : undefined
|
|
5294
6687
|
};
|
|
5295
6688
|
}
|
|
6689
|
+
function rowToDeviceAuthorizationRecord(row) {
|
|
6690
|
+
return {
|
|
6691
|
+
id: row.id,
|
|
6692
|
+
projectId: row.project_id,
|
|
6693
|
+
deviceCodeHash: row.device_code_hash,
|
|
6694
|
+
userCode: row.user_code,
|
|
6695
|
+
clientName: row.client_name,
|
|
6696
|
+
tokenName: row.token_name,
|
|
6697
|
+
tokenExpiresAt: new Date(row.token_expires_at),
|
|
6698
|
+
status: row.status,
|
|
6699
|
+
approvedUserId: row.approved_user_id ?? undefined,
|
|
6700
|
+
approvedSessionId: row.approved_session_id ?? undefined,
|
|
6701
|
+
createdAt: new Date(row.created_at),
|
|
6702
|
+
expiresAt: new Date(row.expires_at),
|
|
6703
|
+
approvedAt: row.approved_at ? new Date(row.approved_at) : undefined,
|
|
6704
|
+
deniedAt: row.denied_at ? new Date(row.denied_at) : undefined,
|
|
6705
|
+
consumedAt: row.consumed_at ? new Date(row.consumed_at) : undefined
|
|
6706
|
+
};
|
|
6707
|
+
}
|
|
5296
6708
|
function serializeOptionalRecord(value) {
|
|
5297
6709
|
return value ? JSON.stringify(value) : null;
|
|
5298
6710
|
}
|
|
@@ -5659,22 +7071,17 @@ function invitationRowToRecord(db, row) {
|
|
|
5659
7071
|
}
|
|
5660
7072
|
|
|
5661
7073
|
// src/auth-storage/access-tokens.ts
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
assertKindMatchesSubject(input);
|
|
5674
|
-
this.assertSubjectExists(projectId, input.subjectType, subjectId);
|
|
5675
|
-
const groupIds = input.groupIds === undefined ? undefined : normalizeGroupIds(input.groupIds);
|
|
5676
|
-
try {
|
|
5677
|
-
this.db.query(`
|
|
7074
|
+
function createAuthAccessToken(db, input) {
|
|
7075
|
+
const id = assertNonEmpty(input.id, "Access token id");
|
|
7076
|
+
const projectId = assertNonEmpty(input.projectId, "Project id");
|
|
7077
|
+
const name = assertNonEmpty(input.name, "Access token name");
|
|
7078
|
+
const subjectId = assertNonEmpty(input.subjectId, "Access token subject id");
|
|
7079
|
+
const tokenHash = assertNonEmpty(input.tokenHash, "Access token hash");
|
|
7080
|
+
assertKindMatchesSubject(input);
|
|
7081
|
+
assertSubjectExists(db, projectId, input.subjectType, subjectId);
|
|
7082
|
+
const groupIds = input.groupIds === undefined ? undefined : normalizeGroupIds(input.groupIds);
|
|
7083
|
+
try {
|
|
7084
|
+
db.query(`
|
|
5678
7085
|
INSERT INTO auth_access_tokens (
|
|
5679
7086
|
project_id,
|
|
5680
7087
|
id,
|
|
@@ -5691,14 +7098,24 @@ class SqliteAuthAccessTokenStore {
|
|
|
5691
7098
|
expires_at
|
|
5692
7099
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5693
7100
|
`).run(projectId, id, name, input.kind, input.subjectType, subjectId, tokenHash, serializeOptionalStringArray(groupIds), input.createdByPrincipal?.type ?? null, input.createdByPrincipal?.id ?? null, input.createdBySessionId ?? null, toIso(input.createdAt), toIso(input.expiresAt));
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
}
|
|
5701
|
-
|
|
7101
|
+
} catch (error) {
|
|
7102
|
+
mapUniqueConstraintError(error, "duplicate_access_token", `[Sixb] Access token '${id}' already exists for project '${projectId}'.`);
|
|
7103
|
+
}
|
|
7104
|
+
const row = db.query("SELECT * FROM auth_access_tokens WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
7105
|
+
const token = row ? rowToAccessTokenRecord(row) : null;
|
|
7106
|
+
if (!token) {
|
|
7107
|
+
throw new AuthStorageError2("missing_access_token", `[Sixb] Access token '${id}' not found for project '${projectId}'.`);
|
|
7108
|
+
}
|
|
7109
|
+
return token;
|
|
7110
|
+
}
|
|
7111
|
+
|
|
7112
|
+
class SqliteAuthAccessTokenStore {
|
|
7113
|
+
db;
|
|
7114
|
+
constructor(db) {
|
|
7115
|
+
this.db = db;
|
|
7116
|
+
}
|
|
7117
|
+
async create(input) {
|
|
7118
|
+
return createAuthAccessToken(this.db, input);
|
|
5702
7119
|
}
|
|
5703
7120
|
async getById(params) {
|
|
5704
7121
|
const row = this.db.query("SELECT * FROM auth_access_tokens WHERE project_id = ? AND id = ?").get(params.projectId, params.id);
|
|
@@ -5787,18 +7204,18 @@ class SqliteAuthAccessTokenStore {
|
|
|
5787
7204
|
lastUsedIpAddress
|
|
5788
7205
|
};
|
|
5789
7206
|
}
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
throw new AuthStorageError2("missing_user", `[Sixb] User '${subjectId}' not found for project '${projectId}'.`);
|
|
5796
|
-
}
|
|
5797
|
-
const row = this.db.query("SELECT * FROM auth_service_accounts WHERE project_id = ? AND id = ?").get(projectId, subjectId);
|
|
5798
|
-
if (row)
|
|
7207
|
+
}
|
|
7208
|
+
function assertSubjectExists(db, projectId, subjectType, subjectId) {
|
|
7209
|
+
if (subjectType === "user") {
|
|
7210
|
+
const row2 = db.query("SELECT * FROM auth_users WHERE project_id = ? AND id = ?").get(projectId, subjectId);
|
|
7211
|
+
if (row2)
|
|
5799
7212
|
return;
|
|
5800
|
-
throw new AuthStorageError2("
|
|
7213
|
+
throw new AuthStorageError2("missing_user", `[Sixb] User '${subjectId}' not found for project '${projectId}'.`);
|
|
5801
7214
|
}
|
|
7215
|
+
const row = db.query("SELECT * FROM auth_service_accounts WHERE project_id = ? AND id = ?").get(projectId, subjectId);
|
|
7216
|
+
if (row)
|
|
7217
|
+
return;
|
|
7218
|
+
throw new AuthStorageError2("missing_service_account", `[Sixb] Service account '${subjectId}' not found for project '${projectId}'.`);
|
|
5802
7219
|
}
|
|
5803
7220
|
function assertKindMatchesSubject(input) {
|
|
5804
7221
|
if (input.kind === "personal" && input.subjectType === "user" || input.kind === "serviceAccount" && input.subjectType === "serviceAccount") {
|
|
@@ -5807,6 +7224,89 @@ function assertKindMatchesSubject(input) {
|
|
|
5807
7224
|
throw new AuthStorageError2("invalid_input", `[Sixb] Access token kind '${input.kind}' cannot target subject type '${input.subjectType}'.`);
|
|
5808
7225
|
}
|
|
5809
7226
|
|
|
7227
|
+
// src/auth-storage/device-authorizations.ts
|
|
7228
|
+
import { AuthStorageError as AuthStorageError3, MAX_PENDING_DEVICE_AUTHORIZATIONS } from "@sixb/core/storage";
|
|
7229
|
+
class SqliteAuthDeviceAuthorizationStore {
|
|
7230
|
+
db;
|
|
7231
|
+
constructor(db) {
|
|
7232
|
+
this.db = db;
|
|
7233
|
+
}
|
|
7234
|
+
async create(input) {
|
|
7235
|
+
return runImmediateTransaction(this.db, () => {
|
|
7236
|
+
this.db.query("DELETE FROM auth_device_authorizations WHERE project_id = ? AND expires_at <= ?").run(input.projectId, toIso(input.createdAt));
|
|
7237
|
+
const pending = this.db.query("SELECT COUNT(*) AS count FROM auth_device_authorizations WHERE project_id = ? AND status = 'pending'").get(input.projectId);
|
|
7238
|
+
if (pending.count >= MAX_PENDING_DEVICE_AUTHORIZATIONS) {
|
|
7239
|
+
throw new AuthStorageError3("device_authorization_limit_exceeded", `[Sixb] Project '${input.projectId}' has too many pending device authorizations.`);
|
|
7240
|
+
}
|
|
7241
|
+
try {
|
|
7242
|
+
this.db.query(`
|
|
7243
|
+
INSERT INTO auth_device_authorizations (
|
|
7244
|
+
project_id, id, device_code_hash, user_code, client_name, token_name,
|
|
7245
|
+
token_expires_at, status, created_at, expires_at
|
|
7246
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)
|
|
7247
|
+
`).run(assertNonEmpty(input.projectId, "Project id"), assertNonEmpty(input.id, "Device authorization id"), assertNonEmpty(input.deviceCodeHash, "Device code hash"), assertNonEmpty(input.userCode, "User code"), assertNonEmpty(input.clientName, "Client name"), assertNonEmpty(input.tokenName, "Token name"), toIso(input.tokenExpiresAt), toIso(input.createdAt), toIso(input.expiresAt));
|
|
7248
|
+
} catch (error) {
|
|
7249
|
+
mapUniqueConstraintError(error, "duplicate_device_authorization", `[Sixb] Device authorization '${input.id}' already exists for project '${input.projectId}'.`);
|
|
7250
|
+
}
|
|
7251
|
+
return requireById(this.db, input.projectId, input.id);
|
|
7252
|
+
});
|
|
7253
|
+
}
|
|
7254
|
+
async getById(params) {
|
|
7255
|
+
return getById(this.db, params.projectId, params.id);
|
|
7256
|
+
}
|
|
7257
|
+
async getByUserCode(params) {
|
|
7258
|
+
const row = this.db.query("SELECT * FROM auth_device_authorizations WHERE project_id = ? AND user_code = ?").get(params.projectId, params.userCode);
|
|
7259
|
+
return row ? rowToDeviceAuthorizationRecord(row) : null;
|
|
7260
|
+
}
|
|
7261
|
+
async approve(params) {
|
|
7262
|
+
return runImmediateTransaction(this.db, () => {
|
|
7263
|
+
const result = this.db.query(`
|
|
7264
|
+
UPDATE auth_device_authorizations
|
|
7265
|
+
SET status = 'approved', approved_user_id = ?, approved_session_id = ?, approved_at = ?
|
|
7266
|
+
WHERE project_id = ? AND id = ? AND status = 'pending' AND expires_at > ?
|
|
7267
|
+
AND EXISTS (
|
|
7268
|
+
SELECT 1 FROM auth_sessions
|
|
7269
|
+
WHERE project_id = ? AND id = ? AND user_id = ?
|
|
7270
|
+
AND revoked_at IS NULL AND expires_at > ?
|
|
7271
|
+
AND (absolute_expires_at IS NULL OR absolute_expires_at > ?)
|
|
7272
|
+
)
|
|
7273
|
+
`).run(params.userId, params.sessionId, toIso(params.approvedAt), params.projectId, params.id, toIso(params.approvedAt), params.projectId, params.sessionId, params.userId, toIso(params.approvedAt), toIso(params.approvedAt));
|
|
7274
|
+
if (result.changes !== 1)
|
|
7275
|
+
throw invalidTransition(params.id, params.projectId);
|
|
7276
|
+
return requireById(this.db, params.projectId, params.id);
|
|
7277
|
+
});
|
|
7278
|
+
}
|
|
7279
|
+
async deny(params) {
|
|
7280
|
+
return runImmediateTransaction(this.db, () => {
|
|
7281
|
+
const result = this.db.query(`
|
|
7282
|
+
UPDATE auth_device_authorizations
|
|
7283
|
+
SET status = 'denied', denied_at = ?
|
|
7284
|
+
WHERE project_id = ? AND id = ? AND status = 'pending' AND expires_at > ?
|
|
7285
|
+
`).run(toIso(params.deniedAt), params.projectId, params.id, toIso(params.deniedAt));
|
|
7286
|
+
if (result.changes !== 1)
|
|
7287
|
+
throw invalidTransition(params.id, params.projectId);
|
|
7288
|
+
return requireById(this.db, params.projectId, params.id);
|
|
7289
|
+
});
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
function getDeviceAuthorizationById(db, projectId, id) {
|
|
7293
|
+
return getById(db, projectId, id);
|
|
7294
|
+
}
|
|
7295
|
+
function getById(db, projectId, id) {
|
|
7296
|
+
const row = db.query("SELECT * FROM auth_device_authorizations WHERE project_id = ? AND id = ?").get(projectId, id);
|
|
7297
|
+
return row ? rowToDeviceAuthorizationRecord(row) : null;
|
|
7298
|
+
}
|
|
7299
|
+
function requireById(db, projectId, id) {
|
|
7300
|
+
const record = getById(db, projectId, id);
|
|
7301
|
+
if (!record) {
|
|
7302
|
+
throw new AuthStorageError3("missing_device_authorization", `[Sixb] Device authorization '${id}' not found for project '${projectId}'.`);
|
|
7303
|
+
}
|
|
7304
|
+
return record;
|
|
7305
|
+
}
|
|
7306
|
+
function invalidTransition(id, projectId) {
|
|
7307
|
+
return new AuthStorageError3("invalid_device_authorization", `[Sixb] Device authorization '${id}' is no longer pending for project '${projectId}'.`);
|
|
7308
|
+
}
|
|
7309
|
+
|
|
5810
7310
|
// src/auth-storage/group-memberships.ts
|
|
5811
7311
|
class SqliteAuthGroupMembershipStore {
|
|
5812
7312
|
db;
|
|
@@ -5895,7 +7395,7 @@ class SqliteAuthUserIdentityStore {
|
|
|
5895
7395
|
}
|
|
5896
7396
|
|
|
5897
7397
|
// src/auth-storage/invitations.ts
|
|
5898
|
-
import { AuthStorageError as
|
|
7398
|
+
import { AuthStorageError as AuthStorageError4 } from "@sixb/core/storage";
|
|
5899
7399
|
class SqliteAuthInvitationStore {
|
|
5900
7400
|
db;
|
|
5901
7401
|
constructor(db) {
|
|
@@ -5932,7 +7432,7 @@ class SqliteAuthInvitationStore {
|
|
|
5932
7432
|
return requireInvitationById(this.db, { projectId, id: existingActive.id });
|
|
5933
7433
|
}
|
|
5934
7434
|
if (getInvitationById(this.db, { projectId, id })) {
|
|
5935
|
-
throw new
|
|
7435
|
+
throw new AuthStorageError4("duplicate_invitation", `[Sixb] Invitation '${id}' already exists but is not active for project '${projectId}'.`);
|
|
5936
7436
|
}
|
|
5937
7437
|
const createdAt = dateOrNow(input.createdAt);
|
|
5938
7438
|
try {
|
|
@@ -6042,7 +7542,7 @@ class SqliteAuthInvitationStore {
|
|
|
6042
7542
|
|
|
6043
7543
|
// src/auth-storage/magic-links.ts
|
|
6044
7544
|
import { resolveAuthSessionAudience } from "@sixb/core";
|
|
6045
|
-
import { AuthStorageError as
|
|
7545
|
+
import { AuthStorageError as AuthStorageError5 } from "@sixb/core/storage";
|
|
6046
7546
|
class SqliteAuthMagicLinkStore {
|
|
6047
7547
|
db;
|
|
6048
7548
|
constructor(db) {
|
|
@@ -6057,7 +7557,7 @@ class SqliteAuthMagicLinkStore {
|
|
|
6057
7557
|
const email = normalizeEmail(input.email);
|
|
6058
7558
|
const tokenHash = assertNonEmpty(input.tokenHash, "Magic link token hash");
|
|
6059
7559
|
if (getMagicLinkRowById(this.db, { projectId, id })) {
|
|
6060
|
-
throw new
|
|
7560
|
+
throw new AuthStorageError5("duplicate_magic_link", `[Sixb] Magic link '${id}' already exists for project '${projectId}'.`);
|
|
6061
7561
|
}
|
|
6062
7562
|
revokeActiveMagicLinksForEmail(this.db, {
|
|
6063
7563
|
projectId,
|
|
@@ -6121,7 +7621,7 @@ class SqliteAuthMagicLinkStore {
|
|
|
6121
7621
|
|
|
6122
7622
|
// src/auth-storage/oidc-attempts.ts
|
|
6123
7623
|
import { resolveAuthSessionAudience as resolveAuthSessionAudience2 } from "@sixb/core";
|
|
6124
|
-
import { AuthStorageError as
|
|
7624
|
+
import { AuthStorageError as AuthStorageError6 } from "@sixb/core/storage";
|
|
6125
7625
|
class SqliteAuthOidcAuthorizationAttemptStore {
|
|
6126
7626
|
db;
|
|
6127
7627
|
constructor(db) {
|
|
@@ -6137,7 +7637,7 @@ class SqliteAuthOidcAuthorizationAttemptStore {
|
|
|
6137
7637
|
const nonceHash = assertNonEmpty(input.nonceHash, "OIDC nonce hash");
|
|
6138
7638
|
const codeVerifier = assertNonEmpty(input.codeVerifier, "OIDC code verifier");
|
|
6139
7639
|
if (getOidcAttemptRowById(this.db, { projectId, id })) {
|
|
6140
|
-
throw new
|
|
7640
|
+
throw new AuthStorageError6("duplicate_oidc_attempt", `[Sixb] OIDC authorization attempt '${id}' already exists for project '${projectId}'.`);
|
|
6141
7641
|
}
|
|
6142
7642
|
try {
|
|
6143
7643
|
this.db.query(`
|
|
@@ -6180,7 +7680,7 @@ class SqliteAuthOidcAuthorizationAttemptStore {
|
|
|
6180
7680
|
}
|
|
6181
7681
|
|
|
6182
7682
|
// src/auth-storage/service-account-group-memberships.ts
|
|
6183
|
-
import { AuthStorageError as
|
|
7683
|
+
import { AuthStorageError as AuthStorageError7 } from "@sixb/core/storage";
|
|
6184
7684
|
class SqliteAuthServiceAccountGroupMembershipStore {
|
|
6185
7685
|
db;
|
|
6186
7686
|
constructor(db) {
|
|
@@ -6192,7 +7692,7 @@ class SqliteAuthServiceAccountGroupMembershipStore {
|
|
|
6192
7692
|
const groupId = assertNonEmpty(input.groupId, "Group id");
|
|
6193
7693
|
const serviceAccount = this.db.query("SELECT * FROM auth_service_accounts WHERE project_id = ? AND id = ?").get(projectId, serviceAccountId);
|
|
6194
7694
|
if (!serviceAccount) {
|
|
6195
|
-
throw new
|
|
7695
|
+
throw new AuthStorageError7("missing_service_account", `[Sixb] Service account '${serviceAccountId}' not found for project '${projectId}'.`);
|
|
6196
7696
|
}
|
|
6197
7697
|
this.db.query(`
|
|
6198
7698
|
INSERT OR IGNORE INTO auth_service_account_group_memberships (
|
|
@@ -6217,7 +7717,7 @@ class SqliteAuthServiceAccountGroupMembershipStore {
|
|
|
6217
7717
|
const serviceAccountId = assertNonEmpty(input.serviceAccountId, "Service account id");
|
|
6218
7718
|
const serviceAccount = this.db.query("SELECT * FROM auth_service_accounts WHERE project_id = ? AND id = ?").get(projectId, serviceAccountId);
|
|
6219
7719
|
if (!serviceAccount) {
|
|
6220
|
-
throw new
|
|
7720
|
+
throw new AuthStorageError7("missing_service_account", `[Sixb] Service account '${serviceAccountId}' not found for project '${projectId}'.`);
|
|
6221
7721
|
}
|
|
6222
7722
|
const groupIds = normalizeGroupIds(input.groupIds);
|
|
6223
7723
|
const desired = new Set(groupIds);
|
|
@@ -6272,7 +7772,7 @@ class SqliteAuthServiceAccountGroupMembershipStore {
|
|
|
6272
7772
|
}
|
|
6273
7773
|
|
|
6274
7774
|
// src/auth-storage/service-accounts.ts
|
|
6275
|
-
import { AuthStorageError as
|
|
7775
|
+
import { AuthStorageError as AuthStorageError8 } from "@sixb/core/storage";
|
|
6276
7776
|
class SqliteAuthServiceAccountStore {
|
|
6277
7777
|
db;
|
|
6278
7778
|
constructor(db) {
|
|
@@ -6334,7 +7834,7 @@ class SqliteAuthServiceAccountStore {
|
|
|
6334
7834
|
`).run(name, input.description ?? null, input.status ?? null, toIso(updatedAt), projectId, id);
|
|
6335
7835
|
const updated = await this.getById({ projectId, id });
|
|
6336
7836
|
if (!updated) {
|
|
6337
|
-
throw new
|
|
7837
|
+
throw new AuthStorageError8("missing_service_account", `[Sixb] Service account '${id}' not found for project '${projectId}'.`);
|
|
6338
7838
|
}
|
|
6339
7839
|
return updated;
|
|
6340
7840
|
}
|
|
@@ -6368,7 +7868,7 @@ class SqliteAuthServiceAccountStore {
|
|
|
6368
7868
|
}
|
|
6369
7869
|
|
|
6370
7870
|
// src/auth-storage/sessions.ts
|
|
6371
|
-
import { AuthStorageError as
|
|
7871
|
+
import { AuthStorageError as AuthStorageError9 } from "@sixb/core/storage";
|
|
6372
7872
|
class SqliteAuthSessionStore {
|
|
6373
7873
|
db;
|
|
6374
7874
|
constructor(db) {
|
|
@@ -6441,7 +7941,7 @@ class SqliteAuthSessionStore {
|
|
|
6441
7941
|
return runImmediateTransaction(this.db, () => {
|
|
6442
7942
|
const existing = getSessionRowById(this.db, params);
|
|
6443
7943
|
if (!existing) {
|
|
6444
|
-
throw new
|
|
7944
|
+
throw new AuthStorageError9("missing_session", `[Sixb] Session '${params.id}' not found for project '${params.projectId}'.`);
|
|
6445
7945
|
}
|
|
6446
7946
|
this.db.query(`
|
|
6447
7947
|
UPDATE auth_sessions
|
|
@@ -6469,7 +7969,7 @@ class SqliteAuthSessionStore {
|
|
|
6469
7969
|
`).run(toIso(params.lastSeenAt), params.projectId, params.id);
|
|
6470
7970
|
const updated = getSessionById(this.db, params);
|
|
6471
7971
|
if (!updated) {
|
|
6472
|
-
throw new
|
|
7972
|
+
throw new AuthStorageError9("missing_session", `[Sixb] Session '${params.id}' not found for project '${params.projectId}'.`);
|
|
6473
7973
|
}
|
|
6474
7974
|
return updated;
|
|
6475
7975
|
});
|
|
@@ -6477,7 +7977,7 @@ class SqliteAuthSessionStore {
|
|
|
6477
7977
|
}
|
|
6478
7978
|
|
|
6479
7979
|
// src/auth-storage/users.ts
|
|
6480
|
-
import { AuthStorageError as
|
|
7980
|
+
import { AuthStorageError as AuthStorageError10 } from "@sixb/core/storage";
|
|
6481
7981
|
class SqliteAuthUserStore {
|
|
6482
7982
|
db;
|
|
6483
7983
|
constructor(db) {
|
|
@@ -6488,10 +7988,10 @@ class SqliteAuthUserStore {
|
|
|
6488
7988
|
const projectId = assertNonEmpty(input.projectId, "Project id");
|
|
6489
7989
|
const email = normalizeEmail(input.email);
|
|
6490
7990
|
if (getUserRowById(this.db, { projectId, id })) {
|
|
6491
|
-
throw new
|
|
7991
|
+
throw new AuthStorageError10("duplicate_user", `[Sixb] User '${id}' already exists for project '${projectId}'.`);
|
|
6492
7992
|
}
|
|
6493
7993
|
if (getUserRowByEmail(this.db, { projectId, email })) {
|
|
6494
|
-
throw new
|
|
7994
|
+
throw new AuthStorageError10("duplicate_user", `[Sixb] User email '${email}' already exists for project '${projectId}'.`);
|
|
6495
7995
|
}
|
|
6496
7996
|
const createdAt = dateOrNow(input.createdAt);
|
|
6497
7997
|
const updatedAt = input.updatedAt ? new Date(input.updatedAt) : createdAt;
|
|
@@ -6536,7 +8036,7 @@ class SqliteAuthUserStore {
|
|
|
6536
8036
|
id: input.id
|
|
6537
8037
|
});
|
|
6538
8038
|
if (!existing) {
|
|
6539
|
-
throw new
|
|
8039
|
+
throw new AuthStorageError10("missing_user", `[Sixb] User '${input.id}' not found for project '${input.projectId}'.`);
|
|
6540
8040
|
}
|
|
6541
8041
|
const updatedAt = dateOrNow(input.updatedAt);
|
|
6542
8042
|
this.db.query(`
|
|
@@ -6560,7 +8060,7 @@ class SqliteAuthUserStore {
|
|
|
6560
8060
|
id: input.id
|
|
6561
8061
|
});
|
|
6562
8062
|
if (!existing) {
|
|
6563
|
-
throw new
|
|
8063
|
+
throw new AuthStorageError10("missing_user", `[Sixb] User '${input.id}' not found for project '${input.projectId}'.`);
|
|
6564
8064
|
}
|
|
6565
8065
|
const updatedAt = dateOrNow(input.updatedAt);
|
|
6566
8066
|
this.db.query(`
|
|
@@ -6619,6 +8119,7 @@ class SqliteAuthStorage {
|
|
|
6619
8119
|
groupMemberships;
|
|
6620
8120
|
magicLinks;
|
|
6621
8121
|
oidcAuthorizationAttempts;
|
|
8122
|
+
deviceAuthorizations;
|
|
6622
8123
|
constructor(options = {}) {
|
|
6623
8124
|
this.connection = openSqliteStoreConnection(options);
|
|
6624
8125
|
this.db = this.connection.db;
|
|
@@ -6635,6 +8136,30 @@ class SqliteAuthStorage {
|
|
|
6635
8136
|
this.groupMemberships = new SqliteAuthGroupMembershipStore(this.db);
|
|
6636
8137
|
this.magicLinks = new SqliteAuthMagicLinkStore(this.db);
|
|
6637
8138
|
this.oidcAuthorizationAttempts = new SqliteAuthOidcAuthorizationAttemptStore(this.db);
|
|
8139
|
+
this.deviceAuthorizations = new SqliteAuthDeviceAuthorizationStore(this.db);
|
|
8140
|
+
}
|
|
8141
|
+
async completeDeviceAuthorization(input) {
|
|
8142
|
+
return runImmediateTransaction(this.db, () => {
|
|
8143
|
+
const authorization = getDeviceAuthorizationById(this.db, input.projectId, input.id);
|
|
8144
|
+
if (!authorization) {
|
|
8145
|
+
throw new AuthStorageError11("missing_device_authorization", `[Sixb] Device authorization '${input.id}' not found for project '${input.projectId}'.`);
|
|
8146
|
+
}
|
|
8147
|
+
assertCompletableDeviceAuthorization(authorization, input);
|
|
8148
|
+
const accessToken = createAuthAccessToken(this.db, input.accessToken);
|
|
8149
|
+
this.db.query(`
|
|
8150
|
+
UPDATE auth_device_authorizations
|
|
8151
|
+
SET status = 'consumed', consumed_at = ?
|
|
8152
|
+
WHERE project_id = ? AND id = ? AND status = 'approved'
|
|
8153
|
+
`).run(toIso(input.completedAt), input.projectId, input.id);
|
|
8154
|
+
return {
|
|
8155
|
+
authorization: {
|
|
8156
|
+
...authorization,
|
|
8157
|
+
status: "consumed",
|
|
8158
|
+
consumedAt: new Date(input.completedAt)
|
|
8159
|
+
},
|
|
8160
|
+
accessToken
|
|
8161
|
+
};
|
|
8162
|
+
});
|
|
6638
8163
|
}
|
|
6639
8164
|
async completeMagicLinkSignIn(input) {
|
|
6640
8165
|
const result = runImmediateTransaction(this.db, () => {
|
|
@@ -6664,7 +8189,7 @@ class SqliteAuthStorage {
|
|
|
6664
8189
|
consumedAt: completedAt
|
|
6665
8190
|
});
|
|
6666
8191
|
return {
|
|
6667
|
-
error: new
|
|
8192
|
+
error: new AuthStorageError11("suspended_user", `[Sixb] User '${existingUserRow.id}' is suspended for project '${projectId}'.`)
|
|
6668
8193
|
};
|
|
6669
8194
|
}
|
|
6670
8195
|
if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
|
|
@@ -6675,7 +8200,7 @@ class SqliteAuthStorage {
|
|
|
6675
8200
|
consumedAt: completedAt
|
|
6676
8201
|
});
|
|
6677
8202
|
return {
|
|
6678
|
-
error: new
|
|
8203
|
+
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
|
|
6679
8204
|
};
|
|
6680
8205
|
}
|
|
6681
8206
|
if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && hasActiveUsers(this.db, projectId)) {
|
|
@@ -6686,7 +8211,7 @@ class SqliteAuthStorage {
|
|
|
6686
8211
|
consumedAt: completedAt
|
|
6687
8212
|
});
|
|
6688
8213
|
return {
|
|
6689
|
-
error: new
|
|
8214
|
+
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
|
|
6690
8215
|
};
|
|
6691
8216
|
}
|
|
6692
8217
|
validateCompleteSessionInput(this.db, projectId, input.session);
|
|
@@ -6695,7 +8220,7 @@ class SqliteAuthStorage {
|
|
|
6695
8220
|
if (shouldCreateUser) {
|
|
6696
8221
|
newUserId = assertNonEmpty(input.newUserId, "User id");
|
|
6697
8222
|
if (getUserRowById(this.db, { projectId, id: newUserId })) {
|
|
6698
|
-
throw new
|
|
8223
|
+
throw new AuthStorageError11("duplicate_user", `[Sixb] User '${newUserId}' already exists for project '${projectId}'.`);
|
|
6699
8224
|
}
|
|
6700
8225
|
}
|
|
6701
8226
|
consumeMagicLink(this.db, {
|
|
@@ -6770,37 +8295,37 @@ class SqliteAuthStorage {
|
|
|
6770
8295
|
if (identity && !userRow) {
|
|
6771
8296
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
6772
8297
|
return {
|
|
6773
|
-
error: new
|
|
8298
|
+
error: new AuthStorageError11("missing_user", `[Sixb] User '${identity.user_id}' not found for linked OIDC identity.`)
|
|
6774
8299
|
};
|
|
6775
8300
|
}
|
|
6776
8301
|
if (!identity && userRow && (!input.autoLinkByVerifiedEmail || !input.emailVerified)) {
|
|
6777
8302
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
6778
8303
|
return {
|
|
6779
|
-
error: new
|
|
8304
|
+
error: new AuthStorageError11("email_link_not_allowed", `[Sixb] OIDC identity cannot auto-link to user '${userRow.id}' for project '${projectId}'.`)
|
|
6780
8305
|
};
|
|
6781
8306
|
}
|
|
6782
8307
|
if (userRow?.status === "suspended") {
|
|
6783
8308
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
6784
8309
|
return {
|
|
6785
|
-
error: new
|
|
8310
|
+
error: new AuthStorageError11("suspended_user", `[Sixb] User '${userRow.id}' is suspended for project '${projectId}'.`)
|
|
6786
8311
|
};
|
|
6787
8312
|
}
|
|
6788
8313
|
if (shouldCreateUser && !input.emailVerified) {
|
|
6789
8314
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
6790
8315
|
return {
|
|
6791
|
-
error: new
|
|
8316
|
+
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
6792
8317
|
};
|
|
6793
8318
|
}
|
|
6794
8319
|
if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
|
|
6795
8320
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
6796
8321
|
return {
|
|
6797
|
-
error: new
|
|
8322
|
+
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
6798
8323
|
};
|
|
6799
8324
|
}
|
|
6800
8325
|
if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && hasActiveUsers(this.db, projectId)) {
|
|
6801
8326
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
6802
8327
|
return {
|
|
6803
|
-
error: new
|
|
8328
|
+
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
6804
8329
|
};
|
|
6805
8330
|
}
|
|
6806
8331
|
validateCompleteSessionInput(this.db, projectId, input.session);
|
|
@@ -6809,7 +8334,7 @@ class SqliteAuthStorage {
|
|
|
6809
8334
|
if (shouldCreateUser) {
|
|
6810
8335
|
newUserId = assertNonEmpty(input.newUserId, "User id");
|
|
6811
8336
|
if (getUserRowById(this.db, { projectId, id: newUserId })) {
|
|
6812
|
-
throw new
|
|
8337
|
+
throw new AuthStorageError11("duplicate_user", `[Sixb] User '${newUserId}' already exists for project '${projectId}'.`);
|
|
6813
8338
|
}
|
|
6814
8339
|
}
|
|
6815
8340
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
@@ -6882,7 +8407,7 @@ class SqliteAuthStorage {
|
|
|
6882
8407
|
id: input.userId
|
|
6883
8408
|
});
|
|
6884
8409
|
if (!existing) {
|
|
6885
|
-
throw new
|
|
8410
|
+
throw new AuthStorageError11("missing_user", `[Sixb] User '${input.userId}' not found for project '${input.projectId}'.`);
|
|
6886
8411
|
}
|
|
6887
8412
|
this.db.query(`
|
|
6888
8413
|
UPDATE auth_users
|
|
@@ -6908,24 +8433,24 @@ class SqliteAuthStorage {
|
|
|
6908
8433
|
}
|
|
6909
8434
|
assertMagicLinkUsable(projectId, id, tokenHash, completedAt, row) {
|
|
6910
8435
|
if (!row) {
|
|
6911
|
-
throw new
|
|
8436
|
+
throw new AuthStorageError11("missing_magic_link", `[Sixb] Magic link '${id}' not found for project '${projectId}'.`);
|
|
6912
8437
|
}
|
|
6913
8438
|
if (row.token_hash !== tokenHash || row.consumed_at || row.revoked_at) {
|
|
6914
|
-
throw new
|
|
8439
|
+
throw new AuthStorageError11("invalid_magic_link", `[Sixb] Magic link '${id}' is not valid for project '${projectId}'.`);
|
|
6915
8440
|
}
|
|
6916
8441
|
if (new Date(row.expires_at) <= completedAt) {
|
|
6917
|
-
throw new
|
|
8442
|
+
throw new AuthStorageError11("expired_magic_link", `[Sixb] Magic link '${id}' is expired for project '${projectId}'.`);
|
|
6918
8443
|
}
|
|
6919
8444
|
}
|
|
6920
8445
|
assertOidcAttemptUsable(projectId, id, stateHash, completedAt, row) {
|
|
6921
8446
|
if (!row) {
|
|
6922
|
-
throw new
|
|
8447
|
+
throw new AuthStorageError11("missing_oidc_attempt", `[Sixb] OIDC authorization attempt '${id}' not found for project '${projectId}'.`);
|
|
6923
8448
|
}
|
|
6924
8449
|
if (row.state_hash !== stateHash || row.consumed_at) {
|
|
6925
|
-
throw new
|
|
8450
|
+
throw new AuthStorageError11("invalid_oidc_attempt", `[Sixb] OIDC authorization attempt '${id}' is not valid for project '${projectId}'.`);
|
|
6926
8451
|
}
|
|
6927
8452
|
if (new Date(row.expires_at) <= completedAt) {
|
|
6928
|
-
throw new
|
|
8453
|
+
throw new AuthStorageError11("expired_oidc_attempt", `[Sixb] OIDC authorization attempt '${id}' is expired for project '${projectId}'.`);
|
|
6929
8454
|
}
|
|
6930
8455
|
}
|
|
6931
8456
|
insertUser(input) {
|
|
@@ -7061,7 +8586,21 @@ function assertSignInSessionAudience(projectId, sessionAudience, storedAudience)
|
|
|
7061
8586
|
if (sessionAudience === storedAudience) {
|
|
7062
8587
|
return;
|
|
7063
8588
|
}
|
|
7064
|
-
throw new
|
|
8589
|
+
throw new AuthStorageError11("invalid_input", `[Sixb] Sign-in session audience '${sessionAudience}' does not match stored auth audience '${storedAudience}' for project '${projectId}'.`);
|
|
8590
|
+
}
|
|
8591
|
+
function assertCompletableDeviceAuthorization(authorization, input) {
|
|
8592
|
+
if (authorization.deviceCodeHash !== input.deviceCodeHash) {
|
|
8593
|
+
throw new AuthStorageError11("invalid_device_authorization", "[Sixb] Invalid device authorization.");
|
|
8594
|
+
}
|
|
8595
|
+
if (authorization.status === "pending") {
|
|
8596
|
+
throw new AuthStorageError11("pending_device_authorization", "[Sixb] Device authorization is pending.");
|
|
8597
|
+
}
|
|
8598
|
+
if (authorization.status === "denied") {
|
|
8599
|
+
throw new AuthStorageError11("device_authorization_denied", "[Sixb] Device authorization was denied.");
|
|
8600
|
+
}
|
|
8601
|
+
if (authorization.status !== "approved" || authorization.expiresAt <= input.completedAt || !authorization.approvedUserId || !authorization.approvedSessionId || input.accessToken.projectId !== input.projectId || input.accessToken.subjectType !== "user" || input.accessToken.subjectId !== authorization.approvedUserId || input.accessToken.createdBySessionId !== authorization.approvedSessionId) {
|
|
8602
|
+
throw new AuthStorageError11("invalid_device_authorization", "[Sixb] Device authorization is invalid.");
|
|
8603
|
+
}
|
|
7065
8604
|
}
|
|
7066
8605
|
// src/connector-connection-storage.ts
|
|
7067
8606
|
import {
|
|
@@ -13521,15 +15060,15 @@ class SqliteWorkflowAgentNodeRunStorage {
|
|
|
13521
15060
|
projectId: input.projectId,
|
|
13522
15061
|
executionId: input.executionId,
|
|
13523
15062
|
nodeRunId: input.nodeRunId,
|
|
13524
|
-
|
|
15063
|
+
actorId: input.actorId,
|
|
13525
15064
|
workflowExecutionId: parent.execution_id
|
|
13526
15065
|
});
|
|
13527
15066
|
try {
|
|
13528
15067
|
this.db.query(`
|
|
13529
15068
|
INSERT INTO workflow_agent_node_runs (
|
|
13530
|
-
project_id, node_run_id, execution_id,
|
|
15069
|
+
project_id, node_run_id, execution_id, actor_id, status, prompt, attempt, created_at
|
|
13531
15070
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
13532
|
-
`).run(input.projectId, input.nodeRunId, input.executionId, input.
|
|
15071
|
+
`).run(input.projectId, input.nodeRunId, input.executionId, input.actorId, "queued", input.prompt, 0, (input.createdAt ?? new Date).toISOString());
|
|
13533
15072
|
} catch (error) {
|
|
13534
15073
|
if (isUniqueConstraintError2(error)) {
|
|
13535
15074
|
throw new WorkflowRunError(`[SixbSqlite] Agent execution already exists for workflow node run '${input.nodeRunId}'.`);
|
|
@@ -13608,9 +15147,9 @@ class SqliteWorkflowAgentNodeRunStorage {
|
|
|
13608
15147
|
return { runs: [], total: 0, hasMore: false };
|
|
13609
15148
|
const whereClauses = ["project_id = ?"];
|
|
13610
15149
|
const args = [input.projectId];
|
|
13611
|
-
if (input.
|
|
13612
|
-
whereClauses.push("
|
|
13613
|
-
args.push(input.
|
|
15150
|
+
if (input.actorId) {
|
|
15151
|
+
whereClauses.push("actor_id = ?");
|
|
15152
|
+
args.push(input.actorId);
|
|
13614
15153
|
}
|
|
13615
15154
|
appendRunListFilters(whereClauses, args, input);
|
|
13616
15155
|
const result = queryRunList({
|
|
@@ -13860,7 +15399,7 @@ function rowToWorkflowAgentNodeRunRecord(row) {
|
|
|
13860
15399
|
projectId: row.project_id,
|
|
13861
15400
|
nodeRunId: row.node_run_id,
|
|
13862
15401
|
executionId: row.execution_id,
|
|
13863
|
-
|
|
15402
|
+
actorId: row.actor_id,
|
|
13864
15403
|
status: row.status,
|
|
13865
15404
|
prompt: row.prompt,
|
|
13866
15405
|
...row.model_id ? { modelId: row.model_id } : {},
|
|
@@ -13895,6 +15434,7 @@ class SqliteStorage {
|
|
|
13895
15434
|
agents;
|
|
13896
15435
|
aiUsage;
|
|
13897
15436
|
aiCosts;
|
|
15437
|
+
aiLimits;
|
|
13898
15438
|
actionRuns;
|
|
13899
15439
|
pipelineRuns;
|
|
13900
15440
|
syncRuns;
|
|
@@ -13942,6 +15482,7 @@ class SqliteStorage {
|
|
|
13942
15482
|
this.agents = createAgentOperationScope(stores.agents, scope);
|
|
13943
15483
|
this.aiUsage = createOperationScopedFacade(stores.aiUsage, scope);
|
|
13944
15484
|
this.aiCosts = createOperationScopedFacade(stores.aiCosts, scope);
|
|
15485
|
+
this.aiLimits = createOperationScopedFacade(stores.aiLimits, scope);
|
|
13945
15486
|
this.actionRuns = createOperationScopedFacade(stores.actionRuns, scope);
|
|
13946
15487
|
this.pipelineRuns = createOperationScopedFacade(stores.pipelineRuns, scope);
|
|
13947
15488
|
this.timeseries = createOperationScopedFacade(readTimeseries, readScope);
|
|
@@ -14062,6 +15603,7 @@ function createSqliteStores(connection, options) {
|
|
|
14062
15603
|
agents: new SqliteAgentStorage({ connection, executions }),
|
|
14063
15604
|
aiUsage: new SqliteAiUsageStorage({ connection }),
|
|
14064
15605
|
aiCosts: new SqliteAiCostStorage(connection),
|
|
15606
|
+
aiLimits: new SqliteAiLimitStorage({ connection }),
|
|
14065
15607
|
actionRuns: new SqliteActionRunStorage({ connection, executions }),
|
|
14066
15608
|
pipelineRuns: new SqlitePipelineRunStorage({ connection, executions }),
|
|
14067
15609
|
timeseries: new SqliteTimeseriesStorage({ connection }),
|
|
@@ -14079,4 +15621,4 @@ export {
|
|
|
14079
15621
|
SqliteStorage
|
|
14080
15622
|
};
|
|
14081
15623
|
|
|
14082
|
-
//# debugId=
|
|
15624
|
+
//# debugId=CC3E88BEF909A1E364756E2164756E21
|