@circuitwall/jarela 0.14.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/route.js +6 -1
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/agents/route.js +6 -1
- package/.next/standalone/.next/server/app/api/v1/agents/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +9 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/route.js +9 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js +36 -29
- package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/events/route.js +7 -1
- package/.next/standalone/.next/server/app/api/v1/events/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extensions/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/extensions/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +4 -4
- package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/health/route.js +7 -1
- package/.next/standalone/.next/server/app/api/v1/health/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js +9 -1
- package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js +9 -1
- package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/models/route.js +6 -1
- package/.next/standalone/.next/server/app/api/v1/models/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +7 -1
- package/.next/standalone/.next/server/app/api/v1/page-capture/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js +14 -7
- package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +28 -0
- package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/providers/route.js +7 -1
- package/.next/standalone/.next/server/app/api/v1/providers/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js +16 -2
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/threads/route.js +6 -1
- package/.next/standalone/.next/server/app/api/v1/threads/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/tools/route.js +10 -3
- package/.next/standalone/.next/server/app/api/v1/tools/route.js.map +1 -1
- package/.next/standalone/.next/server/app/index.html +2 -2
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page.js +56 -0
- package/.next/standalone/.next/server/app/page.js.map +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup.segment.rsc +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -1
- package/.next/standalone/.next/server/chunks/1683.js +2 -2
- package/.next/standalone/.next/server/chunks/2082.js +122 -13
- package/.next/standalone/.next/server/chunks/2082.js.map +1 -1
- package/.next/standalone/.next/server/chunks/210.js +3 -3
- package/.next/standalone/.next/server/chunks/210.js.map +1 -1
- package/.next/standalone/.next/server/chunks/239.js +1902 -1487
- package/.next/standalone/.next/server/chunks/239.js.map +1 -1
- package/.next/standalone/.next/server/chunks/2447.js +9 -1
- package/.next/standalone/.next/server/chunks/2447.js.map +1 -1
- package/.next/standalone/.next/server/chunks/423.js +125 -16
- package/.next/standalone/.next/server/chunks/423.js.map +1 -1
- package/.next/standalone/.next/server/chunks/4631.js +36 -29
- package/.next/standalone/.next/server/chunks/4631.js.map +1 -1
- package/.next/standalone/.next/server/chunks/5937.js +3 -2
- package/.next/standalone/.next/server/chunks/5937.js.map +1 -1
- package/.next/standalone/.next/server/chunks/{947.js → 8866.js} +11321 -10883
- package/.next/standalone/.next/server/chunks/8866.js.map +1 -0
- package/.next/standalone/.next/server/chunks/9032.js +3 -3
- package/.next/standalone/.next/server/chunks/9032.js.map +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/.next/server/middleware.js +122 -13
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/proxy.js.map +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/app/{page-473b39ec30c7f569.js → page-a7cae65f235e2942.js} +57 -1
- package/.next/standalone/.next/static/chunks/app/page-a7cae65f235e2942.js.map +1 -0
- package/.next/standalone/.next/static/css/{6f8b1a84bcbcd467.css → e57bdbbbb5a05779.css} +2 -2
- package/.next/standalone/.next/static/css/e57bdbbbb5a05779.css.map +1 -0
- package/.next/standalone/package.json +9 -1
- package/CHANGELOG.md +90 -0
- package/README.md +30 -2
- package/api/types.ts +8 -0
- package/app/api/v1/agents/[id]/route.ts +7 -0
- package/app/api/v1/agents/route.ts +7 -0
- package/app/api/v1/events/route.ts +8 -0
- package/app/api/v1/extensions/route.ts +2 -2
- package/app/api/v1/extensions/tools/[name]/secrets/route.ts +3 -3
- package/app/api/v1/health/route.ts +8 -0
- package/app/api/v1/models/route.ts +7 -0
- package/app/api/v1/page-capture/route.ts +8 -0
- package/app/api/v1/providers/route.ts +8 -0
- package/app/api/v1/threads/[thread_id]/route.ts +8 -0
- package/app/api/v1/threads/[thread_id]/run/route.ts +9 -0
- package/app/api/v1/threads/route.ts +7 -0
- package/app/api/v1/tools/route.ts +9 -0
- package/components/chat/ContextUsageBar.tsx +44 -0
- package/lib/agents/llm.ts +25 -2
- package/lib/agents/run-thread.ts +13 -1
- package/lib/agents/stream-collector.ts +9 -1
- package/lib/api/serializers.test.ts +15 -0
- package/lib/api/serializers.ts +8 -0
- package/lib/db/migrations.ts +15 -0
- package/lib/health/runner.test.ts +24 -2
- package/lib/mcp/registry.ts +14 -6
- package/lib/providers/anthropic.test.ts +95 -0
- package/lib/providers/anthropic.ts +106 -10
- package/lib/providers/jarela-chat-model.ts +9 -1
- package/lib/providers/known-context-windows.ts +21 -0
- package/lib/providers/types.ts +21 -1
- package/lib/stores/message-usage.test.ts +34 -0
- package/lib/stores/message-usage.ts +15 -3
- package/lib/stores/pricing.test.ts +52 -0
- package/lib/stores/pricing.ts +26 -1
- package/lib/tools/builtins.ts +4 -0
- package/lib/tools/extension-surfaces.test.ts +79 -0
- package/lib/tools/extension-surfaces.ts +153 -0
- package/lib/tools/index.ts +27 -8
- package/lib/tools/list-tools.test.ts +76 -0
- package/lib/tools/list-tools.ts +84 -0
- package/lib/tools/mcp-servers-info.test.ts +73 -0
- package/lib/tools/mcp-servers-info.ts +71 -0
- package/lib/tools/providers-info.test.ts +73 -0
- package/lib/tools/providers-info.ts +106 -0
- package/lib/tools/registry.ts +36 -25
- package/lib/tools/types.ts +13 -0
- package/package.json +9 -1
- package/.next/standalone/.next/server/chunks/947.js.map +0 -1
- package/.next/standalone/.next/static/chunks/app/page-473b39ec30c7f569.js.map +0 -1
- package/.next/standalone/.next/static/css/6f8b1a84bcbcd467.css.map +0 -1
- /package/.next/standalone/.next/static/{T0p2VVPsJPj44rwbmjaFb → d_vhp-lJqfdjRFpnLVIqZ}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{T0p2VVPsJPj44rwbmjaFb → d_vhp-lJqfdjRFpnLVIqZ}/_ssgManifest.js +0 -0
|
@@ -27,8 +27,9 @@ function recordMessageUsage(input) {
|
|
|
27
27
|
model_config_name, input_tokens, output_tokens,
|
|
28
28
|
input_rate_usd_per_mtok, output_rate_usd_per_mtok, cost_usd, created_at,
|
|
29
29
|
hot_tokens, warm_tokens, facts_tokens, overhead_tokens,
|
|
30
|
-
hot_budget_tokens, warm_budget_tokens, facts_budget_tokens, context_window_tokens
|
|
31
|
-
|
|
30
|
+
hot_budget_tokens, warm_budget_tokens, facts_budget_tokens, context_window_tokens,
|
|
31
|
+
cache_creation_input_tokens, cache_read_input_tokens
|
|
32
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(input.message_id, input.thread_id, input.agent_id, input.agent_name, input.provider, input.model_id, input.model_config_name, input.input_tokens, input.output_tokens, input.input_rate_usd_per_mtok, input.output_rate_usd_per_mtok, input.cost_usd, new Date().toISOString(), t?.hot_tokens ?? null, t?.warm_tokens ?? null, t?.facts_tokens ?? null, t?.overhead_tokens ?? null, t?.hot_budget_tokens ?? null, t?.warm_budget_tokens ?? null, t?.facts_budget_tokens ?? null, t?.context_window_tokens ?? null, input.cache_creation_input_tokens ?? null, input.cache_read_input_tokens ?? null);
|
|
32
33
|
}
|
|
33
34
|
function getMessageUsage(messageId) {
|
|
34
35
|
return getDb().prepare("SELECT * FROM message_usage WHERE message_id=?").get(messageId) ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"5937.js","mappings":"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","sources":["webpack://@circuitwall/jarela/./lib/stores/message-usage.ts","webpack://@circuitwall/jarela/./lib/agents/sqlite-checkpoint-saver.ts","webpack://@circuitwall/jarela/./lib/agents/checkpointer.ts","webpack://@circuitwall/jarela/./lib/stores/threads.ts"],"sourcesContent":["// ADR-0041. Immutable per-assistant-turn snapshot of LLM usage.\n//\n// Written once when an assistant turn is persisted (see\n// `persistAssistantMessage` in lib/agents/run-thread.ts). Never updated.\n// The dashboard reads this table in preference to recomputing tokens /\n// cost from `messages.content` + current agent_configs joins.\n\nimport { getDb } from \"@/lib/db\";\n\nexport interface MessageUsageInput {\n message_id: string;\n thread_id: string;\n agent_id: string;\n agent_name: string;\n provider: string;\n model_id: string;\n model_config_name: string | null;\n input_tokens: number;\n output_tokens: number;\n input_rate_usd_per_mtok: number | null;\n output_rate_usd_per_mtok: number | null;\n cost_usd: number;\n // Per-tier input-token breakdown captured from the history-window\n // assembly. NULL/undefined when unknown (very old assistant turns\n // persisted before the breakdown was wired up, or non-LLM persists).\n tier_usage?: TierUsage | null;\n}\n\nexport interface TierUsage {\n hot_tokens: number;\n warm_tokens: number;\n facts_tokens: number;\n overhead_tokens: number;\n hot_budget_tokens: number;\n warm_budget_tokens: number;\n facts_budget_tokens: number;\n context_window_tokens: number;\n}\n\nexport interface MessageUsageRow extends Omit<MessageUsageInput, \"tier_usage\"> {\n created_at: string;\n hot_tokens: number | null;\n warm_tokens: number | null;\n facts_tokens: number | null;\n overhead_tokens: number | null;\n hot_budget_tokens: number | null;\n warm_budget_tokens: number | null;\n facts_budget_tokens: number | null;\n context_window_tokens: number | null;\n}\n\nexport function recordMessageUsage(input: MessageUsageInput): void {\n const db = getDb();\n const t = input.tier_usage ?? null;\n db.prepare(\n `INSERT OR IGNORE INTO message_usage (\n message_id, thread_id, agent_id, agent_name, provider, model_id,\n model_config_name, input_tokens, output_tokens,\n input_rate_usd_per_mtok, output_rate_usd_per_mtok, cost_usd, created_at,\n hot_tokens, warm_tokens, facts_tokens, overhead_tokens,\n hot_budget_tokens, warm_budget_tokens, facts_budget_tokens, context_window_tokens\n ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n ).run(\n input.message_id,\n input.thread_id,\n input.agent_id,\n input.agent_name,\n input.provider,\n input.model_id,\n input.model_config_name,\n input.input_tokens,\n input.output_tokens,\n input.input_rate_usd_per_mtok,\n input.output_rate_usd_per_mtok,\n input.cost_usd,\n new Date().toISOString(),\n t?.hot_tokens ?? null,\n t?.warm_tokens ?? null,\n t?.facts_tokens ?? null,\n t?.overhead_tokens ?? null,\n t?.hot_budget_tokens ?? null,\n t?.warm_budget_tokens ?? null,\n t?.facts_budget_tokens ?? null,\n t?.context_window_tokens ?? null,\n );\n}\n\nexport function getMessageUsage(messageId: string): MessageUsageRow | null {\n return (getDb()\n .prepare(\"SELECT * FROM message_usage WHERE message_id=?\")\n .get(messageId) as MessageUsageRow | undefined) ?? null;\n}\n\n/**\n * Batch lookup keyed by message_id. Returns a Map missing any ids that\n * have no usage row (user turns, legacy rows). Used by the threads GET\n * route to attach per-turn token counts to a page of messages without\n * issuing one SELECT per row.\n */\nexport function getMessageUsageByIds(messageIds: readonly string[]): Map<string, MessageUsageRow> {\n const out = new Map<string, MessageUsageRow>();\n if (messageIds.length === 0) return out;\n const placeholders = messageIds.map(() => \"?\").join(\",\");\n const rows = getDb()\n .prepare(`SELECT * FROM message_usage WHERE message_id IN (${placeholders})`)\n .all(...messageIds) as unknown as MessageUsageRow[];\n for (const row of rows) out.set(row.message_id, row);\n return out;\n}\n\nexport function computeCostUsd(\n inputTokens: number,\n outputTokens: number,\n inputRatePerMTok: number | null | undefined,\n outputRatePerMTok: number | null | undefined,\n): number {\n const inCost = inputRatePerMTok && inputTokens > 0\n ? (inputTokens / 1_000_000) * inputRatePerMTok\n : 0;\n const outCost = outputRatePerMTok && outputTokens > 0\n ? (outputTokens / 1_000_000) * outputRatePerMTok\n : 0;\n return inCost + outCost;\n}\n","import { DatabaseSync, type StatementSync } from \"node:sqlite\";\nimport { type RunnableConfig } from \"@langchain/core/runnables\";\nimport {\n BaseCheckpointSaver,\n TASKS,\n copyCheckpoint,\n maxChannelVersion,\n type Checkpoint,\n type CheckpointListOptions,\n type CheckpointMetadata,\n type CheckpointTuple,\n type PendingWrite,\n type SerializerProtocol,\n} from \"@langchain/langgraph-checkpoint\";\n\n// In-tree LangGraph checkpoint saver backed by node:sqlite.\n//\n// Schema-compatible drop-in replacement for SqliteSaver from\n// @langchain/langgraph-checkpoint-sqlite. The DDL is byte-identical so an\n// existing checkpoints.db keeps working without migration.\n\nconst VALID_FILTER_KEYS: readonly string[] = [\"source\", \"step\", \"parents\"];\n\ntype Row = Record<string, unknown>;\n\nfunction buildLatestSelect(includeCheckpointId: boolean): string {\n return `\n SELECT\n thread_id,\n checkpoint_ns,\n checkpoint_id,\n parent_checkpoint_id,\n type,\n checkpoint,\n metadata,\n (\n SELECT\n json_group_array(\n json_object(\n 'task_id', pw.task_id,\n 'channel', pw.channel,\n 'type', pw.type,\n 'value', CAST(pw.value AS TEXT)\n )\n )\n FROM writes as pw\n WHERE pw.thread_id = checkpoints.thread_id\n AND pw.checkpoint_ns = checkpoints.checkpoint_ns\n AND pw.checkpoint_id = checkpoints.checkpoint_id\n ) as pending_writes,\n (\n SELECT\n json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n )\n FROM writes as ps\n WHERE ps.thread_id = checkpoints.thread_id\n AND ps.checkpoint_ns = checkpoints.checkpoint_ns\n AND ps.checkpoint_id = checkpoints.parent_checkpoint_id\n AND ps.channel = '${TASKS}'\n ORDER BY ps.idx\n ) as pending_sends\n FROM checkpoints\n WHERE thread_id = ? AND checkpoint_ns = ? ${\n includeCheckpointId\n ? \"AND checkpoint_id = ?\"\n : \"ORDER BY checkpoint_id DESC LIMIT 1\"\n }`;\n}\n\n// node:sqlite rejects `undefined` parameter binds; coerce to null.\nfunction nv<T>(v: T | undefined | null): T | null {\n return v === undefined ? null : (v as T | null);\n}\n\nexport class NodeSqliteSaver extends BaseCheckpointSaver {\n db: DatabaseSync;\n\n protected isSetup = false;\n protected withoutCheckpoint!: StatementSync;\n protected withCheckpoint!: StatementSync;\n\n constructor(db: DatabaseSync, serde?: SerializerProtocol) {\n super(serde);\n this.db = db;\n }\n\n static fromConnString(connStringOrLocalPath: string): NodeSqliteSaver {\n return new NodeSqliteSaver(new DatabaseSync(connStringOrLocalPath));\n }\n\n protected setup(): void {\n if (this.isSetup) return;\n this.db.exec(\"PRAGMA journal_mode=WAL\");\n this.db.exec(`\nCREATE TABLE IF NOT EXISTS checkpoints (\n thread_id TEXT NOT NULL,\n checkpoint_ns TEXT NOT NULL DEFAULT '',\n checkpoint_id TEXT NOT NULL,\n parent_checkpoint_id TEXT,\n type TEXT,\n checkpoint BLOB,\n metadata BLOB,\n PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)\n);`);\n this.db.exec(`\nCREATE TABLE IF NOT EXISTS writes (\n thread_id TEXT NOT NULL,\n checkpoint_ns TEXT NOT NULL DEFAULT '',\n checkpoint_id TEXT NOT NULL,\n task_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n channel TEXT NOT NULL,\n type TEXT,\n value BLOB,\n PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)\n);`);\n this.withoutCheckpoint = this.db.prepare(buildLatestSelect(false));\n this.withCheckpoint = this.db.prepare(buildLatestSelect(true));\n this.isSetup = true;\n }\n\n protected txn<T>(fn: () => T): T {\n this.db.exec(\"BEGIN\");\n try {\n const result = fn();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (err) {\n try {\n this.db.exec(\"ROLLBACK\");\n } catch {\n // ignore rollback failure; surface the original error\n }\n throw err;\n }\n }\n\n private async decodeRowState(row: Row): Promise<{\n pendingWrites: [string, string, unknown][];\n checkpoint: Checkpoint;\n metadata: CheckpointMetadata;\n }> {\n const pendingWrites = await Promise.all(\n JSON.parse(String(row.pending_writes ?? \"[]\")).map(\n async (write: { task_id: string; channel: string; type?: string; value?: string }) => {\n return [\n write.task_id,\n write.channel,\n await this.serde.loadsTyped(write.type ?? \"json\", write.value ?? \"\"),\n ] as [string, string, unknown];\n },\n ),\n );\n\n const checkpoint = (await this.serde.loadsTyped(\n (row.type as string | null) ?? \"json\",\n row.checkpoint as Uint8Array | string,\n )) as Checkpoint;\n\n if (checkpoint.v < 4 && row.parent_checkpoint_id != null) {\n await this.migratePendingSends(\n checkpoint,\n row.thread_id as string,\n row.parent_checkpoint_id as string,\n );\n }\n\n const metadata = (await this.serde.loadsTyped(\n (row.type as string | null) ?? \"json\",\n row.metadata as Uint8Array | string,\n )) as CheckpointMetadata;\n\n return { pendingWrites, checkpoint, metadata };\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n this.setup();\n const { thread_id, checkpoint_ns = \"\", checkpoint_id } =\n config.configurable ?? {};\n const args: unknown[] = [nv(thread_id), checkpoint_ns];\n if (checkpoint_id) args.push(checkpoint_id);\n const stmt = checkpoint_id ? this.withCheckpoint : this.withoutCheckpoint;\n const row = stmt.get(...(args as never[])) as Row | undefined;\n if (row === undefined) return undefined;\n\n let finalConfig = config;\n if (!checkpoint_id) {\n finalConfig = {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns,\n checkpoint_id: row.checkpoint_id,\n },\n };\n }\n if (\n finalConfig.configurable?.thread_id === undefined ||\n finalConfig.configurable?.checkpoint_id === undefined\n ) {\n throw new Error(\"Missing thread_id or checkpoint_id\");\n }\n\n const { pendingWrites, checkpoint, metadata } = await this.decodeRowState(row);\n\n return {\n checkpoint,\n config: finalConfig,\n metadata,\n parentConfig: row.parent_checkpoint_id\n ? {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns,\n checkpoint_id: row.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\n }\n\n async *list(\n config: RunnableConfig,\n options?: CheckpointListOptions,\n ): AsyncGenerator<CheckpointTuple> {\n const { limit, before, filter } = options ?? {};\n this.setup();\n const thread_id = config.configurable?.thread_id;\n const checkpoint_ns = config.configurable?.checkpoint_ns;\n\n let sql = `\n SELECT\n thread_id,\n checkpoint_ns,\n checkpoint_id,\n parent_checkpoint_id,\n type,\n checkpoint,\n metadata,\n (\n SELECT\n json_group_array(\n json_object(\n 'task_id', pw.task_id,\n 'channel', pw.channel,\n 'type', pw.type,\n 'value', CAST(pw.value AS TEXT)\n )\n )\n FROM writes as pw\n WHERE pw.thread_id = checkpoints.thread_id\n AND pw.checkpoint_ns = checkpoints.checkpoint_ns\n AND pw.checkpoint_id = checkpoints.checkpoint_id\n ) as pending_writes,\n (\n SELECT\n json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n )\n FROM writes as ps\n WHERE ps.thread_id = checkpoints.thread_id\n AND ps.checkpoint_ns = checkpoints.checkpoint_ns\n AND ps.checkpoint_id = checkpoints.parent_checkpoint_id\n AND ps.channel = '${TASKS}'\n ORDER BY ps.idx\n ) as pending_sends\n FROM checkpoints\\n`;\n\n const whereClause: string[] = [];\n if (thread_id) whereClause.push(\"thread_id = ?\");\n if (checkpoint_ns !== undefined && checkpoint_ns !== null) {\n whereClause.push(\"checkpoint_ns = ?\");\n }\n if (before?.configurable?.checkpoint_id !== undefined) {\n whereClause.push(\"checkpoint_id < ?\");\n }\n\n const sanitizedFilter = Object.fromEntries(\n Object.entries(filter ?? {}).filter(\n ([key, value]) => value !== undefined && VALID_FILTER_KEYS.includes(key),\n ),\n );\n whereClause.push(\n ...Object.entries(sanitizedFilter).map(\n ([key]) => `jsonb(CAST(metadata AS TEXT))->'$.${key}' = ?`,\n ),\n );\n if (whereClause.length > 0) sql += `WHERE\\n ${whereClause.join(\" AND\\n \")}\\n`;\n sql += \"\\nORDER BY checkpoint_id DESC\";\n if (limit) sql += ` LIMIT ${parseInt(String(limit), 10)}`;\n\n const args: unknown[] = [\n thread_id,\n checkpoint_ns,\n before?.configurable?.checkpoint_id,\n ...Object.values(sanitizedFilter).map((value) => JSON.stringify(value)),\n ].filter((value) => value !== undefined && value !== null);\n\n const rows = this.db.prepare(sql).all(...(args as never[])) as Row[];\n for (const row of rows) {\n const { pendingWrites, checkpoint, metadata } = await this.decodeRowState(row);\n yield {\n config: {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns: row.checkpoint_ns,\n checkpoint_id: row.checkpoint_id,\n },\n },\n checkpoint,\n metadata,\n parentConfig: row.parent_checkpoint_id\n ? {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns: row.checkpoint_ns,\n checkpoint_id: row.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\n }\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n ): Promise<RunnableConfig> {\n this.setup();\n if (!config.configurable) throw new Error(\"Empty configuration supplied.\");\n const thread_id = config.configurable?.thread_id as string | undefined;\n const checkpoint_ns = (config.configurable?.checkpoint_ns ?? \"\") as string;\n const parent_checkpoint_id = config.configurable?.checkpoint_id as\n | string\n | undefined;\n if (!thread_id) {\n throw new Error(`Missing \"thread_id\" field in passed \"config.configurable\".`);\n }\n const preparedCheckpoint = copyCheckpoint(checkpoint);\n const [\n [type1, serializedCheckpoint],\n [type2, serializedMetadata],\n ] = await Promise.all([\n this.serde.dumpsTyped(preparedCheckpoint),\n this.serde.dumpsTyped(metadata),\n ]);\n if (type1 !== type2) {\n throw new Error(\n \"Failed to serialized checkpoint and metadata to the same type.\",\n );\n }\n this.db\n .prepare(\n `INSERT OR REPLACE INTO checkpoints (thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n thread_id,\n checkpoint_ns,\n checkpoint.id,\n nv(parent_checkpoint_id),\n type1,\n serializedCheckpoint as never,\n serializedMetadata as never,\n );\n return {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: checkpoint.id,\n },\n };\n }\n\n async putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string,\n ): Promise<void> {\n this.setup();\n if (!config.configurable) throw new Error(\"Empty configuration supplied.\");\n if (!config.configurable?.thread_id) {\n throw new Error(\"Missing thread_id field in config.configurable.\");\n }\n if (!config.configurable?.checkpoint_id) {\n throw new Error(\"Missing checkpoint_id field in config.configurable.\");\n }\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO writes\n (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const rows = await Promise.all(\n writes.map(async (write, idx) => {\n const [type, serializedWrite] = await this.serde.dumpsTyped(write[1]);\n return [\n config.configurable?.thread_id as string,\n (config.configurable?.checkpoint_ns ?? \"\") as string,\n config.configurable?.checkpoint_id as string,\n taskId,\n idx,\n write[0],\n type,\n serializedWrite,\n ] as const;\n }),\n );\n this.txn(() => {\n for (const row of rows) stmt.run(...(row as unknown as never[]));\n });\n }\n\n async deleteThread(threadId: string): Promise<void> {\n this.setup();\n this.txn(() => {\n this.db\n .prepare(`DELETE FROM checkpoints WHERE thread_id = ?`)\n .run(threadId);\n this.db.prepare(`DELETE FROM writes WHERE thread_id = ?`).run(threadId);\n });\n }\n\n protected async migratePendingSends(\n checkpoint: Checkpoint,\n threadId: string,\n parentCheckpointId: string,\n ): Promise<void> {\n const row = this.db\n .prepare(\n `\n SELECT\n checkpoint_id,\n json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n ) as pending_sends\n FROM writes as ps\n WHERE ps.thread_id = ?\n AND ps.checkpoint_id = ?\n AND ps.channel = '${TASKS}'\n ORDER BY ps.idx\n `,\n )\n .get(threadId, parentCheckpointId) as Row | undefined;\n const pendingSendsJson = String(row?.pending_sends ?? \"[]\");\n const mutable = checkpoint as Checkpoint & {\n channel_values?: Record<string, unknown>;\n channel_versions: Record<string, unknown>;\n };\n mutable.channel_values ??= {};\n mutable.channel_values[TASKS] = await Promise.all(\n JSON.parse(pendingSendsJson).map(\n ({ type, value }: { type?: string; value?: string }) =>\n this.serde.loadsTyped(type ?? \"json\", value ?? \"\"),\n ),\n );\n mutable.channel_versions[TASKS] =\n Object.keys(checkpoint.channel_versions).length > 0\n ? maxChannelVersion(...Object.values(checkpoint.channel_versions))\n : this.getNextVersion(undefined);\n }\n}\n","import { join } from \"node:path\";\nimport { getDataDir } from \"@/lib/db/data-dir\";\nimport { NodeSqliteSaver } from \"./sqlite-checkpoint-saver\";\n\n// Persistent graph state per thread_id. Multi-step plans, scratchpad messages,\n// and any pending tool-call sequence survive process restarts and resume on\n// the next agent.stream() with the same thread_id.\n//\n// Stored in a separate DB file (~/.jarela/checkpoints.db) so LangGraph\n// manages its own schema independently of our own migrations under\n// lib/db/. Both files use WAL mode via the same node:sqlite driver.\n\nconst CHECKPOINT_PATH = join(getDataDir(), \"checkpoints.db\");\n\nlet _saver: NodeSqliteSaver | null = null;\n\nexport function getCheckpointer(): NodeSqliteSaver {\n if (!_saver) {\n _saver = NodeSqliteSaver.fromConnString(CHECKPOINT_PATH);\n }\n return _saver;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { getDb } from \"@/lib/db\";\nimport { embedOne } from \"@/lib/embeddings\";\n\nconst now = () => new Date().toISOString();\n\n// Explicit column list for message reads — omits `embedding` (~20KB of\n// JSON-encoded float[] per row) which only the embeddings module reads.\n// Avoids dragging it through the chat-history result set on every call.\nconst MSG_COLS_SQL = \"SELECT msg_id, thread_id, role, content, created_at, tool_events, category, metadata FROM messages\";\n\nexport interface ThreadRow {\n thread_id: string; agent_id: string; title: string | null;\n created_at: string; updated_at: string; message_count: number;\n // ADR-0042 — explicit context pin + cached warm summary. NULL on threads\n // that haven't had the boundary moved away from the agent default. The\n // summary is fresh only when warm_summary_before === hot_since.\n hot_since?: string | null;\n warm_summary?: string | null;\n warm_summary_before?: string | null;\n warm_summary_computed_at?: string | null;\n}\nexport interface MessageRow {\n msg_id: string; thread_id: string; role: string; content: string; created_at: string;\n // JSON-encoded array of PersistedToolEvent. null when no tool work happened\n // on this turn or for user messages. Read back by the chat UI so historical\n // bubbles show the same expandable CALL/RESULT entries as live streaming.\n tool_events?: string | null;\n // Non-null tags classify the message into a filterable group in the chat\n // panel (e.g. 'scheduled_task', 'bridge', 'synthetic'). NULL = ordinary\n // user/assistant chat content.\n category?: string | null;\n // JSON-encoded auxiliary per-message data. NULL on legacy rows. Currently\n // carries the citation-checker verdict when `require_source_links` is on.\n metadata?: string | null;\n}\n\nexport interface PersistedToolEvent {\n id: string;\n phase: \"call\" | \"result\";\n name: string;\n payload: unknown;\n}\n\nexport function listThreads(limit = 50, offset = 0): ThreadRow[] {\n return getDb()\n .prepare(\"SELECT * FROM threads ORDER BY updated_at DESC LIMIT ? OFFSET ?\")\n .all(limit, offset) as unknown as ThreadRow[];\n}\n\nexport function listThreadsByAgent(agent_id: string, limit = 50): ThreadRow[] {\n return getDb()\n .prepare(\"SELECT * FROM threads WHERE agent_id=? ORDER BY updated_at DESC LIMIT ?\")\n .all(agent_id, limit) as unknown as ThreadRow[];\n}\n\nexport function getThread(thread_id: string): ThreadRow | null {\n return (getDb().prepare(\"SELECT * FROM threads WHERE thread_id=?\").get(thread_id) as unknown as ThreadRow) ?? null;\n}\n\nexport function createThread(agent_id: string, title?: string): ThreadRow {\n const t = now();\n const thread_id = randomUUID();\n getDb()\n .prepare(\"INSERT INTO threads (thread_id,agent_id,title,created_at,updated_at,message_count) VALUES (?,?,?,?,?,0)\")\n .run(thread_id, agent_id, title ?? null, t, t);\n return { thread_id, agent_id, title: title ?? null, created_at: t, updated_at: t, message_count: 0 };\n}\n\nexport function deleteThread(thread_id: string): boolean {\n const db = getDb();\n db.prepare(\"DELETE FROM messages WHERE thread_id=?\").run(thread_id);\n const r = db.prepare(\"DELETE FROM threads WHERE thread_id=?\").run(thread_id);\n return r.changes > 0;\n}\n\nexport function getMessages(thread_id: string): MessageRow[] {\n return getDb()\n .prepare(MSG_COLS_SQL + \" WHERE thread_id=? ORDER BY created_at ASC\")\n .all(thread_id) as unknown as MessageRow[];\n}\n\n// Pull the latest N messages within a time window. Used to build the LLM\n// context — keeps prompt size bounded as threads grow indefinitely.\n// limit: 0 or negative = unlimited\n// sinceISO: undefined = no time bound\n// Returns chronological order (oldest first) so it can be appended to the prompt directly.\nexport function getRecentMessagesWindow(\n thread_id: string,\n limit: number,\n sinceISO?: string,\n): MessageRow[] {\n const db = getDb();\n const params: (string | number)[] = [thread_id];\n let sql = MSG_COLS_SQL + \" WHERE thread_id=?\";\n if (sinceISO) {\n sql += \" AND created_at >= ?\";\n params.push(sinceISO);\n }\n sql += \" ORDER BY created_at DESC\";\n if (limit > 0) {\n sql += \" LIMIT ?\";\n params.push(limit);\n }\n const rows = db.prepare(sql).all(...params) as unknown as MessageRow[];\n return rows.reverse();\n}\n\n// Forward-fetch — return messages strictly newer than `afterISO`, oldest\n// first, capped at `limit`. Used by the chat view to pull only the\n// freshly-persisted user+assistant pair after a run completes, instead of\n// re-fetching the whole most-recent page.\nexport function getMessagesAfter(\n thread_id: string,\n afterISO: string,\n limit = 50,\n): MessageRow[] {\n return getDb()\n .prepare(\n MSG_COLS_SQL +\n \" WHERE thread_id=? AND created_at > ? ORDER BY created_at ASC LIMIT ?\",\n )\n .all(thread_id, afterISO, limit) as unknown as MessageRow[];\n}\n\n// Pagination for the chat UI. Returns the latest N messages strictly older\n// than `beforeISO` (cursor). Caller passes the oldest already-loaded message's\n// created_at as the cursor; first page omits beforeISO.\nexport function getMessagesPage(\n thread_id: string,\n limit: number,\n beforeISO?: string,\n): { messages: MessageRow[]; has_more: boolean } {\n const db = getDb();\n const params: (string | number)[] = [thread_id];\n let sql = MSG_COLS_SQL + \" WHERE thread_id=?\";\n if (beforeISO) {\n sql += \" AND created_at < ?\";\n params.push(beforeISO);\n }\n sql += \" ORDER BY created_at DESC LIMIT ?\";\n params.push(limit + 1); // fetch one extra to detect if there's more\n const rows = db.prepare(sql).all(...params) as unknown as MessageRow[];\n const has_more = rows.length > limit;\n return { messages: rows.slice(0, limit).reverse(), has_more };\n}\n\nexport function addMessage(\n thread_id: string,\n role: \"user\" | \"assistant\",\n content: string,\n toolEvents?: PersistedToolEvent[] | null,\n category: string | null = null,\n metadata?: Record<string, unknown> | null,\n): MessageRow {\n const msg_id = randomUUID();\n const t = now();\n const db = getDb();\n const toolEventsJson = toolEvents && toolEvents.length > 0 ? JSON.stringify(toolEvents) : null;\n const metadataJson = metadata && Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null;\n db.prepare(\"INSERT INTO messages (msg_id,thread_id,role,content,created_at,tool_events,category,metadata) VALUES (?,?,?,?,?,?,?,?)\")\n .run(msg_id, thread_id, role, content, t, toolEventsJson, category, metadataJson);\n db.prepare(\"UPDATE threads SET message_count=message_count+1 WHERE thread_id=?\").run(thread_id);\n // Best-effort: embed the message so semantic recall can pull it back later.\n // Skip empty / very short content (greetings have no useful signal).\n if (content.trim().length >= 12) {\n embedOne(content).then((vec) => {\n if (vec) {\n getDb().prepare(\"UPDATE messages SET embedding=? WHERE msg_id=?\").run(JSON.stringify(vec), msg_id);\n }\n }).catch(() => { /* logged in embeddings module */ });\n }\n return { msg_id, thread_id, role, content, created_at: t, tool_events: toolEventsJson, category, metadata: metadataJson };\n}\n\n// Replace a single message's metadata blob. Used by the citation checker\n// to write its verdict back after the assistant turn has already been\n// persisted (the checker runs async post-insert so the chat UI sees the\n// content immediately and picks up the metadata on the next refresh).\nexport function setMessageMetadata(msg_id: string, metadata: Record<string, unknown> | null): void {\n const json = metadata && Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null;\n getDb().prepare(\"UPDATE messages SET metadata=? WHERE msg_id=?\").run(json, msg_id);\n}\n\nexport function getOrCreateAgentThread(agentId: string): ThreadRow {\n const existing = getDb()\n .prepare(\"SELECT * FROM threads WHERE agent_id=? LIMIT 1\")\n .get(agentId) as ThreadRow | undefined;\n if (existing) return existing;\n return createThread(agentId);\n}\n\nexport function clearThreadMessages(threadId: string): void {\n const db = getDb();\n db.prepare(\"DELETE FROM messages WHERE thread_id=?\").run(threadId);\n db.prepare(\"UPDATE threads SET message_count=0, updated_at=? WHERE thread_id=?\")\n .run(new Date().toISOString(), threadId);\n}\n\nexport function touchThread(thread_id: string, firstMsg?: string): void {\n const t = now();\n getDb()\n .prepare(\"UPDATE threads SET updated_at=?, title=COALESCE(title,?) WHERE thread_id=?\")\n .run(t, firstMsg ? firstMsg.slice(0, 80) : null, thread_id);\n}\n\n// ADR-0042. Move the user's explicit boundary between hot and warm context.\n// Pass `null` to clear the pin and let the agent's default window apply\n// again. Persisting the pin here keeps it stable across reloads and devices.\nexport function setThreadContextPin(thread_id: string, hot_since: string | null): void {\n getDb()\n .prepare(\"UPDATE threads SET hot_since=? WHERE thread_id=?\")\n .run(hot_since, thread_id);\n}\n\n// Cache the latest warm-tier summary alongside the boundary it covers. The\n// chat UI considers the summary fresh only when `warm_summary_before` matches\n// the current `hot_since`; any boundary change triggers a re-summarise on the\n// next run rather than a synchronous LLM call here.\nexport function setThreadWarmSummary(\n thread_id: string,\n summary: string,\n before: string | null,\n): void {\n getDb()\n .prepare(\n \"UPDATE threads SET warm_summary=?, warm_summary_before=?, warm_summary_computed_at=? WHERE thread_id=?\",\n )\n .run(summary, before, now(), thread_id);\n}\n"],"names":["getDb","recordMessageUsage","input","db","t","tier_usage","prepare","run","message_id","thread_id","agent_id","agent_name","provider","model_id","model_config_name","input_tokens","output_tokens","input_rate_usd_per_mtok","output_rate_usd_per_mtok","cost_usd","Date","toISOString","hot_tokens","warm_tokens","facts_tokens","overhead_tokens","hot_budget_tokens","warm_budget_tokens","facts_budget_tokens","context_window_tokens","getMessageUsage","messageId","get","getMessageUsageByIds","messageIds","out","Map","length","placeholders","map","join","rows","all","row","set","computeCostUsd","inputTokens","outputTokens","inputRatePerMTok","outputRatePerMTok","inCost","outCost","DatabaseSync","BaseCheckpointSaver","TASKS","copyCheckpoint","maxChannelVersion","VALID_FILTER_KEYS","buildLatestSelect","includeCheckpointId","nv","v","undefined","NodeSqliteSaver","serde","isSetup","fromConnString","connStringOrLocalPath","setup","exec","withoutCheckpoint","withCheckpoint","txn","fn","result","err","decodeRowState","pendingWrites","Promise","JSON","parse","String","pending_writes","write","task_id","channel","loadsTyped","type","value","checkpoint","parent_checkpoint_id","migratePendingSends","metadata","getTuple","config","checkpoint_ns","checkpoint_id","configurable","args","push","stmt","finalConfig","Error","parentConfig","list","options","limit","before","filter","sql","whereClause","sanitizedFilter","Object","fromEntries","entries","key","includes","parseInt","values","stringify","put","preparedCheckpoint","type1","serializedCheckpoint","type2","serializedMetadata","dumpsTyped","id","putWrites","writes","taskId","idx","serializedWrite","deleteThread","threadId","parentCheckpointId","pendingSendsJson","pending_sends","mutable","channel_values","channel_versions","keys","getNextVersion","getDataDir","CHECKPOINT_PATH","_saver","getCheckpointer","randomUUID","embedOne","now","MSG_COLS_SQL","listThreads","offset","listThreadsByAgent","getThread","createThread","title","created_at","updated_at","message_count","r","changes","getMessages","getRecentMessagesWindow","sinceISO","params","reverse","getMessagesAfter","afterISO","getMessagesPage","beforeISO","has_more","messages","slice","addMessage","role","content","toolEvents","category","msg_id","toolEventsJson","metadataJson","trim","then","vec","catch","tool_events","setMessageMetadata","json","getOrCreateAgentThread","agentId","existing","clearThreadMessages","touchThread","firstMsg","setThreadContextPin","hot_since","setThreadWarmSummary","summary"],"sourceRoot":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"5937.js","mappings":";;;;;;;;;;;;;;AAAA,gEAAgE;AAChE,EAAE;AACF,wDAAwD;AACxD,yEAAyE;AACzE,uEAAuE;AACvE,8DAA8D;AAE7B;AAqD1B,SAASC,mBAAmBC,KAAwB;IACzD,MAAMC,KAAKH,wDAAKA;IAChB,MAAMI,IAAIF,MAAMG,UAAU,IAAI;IAC9BF,GAAGG,OAAO,CACR,CAAC;;;;;;;6DAOwD,CAAC,EAC1DC,GAAG,CACHL,MAAMM,UAAU,EAChBN,MAAMO,SAAS,EACfP,MAAMQ,QAAQ,EACdR,MAAMS,UAAU,EAChBT,MAAMU,QAAQ,EACdV,MAAMW,QAAQ,EACdX,MAAMY,iBAAiB,EACvBZ,MAAMa,YAAY,EAClBb,MAAMc,aAAa,EACnBd,MAAMe,uBAAuB,EAC7Bf,MAAMgB,wBAAwB,EAC9BhB,MAAMiB,QAAQ,EACd,IAAIC,OAAOC,WAAW,IACtBjB,GAAGkB,cAAc,MACjBlB,GAAGmB,eAAe,MAClBnB,GAAGoB,gBAAgB,MACnBpB,GAAGqB,mBAAmB,MACtBrB,GAAGsB,qBAAqB,MACxBtB,GAAGuB,sBAAsB,MACzBvB,GAAGwB,uBAAuB,MAC1BxB,GAAGyB,yBAAyB,MAC5B3B,MAAM4B,2BAA2B,IAAI,MACrC5B,MAAM6B,uBAAuB,IAAI;AAErC;AAEO,SAASC,gBAAgBC,SAAiB;IAC/C,OAAO,QACJ3B,OAAO,CAAC,kDACR4B,GAAG,CAACD,cAA8C;AACvD;AAEA;;;;;CAKC,GACM,SAASE,qBAAqBC,UAA6B;IAChE,MAAMC,MAAM,IAAIC;IAChB,IAAIF,WAAWG,MAAM,KAAK,GAAG,OAAOF;IACpC,MAAMG,eAAeJ,WAAWK,GAAG,CAAC,IAAM,KAAKC,IAAI,CAAC;IACpD,MAAMC,OAAO3C,wDAAKA,GACfM,OAAO,CAAC,CAAC,iDAAiD,EAAEkC,aAAa,CAAC,CAAC,EAC3EI,GAAG,IAAIR;IACV,KAAK,MAAMS,OAAOF,KAAMN,IAAIS,GAAG,CAACD,IAAIrC,UAAU,EAAEqC;IAChD,OAAOR;AACT;AAEO,SAASU,eACdC,WAAmB,EACnBC,YAAoB,EACpBC,gBAA2C,EAC3CC,iBAA4C;IAE5C,MAAMC,SAASF,oBAAoBF,cAAc,IAC7C,cAAe,UAAaE,mBAC5B;IACJ,MAAMG,UAAUF,qBAAqBF,eAAe,IAChD,eAAgB,UAAaE,oBAC7B;IACJ,OAAOC,SAASC;AAClB;;;;;;;;;;;;;;;;;;;;;;;ACvI+D;AAatB;AAEzC,4DAA4D;AAC5D,EAAE;AACF,6DAA6D;AAC7D,0EAA0E;AAC1E,2DAA2D;AAE3D,MAAMM,oBAAuC;IAAC;IAAU;IAAQ;CAAU;AAI1E,SAASC,kBAAkBC,mBAA4B;IACrD,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAoCgB,EAAEL,kBAAKA,CAAC;;;;4CAIU,EACxCK,sBACI,0BACA,uCACJ;AACJ;AAEA,mEAAmE;AACnE,SAASC,GAAMC,CAAuB;IACpC,OAAOA,MAAMC,YAAY,OAAQD;AACnC;AAEO,MAAME,wBAAwBV,gCAAmBA;IAOtD,YAAYpD,EAAgB,EAAE+D,KAA0B,CAAE;QACxD,KAAK,CAACA,aALEC,UAAU;QAMlB,IAAI,CAAChE,EAAE,GAAGA;IACZ;IAEA,OAAOiE,eAAeC,qBAA6B,EAAmB;QACpE,OAAO,IAAIJ,gBAAgB,IAAIX,kCAAYA,CAACe;IAC9C;IAEUC,QAAc;QACtB,IAAI,IAAI,CAACH,OAAO,EAAE;QAClB,IAAI,CAAChE,EAAE,CAACoE,IAAI,CAAC;QACb,IAAI,CAACpE,EAAE,CAACoE,IAAI,CAAC,CAAC;;;;;;;;;;EAUhB,CAAC;QACC,IAAI,CAACpE,EAAE,CAACoE,IAAI,CAAC,CAAC;;;;;;;;;;;EAWhB,CAAC;QACC,IAAI,CAACC,iBAAiB,GAAG,IAAI,CAACrE,EAAE,CAACG,OAAO,CAACsD,kBAAkB;QAC3D,IAAI,CAACa,cAAc,GAAG,IAAI,CAACtE,EAAE,CAACG,OAAO,CAACsD,kBAAkB;QACxD,IAAI,CAACO,OAAO,GAAG;IACjB;IAEUO,IAAOC,EAAW,EAAK;QAC/B,IAAI,CAACxE,EAAE,CAACoE,IAAI,CAAC;QACb,IAAI;YACF,MAAMK,SAASD;YACf,IAAI,CAACxE,EAAE,CAACoE,IAAI,CAAC;YACb,OAAOK;QACT,EAAE,OAAOC,KAAK;YACZ,IAAI;gBACF,IAAI,CAAC1E,EAAE,CAACoE,IAAI,CAAC;YACf,EAAE,OAAM;YACN,sDAAsD;YACxD;YACA,MAAMM;QACR;IACF;IAEA,MAAcC,eAAejC,GAAQ,EAIlC;QACD,MAAMkC,gBAAgB,MAAMC,QAAQpC,GAAG,CACrCqC,KAAKC,KAAK,CAACC,OAAOtC,IAAIuC,cAAc,IAAI,OAAO3C,GAAG,CAChD,OAAO4C;YACL,OAAO;gBACLA,MAAMC,OAAO;gBACbD,MAAME,OAAO;gBACb,MAAM,IAAI,CAACrB,KAAK,CAACsB,UAAU,CAACH,MAAMI,IAAI,IAAI,QAAQJ,MAAMK,KAAK,IAAI;aAClE;QACH;QAIJ,MAAMC,aAAc,MAAM,IAAI,CAACzB,KAAK,CAACsB,UAAU,CAC7C,IAAKC,IAAI,IAAsB,QAC/B5C,IAAI8C,UAAU;QAGhB,IAAIA,WAAW5B,CAAC,GAAG,KAAKlB,IAAI+C,oBAAoB,IAAI,MAAM;YACxD,MAAM,IAAI,CAACC,mBAAmB,CAC5BF,YACA9C,IAAIpC,SAAS,EACboC,IAAI+C,oBAAoB;QAE5B;QAEA,MAAME,WAAY,MAAM,IAAI,CAAC5B,KAAK,CAACsB,UAAU,CAC3C,IAAKC,IAAI,IAAsB,QAC/B5C,IAAIiD,QAAQ;QAGd,OAAO;YAAEf;YAAeY;YAAYG;QAAS;IAC/C;IAEA,MAAMC,SAASC,MAAsB,EAAwC;QAC3E,IAAI,CAAC1B,KAAK;QACV,MAAM,EAAE7D,SAAS,EAAEwF,gBAAgB,EAAE,EAAEC,aAAa,EAAE,GACpDF,OAAOG,YAAY,IAAI,CAAC;QAC1B,MAAMC,OAAkB;YAACtC,GAAGrD;YAAYwF;SAAc;QACtD,IAAIC,eAAeE,KAAKC,IAAI,CAACH;QAC7B,MAAMI,OAAOJ,gBAAgB,IAAI,CAACzB,cAAc,GAAG,IAAI,CAACD,iBAAiB;QACzE,MAAM3B,MAAMyD,KAAKpE,GAAG,IAAKkE;QACzB,IAAIvD,QAAQmB,WAAW,OAAOA;QAE9B,IAAIuC,cAAcP;QAClB,IAAI,CAACE,eAAe;YAClBK,cAAc;gBACZJ,cAAc;oBACZ1F,WAAWoC,IAAIpC,SAAS;oBACxBwF;oBACAC,eAAerD,IAAIqD,aAAa;gBAClC;YACF;QACF;QACA,IACEK,YAAYJ,YAAY,EAAE1F,cAAcuD,aACxCuC,YAAYJ,YAAY,EAAED,kBAAkBlC,WAC5C;YACA,MAAM,IAAIwC,MAAM;QAClB;QAEA,MAAM,EAAEzB,aAAa,EAAEY,UAAU,EAAEG,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAChB,cAAc,CAACjC;QAE1E,OAAO;YACL8C;YACAK,QAAQO;YACRT;YACAW,cAAc5D,IAAI+C,oBAAoB,GAClC;gBACEO,cAAc;oBACZ1F,WAAWoC,IAAIpC,SAAS;oBACxBwF;oBACAC,eAAerD,IAAI+C,oBAAoB;gBACzC;YACF,IACA5B;YACJe;QACF;IACF;IAEA,OAAO2B,KACLV,MAAsB,EACtBW,OAA+B,EACE;QACjC,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAEC,MAAM,EAAE,GAAGH,WAAW,CAAC;QAC9C,IAAI,CAACrC,KAAK;QACV,MAAM7D,YAAYuF,OAAOG,YAAY,EAAE1F;QACvC,MAAMwF,gBAAgBD,OAAOG,YAAY,EAAEF;QAE3C,IAAIc,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAoCe,EAAEvD,kBAAKA,CAAC;;;wBAGd,CAAC;QAErB,MAAMwD,cAAwB,EAAE;QAChC,IAAIvG,WAAWuG,YAAYX,IAAI,CAAC;QAChC,IAAIJ,kBAAkBjC,aAAaiC,kBAAkB,MAAM;YACzDe,YAAYX,IAAI,CAAC;QACnB;QACA,IAAIQ,QAAQV,cAAcD,kBAAkBlC,WAAW;YACrDgD,YAAYX,IAAI,CAAC;QACnB;QAEA,MAAMY,kBAAkBC,OAAOC,WAAW,CACxCD,OAAOE,OAAO,CAACN,UAAU,CAAC,GAAGA,MAAM,CACjC,CAAC,CAACO,KAAK3B,MAAM,GAAKA,UAAU1B,aAAaL,kBAAkB2D,QAAQ,CAACD;QAGxEL,YAAYX,IAAI,IACXa,OAAOE,OAAO,CAACH,iBAAiBxE,GAAG,CACpC,CAAC,CAAC4E,IAAI,GAAK,CAAC,kCAAkC,EAAEA,IAAI,KAAK,CAAC;QAG9D,IAAIL,YAAYzE,MAAM,GAAG,GAAGwE,OAAO,CAAC,SAAS,EAAEC,YAAYtE,IAAI,CAAC,YAAY,EAAE,CAAC;QAC/EqE,OAAO;QACP,IAAIH,OAAOG,OAAO,CAAC,OAAO,EAAEQ,SAASpC,OAAOyB,QAAQ,KAAK;QAEzD,MAAMR,OAAkB;YACtB3F;YACAwF;YACAY,QAAQV,cAAcD;eACnBgB,OAAOM,MAAM,CAACP,iBAAiBxE,GAAG,CAAC,CAACiD,QAAUT,KAAKwC,SAAS,CAAC/B;SACjE,CAACoB,MAAM,CAAC,CAACpB,QAAUA,UAAU1B,aAAa0B,UAAU;QAErD,MAAM/C,OAAO,IAAI,CAACxC,EAAE,CAACG,OAAO,CAACyG,KAAKnE,GAAG,IAAKwD;QAC1C,KAAK,MAAMvD,OAAOF,KAAM;YACtB,MAAM,EAAEoC,aAAa,EAAEY,UAAU,EAAEG,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAChB,cAAc,CAACjC;YAC1E,MAAM;gBACJmD,QAAQ;oBACNG,cAAc;wBACZ1F,WAAWoC,IAAIpC,SAAS;wBACxBwF,eAAepD,IAAIoD,aAAa;wBAChCC,eAAerD,IAAIqD,aAAa;oBAClC;gBACF;gBACAP;gBACAG;gBACAW,cAAc5D,IAAI+C,oBAAoB,GAClC;oBACEO,cAAc;wBACZ1F,WAAWoC,IAAIpC,SAAS;wBACxBwF,eAAepD,IAAIoD,aAAa;wBAChCC,eAAerD,IAAI+C,oBAAoB;oBACzC;gBACF,IACA5B;gBACJe;YACF;QACF;IACF;IAEA,MAAM2C,IACJ1B,MAAsB,EACtBL,UAAsB,EACtBG,QAA4B,EACH;QACzB,IAAI,CAACxB,KAAK;QACV,IAAI,CAAC0B,OAAOG,YAAY,EAAE,MAAM,IAAIK,MAAM;QAC1C,MAAM/F,YAAYuF,OAAOG,YAAY,EAAE1F;QACvC,MAAMwF,gBAAiBD,OAAOG,YAAY,EAAEF,iBAAiB;QAC7D,MAAML,uBAAuBI,OAAOG,YAAY,EAAED;QAGlD,IAAI,CAACzF,WAAW;YACd,MAAM,IAAI+F,MAAM,CAAC,0DAA0D,CAAC;QAC9E;QACA,MAAMmB,qBAAqBlE,+BAAcA,CAACkC;QAC1C,MAAM,CACJ,CAACiC,OAAOC,qBAAqB,EAC7B,CAACC,OAAOC,mBAAmB,CAC5B,GAAG,MAAM/C,QAAQpC,GAAG,CAAC;YACpB,IAAI,CAACsB,KAAK,CAAC8D,UAAU,CAACL;YACtB,IAAI,CAACzD,KAAK,CAAC8D,UAAU,CAAClC;SACvB;QACD,IAAI8B,UAAUE,OAAO;YACnB,MAAM,IAAItB,MACR;QAEJ;QACA,IAAI,CAACrG,EAAE,CACJG,OAAO,CACN,CAAC,2JAA2J,CAAC,EAE9JC,GAAG,CACFE,WACAwF,eACAN,WAAWsC,EAAE,EACbnE,GAAG8B,uBACHgC,OACAC,sBACAE;QAEJ,OAAO;YACL5B,cAAc;gBACZ1F;gBACAwF;gBACAC,eAAeP,WAAWsC,EAAE;YAC9B;QACF;IACF;IAEA,MAAMC,UACJlC,MAAsB,EACtBmC,MAAsB,EACtBC,MAAc,EACC;QACf,IAAI,CAAC9D,KAAK;QACV,IAAI,CAAC0B,OAAOG,YAAY,EAAE,MAAM,IAAIK,MAAM;QAC1C,IAAI,CAACR,OAAOG,YAAY,EAAE1F,WAAW;YACnC,MAAM,IAAI+F,MAAM;QAClB;QACA,IAAI,CAACR,OAAOG,YAAY,EAAED,eAAe;YACvC,MAAM,IAAIM,MAAM;QAClB;QACA,MAAMF,OAAO,IAAI,CAACnG,EAAE,CAACG,OAAO,CAAC,CAAC;;;;IAI9B,CAAC;QACD,MAAMqC,OAAO,MAAMqC,QAAQpC,GAAG,CAC5BuF,OAAO1F,GAAG,CAAC,OAAO4C,OAAOgD;YACvB,MAAM,CAAC5C,MAAM6C,gBAAgB,GAAG,MAAM,IAAI,CAACpE,KAAK,CAAC8D,UAAU,CAAC3C,KAAK,CAAC,EAAE;YACpE,OAAO;gBACLW,OAAOG,YAAY,EAAE1F;gBACpBuF,OAAOG,YAAY,EAAEF,iBAAiB;gBACvCD,OAAOG,YAAY,EAAED;gBACrBkC;gBACAC;gBACAhD,KAAK,CAAC,EAAE;gBACRI;gBACA6C;aACD;QACH;QAEF,IAAI,CAAC5D,GAAG,CAAC;YACP,KAAK,MAAM7B,OAAOF,KAAM2D,KAAK/F,GAAG,IAAKsC;QACvC;IACF;IAEA,MAAM0F,aAAaC,QAAgB,EAAiB;QAClD,IAAI,CAAClE,KAAK;QACV,IAAI,CAACI,GAAG,CAAC;YACP,IAAI,CAACvE,EAAE,CACJG,OAAO,CAAC,CAAC,2CAA2C,CAAC,EACrDC,GAAG,CAACiI;YACP,IAAI,CAACrI,EAAE,CAACG,OAAO,CAAC,CAAC,sCAAsC,CAAC,EAAEC,GAAG,CAACiI;QAChE;IACF;IAEA,MAAgB3C,oBACdF,UAAsB,EACtB6C,QAAgB,EAChBC,kBAA0B,EACX;QACf,MAAM5F,MAAM,IAAI,CAAC1C,EAAE,CAChBG,OAAO,CACN,CAAC;;;;;;;;;;;;8BAYqB,EAAEkD,kBAAKA,CAAC;;QAE9B,CAAC,EAEFtB,GAAG,CAACsG,UAAUC;QACjB,MAAMC,mBAAmBvD,OAAOtC,KAAK8F,iBAAiB;QACtD,MAAMC,UAAUjD;QAIhBiD,QAAQC,cAAc,KAAK,CAAC;QAC5BD,QAAQC,cAAc,CAACrF,kBAAKA,CAAC,GAAG,MAAMwB,QAAQpC,GAAG,CAC/CqC,KAAKC,KAAK,CAACwD,kBAAkBjG,GAAG,CAC9B,CAAC,EAAEgD,IAAI,EAAEC,KAAK,EAAqC,GACjD,IAAI,CAACxB,KAAK,CAACsB,UAAU,CAACC,QAAQ,QAAQC,SAAS;QAGrDkD,QAAQE,gBAAgB,CAACtF,kBAAKA,CAAC,GAC7B0D,OAAO6B,IAAI,CAACpD,WAAWmD,gBAAgB,EAAEvG,MAAM,GAAG,IAC9CmB,kCAAiBA,IAAIwD,OAAOM,MAAM,CAAC7B,WAAWmD,gBAAgB,KAC9D,IAAI,CAACE,cAAc,CAAChF;IAC5B;AACF;;;ACvdiC;AACc;AACa;AAE5D,+EAA+E;AAC/E,4EAA4E;AAC5E,mDAAmD;AACnD,EAAE;AACF,uEAAuE;AACvE,mEAAmE;AACnE,oEAAoE;AAEpE,MAAMkF,kBAAkBxG,4BAAIA,CAACuG,8BAAUA,IAAI;AAE3C,IAAIE,SAAiC;AAE9B,SAASC;IACd,IAAI,CAACD,QAAQ;QACXA,SAASlF,eAAeA,CAACG,cAAc,CAAC8E;IAC1C;IACA,OAAOC;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrByC;AACR;AACW;AAE5C,MAAMI,MAAM,IAAM,IAAInI,OAAOC,WAAW;AAExC,uEAAuE;AACvE,wEAAwE;AACxE,wEAAwE;AACxE,MAAMmI,eAAe;AAmCd,SAASC,YAAY7C,QAAQ,EAAE,EAAE8C,SAAS,CAAC;IAChD,OAAO1J,wDAAKA,GACTM,OAAO,CAAC,mEACRsC,GAAG,CAACgE,OAAO8C;AAChB;AAEO,SAASC,mBAAmBjJ,QAAgB,EAAEkG,QAAQ,EAAE;IAC7D,OAAO5G,wDAAKA,GACTM,OAAO,CAAC,2EACRsC,GAAG,CAAClC,UAAUkG;AACnB;AAEO,SAASgD,UAAUnJ,SAAiB;IACzC,OAAO,2DAASH,OAAO,CAAC,2CAA2C4B,GAAG,CAACzB,cAAuC;AAChH;AAEO,SAASoJ,aAAanJ,QAAgB,EAAEoJ,KAAc;IAC3D,MAAM1J,IAAImJ;IACV,MAAM9I,YAAY4I,uDAAUA;IAC5BrJ,wDAAKA,GACFM,OAAO,CAAC,2GACRC,GAAG,CAACE,WAAWC,UAAUoJ,SAAS,MAAM1J,GAAGA;IAC9C,OAAO;QAAEK;QAAWC;QAAUoJ,OAAOA,SAAS;QAAMC,YAAY3J;QAAG4J,YAAY5J;QAAG6J,eAAe;IAAE;AACrG;AAEO,SAAS1B,aAAa9H,SAAiB;IAC5C,MAAMN,KAAKH,wDAAKA;IAChBG,GAAGG,OAAO,CAAC,0CAA0CC,GAAG,CAACE;IACzD,MAAMyJ,IAAI/J,GAAGG,OAAO,CAAC,yCAAyCC,GAAG,CAACE;IAClE,OAAOyJ,EAAEC,OAAO,GAAG;AACrB;AAEO,SAASC,YAAY3J,SAAiB;IAC3C,OAAOT,wDAAKA,GACTM,OAAO,CAACkJ,eAAe,8CACvB5G,GAAG,CAACnC;AACT;AAEA,yEAAyE;AACzE,oEAAoE;AACpE,qCAAqC;AACrC,wCAAwC;AACxC,2FAA2F;AACpF,SAAS4J,wBACd5J,SAAiB,EACjBmG,KAAa,EACb0D,QAAiB;IAEjB,MAAMnK,KAAKH,wDAAKA;IAChB,MAAMuK,SAA8B;QAAC9J;KAAU;IAC/C,IAAIsG,MAAMyC,eAAe;IACzB,IAAIc,UAAU;QACZvD,OAAO;QACPwD,OAAOlE,IAAI,CAACiE;IACd;IACAvD,OAAO;IACP,IAAIH,QAAQ,GAAG;QACbG,OAAO;QACPwD,OAAOlE,IAAI,CAACO;IACd;IACA,MAAMjE,OAAOxC,GAAGG,OAAO,CAACyG,KAAKnE,GAAG,IAAI2H;IACpC,OAAO5H,KAAK6H,OAAO;AACrB;AAEA,yEAAyE;AACzE,mEAAmE;AACnE,0EAA0E;AAC1E,0CAA0C;AACnC,SAASC,iBACdhK,SAAiB,EACjBiK,QAAgB,EAChB9D,QAAQ,EAAE;IAEV,OAAO5G,wDAAKA,GACTM,OAAO,CACNkJ,eACE,yEAEH5G,GAAG,CAACnC,WAAWiK,UAAU9D;AAC9B;AAEA,2EAA2E;AAC3E,+EAA+E;AAC/E,wDAAwD;AACjD,SAAS+D,gBACdlK,SAAiB,EACjBmG,KAAa,EACbgE,SAAkB;IAElB,MAAMzK,KAAKH,wDAAKA;IAChB,MAAMuK,SAA8B;QAAC9J;KAAU;IAC/C,IAAIsG,MAAMyC,eAAe;IACzB,IAAIoB,WAAW;QACb7D,OAAO;QACPwD,OAAOlE,IAAI,CAACuE;IACd;IACA7D,OAAO;IACPwD,OAAOlE,IAAI,CAACO,QAAQ,IAAI,4CAA4C;IACpE,MAAMjE,OAAOxC,GAAGG,OAAO,CAACyG,KAAKnE,GAAG,IAAI2H;IACpC,MAAMM,WAAWlI,KAAKJ,MAAM,GAAGqE;IAC/B,OAAO;QAAEkE,UAAUnI,KAAKoI,KAAK,CAAC,GAAGnE,OAAO4D,OAAO;QAAIK;IAAS;AAC9D;AAEO,SAASG,WACdvK,SAAiB,EACjBwK,IAA0B,EAC1BC,OAAe,EACfC,UAAwC,EACxCC,WAA0B,IAAI,EAC9BtF,QAAyC;IAEzC,MAAMuF,SAAShC,uDAAUA;IACzB,MAAMjJ,IAAImJ;IACV,MAAMpJ,KAAKH,wDAAKA;IAChB,MAAMsL,iBAAiBH,cAAcA,WAAW5I,MAAM,GAAG,IAAI0C,KAAKwC,SAAS,CAAC0D,cAAc;IAC1F,MAAMI,eAAezF,YAAYoB,OAAO6B,IAAI,CAACjD,UAAUvD,MAAM,GAAG,IAAI0C,KAAKwC,SAAS,CAAC3B,YAAY;IAC/F3F,GAAGG,OAAO,CAAC,0HACRC,GAAG,CAAC8K,QAAQ5K,WAAWwK,MAAMC,SAAS9K,GAAGkL,gBAAgBF,UAAUG;IACtEpL,GAAGG,OAAO,CAAC,sEAAsEC,GAAG,CAACE;IACrF,4EAA4E;IAC5E,qEAAqE;IACrE,IAAIyK,QAAQM,IAAI,GAAGjJ,MAAM,IAAI,IAAI;QAC/B+G,mEAAQA,CAAC4B,SAASO,IAAI,CAAC,CAACC;YACtB,IAAIA,KAAK;gBACP1L,wDAAKA,GAAGM,OAAO,CAAC,kDAAkDC,GAAG,CAAC0E,KAAKwC,SAAS,CAACiE,MAAML;YAC7F;QACF,GAAGM,KAAK,CAAC,KAA0C;IACrD;IACA,OAAO;QAAEN;QAAQ5K;QAAWwK;QAAMC;QAASnB,YAAY3J;QAAGwL,aAAaN;QAAgBF;QAAUtF,UAAUyF;IAAa;AAC1H;AAEA,yEAAyE;AACzE,sEAAsE;AACtE,wEAAwE;AACxE,sEAAsE;AAC/D,SAASM,mBAAmBR,MAAc,EAAEvF,QAAwC;IACzF,MAAMgG,OAAOhG,YAAYoB,OAAO6B,IAAI,CAACjD,UAAUvD,MAAM,GAAG,IAAI0C,KAAKwC,SAAS,CAAC3B,YAAY;IACvF9F,wDAAKA,GAAGM,OAAO,CAAC,iDAAiDC,GAAG,CAACuL,MAAMT;AAC7E;AAEO,SAASU,uBAAuBC,OAAe;IACpD,MAAMC,WAAWjM,wDAAKA,GACnBM,OAAO,CAAC,kDACR4B,GAAG,CAAC8J;IACP,IAAIC,UAAU,OAAOA;IACrB,OAAOpC,aAAamC;AACtB;AAEO,SAASE,oBAAoB1D,QAAgB;IAClD,MAAMrI,KAAKH,wDAAKA;IAChBG,GAAGG,OAAO,CAAC,0CAA0CC,GAAG,CAACiI;IACzDrI,GAAGG,OAAO,CAAC,sEACRC,GAAG,CAAC,IAAIa,OAAOC,WAAW,IAAImH;AACnC;AAEO,SAAS2D,YAAY1L,SAAiB,EAAE2L,QAAiB;IAC9D,MAAMhM,IAAImJ;IACVvJ,wDAAKA,GACFM,OAAO,CAAC,8EACRC,GAAG,CAACH,GAAGgM,WAAWA,SAASrB,KAAK,CAAC,GAAG,MAAM,MAAMtK;AACrD;AAEA,4EAA4E;AAC5E,wEAAwE;AACxE,6EAA6E;AACtE,SAAS4L,oBAAoB5L,SAAiB,EAAE6L,SAAwB;IAC7EtM,wDAAKA,GACFM,OAAO,CAAC,oDACRC,GAAG,CAAC+L,WAAW7L;AACpB;AAEA,2EAA2E;AAC3E,8EAA8E;AAC9E,8EAA8E;AAC9E,oDAAoD;AAC7C,SAAS8L,qBACd9L,SAAiB,EACjB+L,OAAe,EACf3F,MAAqB;IAErB7G,wDAAKA,GACFM,OAAO,CACN,0GAEDC,GAAG,CAACiM,SAAS3F,QAAQ0C,OAAO9I;AACjC","sources":["webpack://@circuitwall/jarela/./lib/stores/message-usage.ts","webpack://@circuitwall/jarela/./lib/agents/sqlite-checkpoint-saver.ts","webpack://@circuitwall/jarela/./lib/agents/checkpointer.ts","webpack://@circuitwall/jarela/./lib/stores/threads.ts"],"sourcesContent":["// ADR-0041. Immutable per-assistant-turn snapshot of LLM usage.\n//\n// Written once when an assistant turn is persisted (see\n// `persistAssistantMessage` in lib/agents/run-thread.ts). Never updated.\n// The dashboard reads this table in preference to recomputing tokens /\n// cost from `messages.content` + current agent_configs joins.\n\nimport { getDb } from \"@/lib/db\";\n\nexport interface MessageUsageInput {\n message_id: string;\n thread_id: string;\n agent_id: string;\n agent_name: string;\n provider: string;\n model_id: string;\n model_config_name: string | null;\n input_tokens: number;\n output_tokens: number;\n input_rate_usd_per_mtok: number | null;\n output_rate_usd_per_mtok: number | null;\n cost_usd: number;\n // Per-tier input-token breakdown captured from the history-window\n // assembly. NULL/undefined when unknown (very old assistant turns\n // persisted before the breakdown was wired up, or non-LLM persists).\n tier_usage?: TierUsage | null;\n // Anthropic prompt caching (PR #181). Both fields are disjoint from\n // `input_tokens`: total billable input tokens =\n // input_tokens + cache_creation_input_tokens + cache_read_input_tokens\n // priced at 1×, 1.25×, and 0.1× the input rate respectively. NULL/zero\n // for providers that don't expose cache counts.\n cache_creation_input_tokens?: number | null;\n cache_read_input_tokens?: number | null;\n}\n\nexport interface TierUsage {\n hot_tokens: number;\n warm_tokens: number;\n facts_tokens: number;\n overhead_tokens: number;\n hot_budget_tokens: number;\n warm_budget_tokens: number;\n facts_budget_tokens: number;\n context_window_tokens: number;\n}\n\nexport interface MessageUsageRow extends Omit<MessageUsageInput, \"tier_usage\" | \"cache_creation_input_tokens\" | \"cache_read_input_tokens\"> {\n created_at: string;\n hot_tokens: number | null;\n warm_tokens: number | null;\n facts_tokens: number | null;\n overhead_tokens: number | null;\n hot_budget_tokens: number | null;\n warm_budget_tokens: number | null;\n facts_budget_tokens: number | null;\n context_window_tokens: number | null;\n cache_creation_input_tokens: number | null;\n cache_read_input_tokens: number | null;\n}\n\nexport function recordMessageUsage(input: MessageUsageInput): void {\n const db = getDb();\n const t = input.tier_usage ?? null;\n db.prepare(\n `INSERT OR IGNORE INTO message_usage (\n message_id, thread_id, agent_id, agent_name, provider, model_id,\n model_config_name, input_tokens, output_tokens,\n input_rate_usd_per_mtok, output_rate_usd_per_mtok, cost_usd, created_at,\n hot_tokens, warm_tokens, facts_tokens, overhead_tokens,\n hot_budget_tokens, warm_budget_tokens, facts_budget_tokens, context_window_tokens,\n cache_creation_input_tokens, cache_read_input_tokens\n ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n ).run(\n input.message_id,\n input.thread_id,\n input.agent_id,\n input.agent_name,\n input.provider,\n input.model_id,\n input.model_config_name,\n input.input_tokens,\n input.output_tokens,\n input.input_rate_usd_per_mtok,\n input.output_rate_usd_per_mtok,\n input.cost_usd,\n new Date().toISOString(),\n t?.hot_tokens ?? null,\n t?.warm_tokens ?? null,\n t?.facts_tokens ?? null,\n t?.overhead_tokens ?? null,\n t?.hot_budget_tokens ?? null,\n t?.warm_budget_tokens ?? null,\n t?.facts_budget_tokens ?? null,\n t?.context_window_tokens ?? null,\n input.cache_creation_input_tokens ?? null,\n input.cache_read_input_tokens ?? null,\n );\n}\n\nexport function getMessageUsage(messageId: string): MessageUsageRow | null {\n return (getDb()\n .prepare(\"SELECT * FROM message_usage WHERE message_id=?\")\n .get(messageId) as MessageUsageRow | undefined) ?? null;\n}\n\n/**\n * Batch lookup keyed by message_id. Returns a Map missing any ids that\n * have no usage row (user turns, legacy rows). Used by the threads GET\n * route to attach per-turn token counts to a page of messages without\n * issuing one SELECT per row.\n */\nexport function getMessageUsageByIds(messageIds: readonly string[]): Map<string, MessageUsageRow> {\n const out = new Map<string, MessageUsageRow>();\n if (messageIds.length === 0) return out;\n const placeholders = messageIds.map(() => \"?\").join(\",\");\n const rows = getDb()\n .prepare(`SELECT * FROM message_usage WHERE message_id IN (${placeholders})`)\n .all(...messageIds) as unknown as MessageUsageRow[];\n for (const row of rows) out.set(row.message_id, row);\n return out;\n}\n\nexport function computeCostUsd(\n inputTokens: number,\n outputTokens: number,\n inputRatePerMTok: number | null | undefined,\n outputRatePerMTok: number | null | undefined,\n): number {\n const inCost = inputRatePerMTok && inputTokens > 0\n ? (inputTokens / 1_000_000) * inputRatePerMTok\n : 0;\n const outCost = outputRatePerMTok && outputTokens > 0\n ? (outputTokens / 1_000_000) * outputRatePerMTok\n : 0;\n return inCost + outCost;\n}\n","import { DatabaseSync, type StatementSync } from \"node:sqlite\";\nimport { type RunnableConfig } from \"@langchain/core/runnables\";\nimport {\n BaseCheckpointSaver,\n TASKS,\n copyCheckpoint,\n maxChannelVersion,\n type Checkpoint,\n type CheckpointListOptions,\n type CheckpointMetadata,\n type CheckpointTuple,\n type PendingWrite,\n type SerializerProtocol,\n} from \"@langchain/langgraph-checkpoint\";\n\n// In-tree LangGraph checkpoint saver backed by node:sqlite.\n//\n// Schema-compatible drop-in replacement for SqliteSaver from\n// @langchain/langgraph-checkpoint-sqlite. The DDL is byte-identical so an\n// existing checkpoints.db keeps working without migration.\n\nconst VALID_FILTER_KEYS: readonly string[] = [\"source\", \"step\", \"parents\"];\n\ntype Row = Record<string, unknown>;\n\nfunction buildLatestSelect(includeCheckpointId: boolean): string {\n return `\n SELECT\n thread_id,\n checkpoint_ns,\n checkpoint_id,\n parent_checkpoint_id,\n type,\n checkpoint,\n metadata,\n (\n SELECT\n json_group_array(\n json_object(\n 'task_id', pw.task_id,\n 'channel', pw.channel,\n 'type', pw.type,\n 'value', CAST(pw.value AS TEXT)\n )\n )\n FROM writes as pw\n WHERE pw.thread_id = checkpoints.thread_id\n AND pw.checkpoint_ns = checkpoints.checkpoint_ns\n AND pw.checkpoint_id = checkpoints.checkpoint_id\n ) as pending_writes,\n (\n SELECT\n json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n )\n FROM writes as ps\n WHERE ps.thread_id = checkpoints.thread_id\n AND ps.checkpoint_ns = checkpoints.checkpoint_ns\n AND ps.checkpoint_id = checkpoints.parent_checkpoint_id\n AND ps.channel = '${TASKS}'\n ORDER BY ps.idx\n ) as pending_sends\n FROM checkpoints\n WHERE thread_id = ? AND checkpoint_ns = ? ${\n includeCheckpointId\n ? \"AND checkpoint_id = ?\"\n : \"ORDER BY checkpoint_id DESC LIMIT 1\"\n }`;\n}\n\n// node:sqlite rejects `undefined` parameter binds; coerce to null.\nfunction nv<T>(v: T | undefined | null): T | null {\n return v === undefined ? null : (v as T | null);\n}\n\nexport class NodeSqliteSaver extends BaseCheckpointSaver {\n db: DatabaseSync;\n\n protected isSetup = false;\n protected withoutCheckpoint!: StatementSync;\n protected withCheckpoint!: StatementSync;\n\n constructor(db: DatabaseSync, serde?: SerializerProtocol) {\n super(serde);\n this.db = db;\n }\n\n static fromConnString(connStringOrLocalPath: string): NodeSqliteSaver {\n return new NodeSqliteSaver(new DatabaseSync(connStringOrLocalPath));\n }\n\n protected setup(): void {\n if (this.isSetup) return;\n this.db.exec(\"PRAGMA journal_mode=WAL\");\n this.db.exec(`\nCREATE TABLE IF NOT EXISTS checkpoints (\n thread_id TEXT NOT NULL,\n checkpoint_ns TEXT NOT NULL DEFAULT '',\n checkpoint_id TEXT NOT NULL,\n parent_checkpoint_id TEXT,\n type TEXT,\n checkpoint BLOB,\n metadata BLOB,\n PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)\n);`);\n this.db.exec(`\nCREATE TABLE IF NOT EXISTS writes (\n thread_id TEXT NOT NULL,\n checkpoint_ns TEXT NOT NULL DEFAULT '',\n checkpoint_id TEXT NOT NULL,\n task_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n channel TEXT NOT NULL,\n type TEXT,\n value BLOB,\n PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)\n);`);\n this.withoutCheckpoint = this.db.prepare(buildLatestSelect(false));\n this.withCheckpoint = this.db.prepare(buildLatestSelect(true));\n this.isSetup = true;\n }\n\n protected txn<T>(fn: () => T): T {\n this.db.exec(\"BEGIN\");\n try {\n const result = fn();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (err) {\n try {\n this.db.exec(\"ROLLBACK\");\n } catch {\n // ignore rollback failure; surface the original error\n }\n throw err;\n }\n }\n\n private async decodeRowState(row: Row): Promise<{\n pendingWrites: [string, string, unknown][];\n checkpoint: Checkpoint;\n metadata: CheckpointMetadata;\n }> {\n const pendingWrites = await Promise.all(\n JSON.parse(String(row.pending_writes ?? \"[]\")).map(\n async (write: { task_id: string; channel: string; type?: string; value?: string }) => {\n return [\n write.task_id,\n write.channel,\n await this.serde.loadsTyped(write.type ?? \"json\", write.value ?? \"\"),\n ] as [string, string, unknown];\n },\n ),\n );\n\n const checkpoint = (await this.serde.loadsTyped(\n (row.type as string | null) ?? \"json\",\n row.checkpoint as Uint8Array | string,\n )) as Checkpoint;\n\n if (checkpoint.v < 4 && row.parent_checkpoint_id != null) {\n await this.migratePendingSends(\n checkpoint,\n row.thread_id as string,\n row.parent_checkpoint_id as string,\n );\n }\n\n const metadata = (await this.serde.loadsTyped(\n (row.type as string | null) ?? \"json\",\n row.metadata as Uint8Array | string,\n )) as CheckpointMetadata;\n\n return { pendingWrites, checkpoint, metadata };\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n this.setup();\n const { thread_id, checkpoint_ns = \"\", checkpoint_id } =\n config.configurable ?? {};\n const args: unknown[] = [nv(thread_id), checkpoint_ns];\n if (checkpoint_id) args.push(checkpoint_id);\n const stmt = checkpoint_id ? this.withCheckpoint : this.withoutCheckpoint;\n const row = stmt.get(...(args as never[])) as Row | undefined;\n if (row === undefined) return undefined;\n\n let finalConfig = config;\n if (!checkpoint_id) {\n finalConfig = {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns,\n checkpoint_id: row.checkpoint_id,\n },\n };\n }\n if (\n finalConfig.configurable?.thread_id === undefined ||\n finalConfig.configurable?.checkpoint_id === undefined\n ) {\n throw new Error(\"Missing thread_id or checkpoint_id\");\n }\n\n const { pendingWrites, checkpoint, metadata } = await this.decodeRowState(row);\n\n return {\n checkpoint,\n config: finalConfig,\n metadata,\n parentConfig: row.parent_checkpoint_id\n ? {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns,\n checkpoint_id: row.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\n }\n\n async *list(\n config: RunnableConfig,\n options?: CheckpointListOptions,\n ): AsyncGenerator<CheckpointTuple> {\n const { limit, before, filter } = options ?? {};\n this.setup();\n const thread_id = config.configurable?.thread_id;\n const checkpoint_ns = config.configurable?.checkpoint_ns;\n\n let sql = `\n SELECT\n thread_id,\n checkpoint_ns,\n checkpoint_id,\n parent_checkpoint_id,\n type,\n checkpoint,\n metadata,\n (\n SELECT\n json_group_array(\n json_object(\n 'task_id', pw.task_id,\n 'channel', pw.channel,\n 'type', pw.type,\n 'value', CAST(pw.value AS TEXT)\n )\n )\n FROM writes as pw\n WHERE pw.thread_id = checkpoints.thread_id\n AND pw.checkpoint_ns = checkpoints.checkpoint_ns\n AND pw.checkpoint_id = checkpoints.checkpoint_id\n ) as pending_writes,\n (\n SELECT\n json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n )\n FROM writes as ps\n WHERE ps.thread_id = checkpoints.thread_id\n AND ps.checkpoint_ns = checkpoints.checkpoint_ns\n AND ps.checkpoint_id = checkpoints.parent_checkpoint_id\n AND ps.channel = '${TASKS}'\n ORDER BY ps.idx\n ) as pending_sends\n FROM checkpoints\\n`;\n\n const whereClause: string[] = [];\n if (thread_id) whereClause.push(\"thread_id = ?\");\n if (checkpoint_ns !== undefined && checkpoint_ns !== null) {\n whereClause.push(\"checkpoint_ns = ?\");\n }\n if (before?.configurable?.checkpoint_id !== undefined) {\n whereClause.push(\"checkpoint_id < ?\");\n }\n\n const sanitizedFilter = Object.fromEntries(\n Object.entries(filter ?? {}).filter(\n ([key, value]) => value !== undefined && VALID_FILTER_KEYS.includes(key),\n ),\n );\n whereClause.push(\n ...Object.entries(sanitizedFilter).map(\n ([key]) => `jsonb(CAST(metadata AS TEXT))->'$.${key}' = ?`,\n ),\n );\n if (whereClause.length > 0) sql += `WHERE\\n ${whereClause.join(\" AND\\n \")}\\n`;\n sql += \"\\nORDER BY checkpoint_id DESC\";\n if (limit) sql += ` LIMIT ${parseInt(String(limit), 10)}`;\n\n const args: unknown[] = [\n thread_id,\n checkpoint_ns,\n before?.configurable?.checkpoint_id,\n ...Object.values(sanitizedFilter).map((value) => JSON.stringify(value)),\n ].filter((value) => value !== undefined && value !== null);\n\n const rows = this.db.prepare(sql).all(...(args as never[])) as Row[];\n for (const row of rows) {\n const { pendingWrites, checkpoint, metadata } = await this.decodeRowState(row);\n yield {\n config: {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns: row.checkpoint_ns,\n checkpoint_id: row.checkpoint_id,\n },\n },\n checkpoint,\n metadata,\n parentConfig: row.parent_checkpoint_id\n ? {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns: row.checkpoint_ns,\n checkpoint_id: row.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\n }\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n ): Promise<RunnableConfig> {\n this.setup();\n if (!config.configurable) throw new Error(\"Empty configuration supplied.\");\n const thread_id = config.configurable?.thread_id as string | undefined;\n const checkpoint_ns = (config.configurable?.checkpoint_ns ?? \"\") as string;\n const parent_checkpoint_id = config.configurable?.checkpoint_id as\n | string\n | undefined;\n if (!thread_id) {\n throw new Error(`Missing \"thread_id\" field in passed \"config.configurable\".`);\n }\n const preparedCheckpoint = copyCheckpoint(checkpoint);\n const [\n [type1, serializedCheckpoint],\n [type2, serializedMetadata],\n ] = await Promise.all([\n this.serde.dumpsTyped(preparedCheckpoint),\n this.serde.dumpsTyped(metadata),\n ]);\n if (type1 !== type2) {\n throw new Error(\n \"Failed to serialized checkpoint and metadata to the same type.\",\n );\n }\n this.db\n .prepare(\n `INSERT OR REPLACE INTO checkpoints (thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n thread_id,\n checkpoint_ns,\n checkpoint.id,\n nv(parent_checkpoint_id),\n type1,\n serializedCheckpoint as never,\n serializedMetadata as never,\n );\n return {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: checkpoint.id,\n },\n };\n }\n\n async putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string,\n ): Promise<void> {\n this.setup();\n if (!config.configurable) throw new Error(\"Empty configuration supplied.\");\n if (!config.configurable?.thread_id) {\n throw new Error(\"Missing thread_id field in config.configurable.\");\n }\n if (!config.configurable?.checkpoint_id) {\n throw new Error(\"Missing checkpoint_id field in config.configurable.\");\n }\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO writes\n (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const rows = await Promise.all(\n writes.map(async (write, idx) => {\n const [type, serializedWrite] = await this.serde.dumpsTyped(write[1]);\n return [\n config.configurable?.thread_id as string,\n (config.configurable?.checkpoint_ns ?? \"\") as string,\n config.configurable?.checkpoint_id as string,\n taskId,\n idx,\n write[0],\n type,\n serializedWrite,\n ] as const;\n }),\n );\n this.txn(() => {\n for (const row of rows) stmt.run(...(row as unknown as never[]));\n });\n }\n\n async deleteThread(threadId: string): Promise<void> {\n this.setup();\n this.txn(() => {\n this.db\n .prepare(`DELETE FROM checkpoints WHERE thread_id = ?`)\n .run(threadId);\n this.db.prepare(`DELETE FROM writes WHERE thread_id = ?`).run(threadId);\n });\n }\n\n protected async migratePendingSends(\n checkpoint: Checkpoint,\n threadId: string,\n parentCheckpointId: string,\n ): Promise<void> {\n const row = this.db\n .prepare(\n `\n SELECT\n checkpoint_id,\n json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n ) as pending_sends\n FROM writes as ps\n WHERE ps.thread_id = ?\n AND ps.checkpoint_id = ?\n AND ps.channel = '${TASKS}'\n ORDER BY ps.idx\n `,\n )\n .get(threadId, parentCheckpointId) as Row | undefined;\n const pendingSendsJson = String(row?.pending_sends ?? \"[]\");\n const mutable = checkpoint as Checkpoint & {\n channel_values?: Record<string, unknown>;\n channel_versions: Record<string, unknown>;\n };\n mutable.channel_values ??= {};\n mutable.channel_values[TASKS] = await Promise.all(\n JSON.parse(pendingSendsJson).map(\n ({ type, value }: { type?: string; value?: string }) =>\n this.serde.loadsTyped(type ?? \"json\", value ?? \"\"),\n ),\n );\n mutable.channel_versions[TASKS] =\n Object.keys(checkpoint.channel_versions).length > 0\n ? maxChannelVersion(...Object.values(checkpoint.channel_versions))\n : this.getNextVersion(undefined);\n }\n}\n","import { join } from \"node:path\";\nimport { getDataDir } from \"@/lib/db/data-dir\";\nimport { NodeSqliteSaver } from \"./sqlite-checkpoint-saver\";\n\n// Persistent graph state per thread_id. Multi-step plans, scratchpad messages,\n// and any pending tool-call sequence survive process restarts and resume on\n// the next agent.stream() with the same thread_id.\n//\n// Stored in a separate DB file (~/.jarela/checkpoints.db) so LangGraph\n// manages its own schema independently of our own migrations under\n// lib/db/. Both files use WAL mode via the same node:sqlite driver.\n\nconst CHECKPOINT_PATH = join(getDataDir(), \"checkpoints.db\");\n\nlet _saver: NodeSqliteSaver | null = null;\n\nexport function getCheckpointer(): NodeSqliteSaver {\n if (!_saver) {\n _saver = NodeSqliteSaver.fromConnString(CHECKPOINT_PATH);\n }\n return _saver;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { getDb } from \"@/lib/db\";\nimport { embedOne } from \"@/lib/embeddings\";\n\nconst now = () => new Date().toISOString();\n\n// Explicit column list for message reads — omits `embedding` (~20KB of\n// JSON-encoded float[] per row) which only the embeddings module reads.\n// Avoids dragging it through the chat-history result set on every call.\nconst MSG_COLS_SQL = \"SELECT msg_id, thread_id, role, content, created_at, tool_events, category, metadata FROM messages\";\n\nexport interface ThreadRow {\n thread_id: string; agent_id: string; title: string | null;\n created_at: string; updated_at: string; message_count: number;\n // ADR-0042 — explicit context pin + cached warm summary. NULL on threads\n // that haven't had the boundary moved away from the agent default. The\n // summary is fresh only when warm_summary_before === hot_since.\n hot_since?: string | null;\n warm_summary?: string | null;\n warm_summary_before?: string | null;\n warm_summary_computed_at?: string | null;\n}\nexport interface MessageRow {\n msg_id: string; thread_id: string; role: string; content: string; created_at: string;\n // JSON-encoded array of PersistedToolEvent. null when no tool work happened\n // on this turn or for user messages. Read back by the chat UI so historical\n // bubbles show the same expandable CALL/RESULT entries as live streaming.\n tool_events?: string | null;\n // Non-null tags classify the message into a filterable group in the chat\n // panel (e.g. 'scheduled_task', 'bridge', 'synthetic'). NULL = ordinary\n // user/assistant chat content.\n category?: string | null;\n // JSON-encoded auxiliary per-message data. NULL on legacy rows. Currently\n // carries the citation-checker verdict when `require_source_links` is on.\n metadata?: string | null;\n}\n\nexport interface PersistedToolEvent {\n id: string;\n phase: \"call\" | \"result\";\n name: string;\n payload: unknown;\n}\n\nexport function listThreads(limit = 50, offset = 0): ThreadRow[] {\n return getDb()\n .prepare(\"SELECT * FROM threads ORDER BY updated_at DESC LIMIT ? OFFSET ?\")\n .all(limit, offset) as unknown as ThreadRow[];\n}\n\nexport function listThreadsByAgent(agent_id: string, limit = 50): ThreadRow[] {\n return getDb()\n .prepare(\"SELECT * FROM threads WHERE agent_id=? ORDER BY updated_at DESC LIMIT ?\")\n .all(agent_id, limit) as unknown as ThreadRow[];\n}\n\nexport function getThread(thread_id: string): ThreadRow | null {\n return (getDb().prepare(\"SELECT * FROM threads WHERE thread_id=?\").get(thread_id) as unknown as ThreadRow) ?? null;\n}\n\nexport function createThread(agent_id: string, title?: string): ThreadRow {\n const t = now();\n const thread_id = randomUUID();\n getDb()\n .prepare(\"INSERT INTO threads (thread_id,agent_id,title,created_at,updated_at,message_count) VALUES (?,?,?,?,?,0)\")\n .run(thread_id, agent_id, title ?? null, t, t);\n return { thread_id, agent_id, title: title ?? null, created_at: t, updated_at: t, message_count: 0 };\n}\n\nexport function deleteThread(thread_id: string): boolean {\n const db = getDb();\n db.prepare(\"DELETE FROM messages WHERE thread_id=?\").run(thread_id);\n const r = db.prepare(\"DELETE FROM threads WHERE thread_id=?\").run(thread_id);\n return r.changes > 0;\n}\n\nexport function getMessages(thread_id: string): MessageRow[] {\n return getDb()\n .prepare(MSG_COLS_SQL + \" WHERE thread_id=? ORDER BY created_at ASC\")\n .all(thread_id) as unknown as MessageRow[];\n}\n\n// Pull the latest N messages within a time window. Used to build the LLM\n// context — keeps prompt size bounded as threads grow indefinitely.\n// limit: 0 or negative = unlimited\n// sinceISO: undefined = no time bound\n// Returns chronological order (oldest first) so it can be appended to the prompt directly.\nexport function getRecentMessagesWindow(\n thread_id: string,\n limit: number,\n sinceISO?: string,\n): MessageRow[] {\n const db = getDb();\n const params: (string | number)[] = [thread_id];\n let sql = MSG_COLS_SQL + \" WHERE thread_id=?\";\n if (sinceISO) {\n sql += \" AND created_at >= ?\";\n params.push(sinceISO);\n }\n sql += \" ORDER BY created_at DESC\";\n if (limit > 0) {\n sql += \" LIMIT ?\";\n params.push(limit);\n }\n const rows = db.prepare(sql).all(...params) as unknown as MessageRow[];\n return rows.reverse();\n}\n\n// Forward-fetch — return messages strictly newer than `afterISO`, oldest\n// first, capped at `limit`. Used by the chat view to pull only the\n// freshly-persisted user+assistant pair after a run completes, instead of\n// re-fetching the whole most-recent page.\nexport function getMessagesAfter(\n thread_id: string,\n afterISO: string,\n limit = 50,\n): MessageRow[] {\n return getDb()\n .prepare(\n MSG_COLS_SQL +\n \" WHERE thread_id=? AND created_at > ? ORDER BY created_at ASC LIMIT ?\",\n )\n .all(thread_id, afterISO, limit) as unknown as MessageRow[];\n}\n\n// Pagination for the chat UI. Returns the latest N messages strictly older\n// than `beforeISO` (cursor). Caller passes the oldest already-loaded message's\n// created_at as the cursor; first page omits beforeISO.\nexport function getMessagesPage(\n thread_id: string,\n limit: number,\n beforeISO?: string,\n): { messages: MessageRow[]; has_more: boolean } {\n const db = getDb();\n const params: (string | number)[] = [thread_id];\n let sql = MSG_COLS_SQL + \" WHERE thread_id=?\";\n if (beforeISO) {\n sql += \" AND created_at < ?\";\n params.push(beforeISO);\n }\n sql += \" ORDER BY created_at DESC LIMIT ?\";\n params.push(limit + 1); // fetch one extra to detect if there's more\n const rows = db.prepare(sql).all(...params) as unknown as MessageRow[];\n const has_more = rows.length > limit;\n return { messages: rows.slice(0, limit).reverse(), has_more };\n}\n\nexport function addMessage(\n thread_id: string,\n role: \"user\" | \"assistant\",\n content: string,\n toolEvents?: PersistedToolEvent[] | null,\n category: string | null = null,\n metadata?: Record<string, unknown> | null,\n): MessageRow {\n const msg_id = randomUUID();\n const t = now();\n const db = getDb();\n const toolEventsJson = toolEvents && toolEvents.length > 0 ? JSON.stringify(toolEvents) : null;\n const metadataJson = metadata && Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null;\n db.prepare(\"INSERT INTO messages (msg_id,thread_id,role,content,created_at,tool_events,category,metadata) VALUES (?,?,?,?,?,?,?,?)\")\n .run(msg_id, thread_id, role, content, t, toolEventsJson, category, metadataJson);\n db.prepare(\"UPDATE threads SET message_count=message_count+1 WHERE thread_id=?\").run(thread_id);\n // Best-effort: embed the message so semantic recall can pull it back later.\n // Skip empty / very short content (greetings have no useful signal).\n if (content.trim().length >= 12) {\n embedOne(content).then((vec) => {\n if (vec) {\n getDb().prepare(\"UPDATE messages SET embedding=? WHERE msg_id=?\").run(JSON.stringify(vec), msg_id);\n }\n }).catch(() => { /* logged in embeddings module */ });\n }\n return { msg_id, thread_id, role, content, created_at: t, tool_events: toolEventsJson, category, metadata: metadataJson };\n}\n\n// Replace a single message's metadata blob. Used by the citation checker\n// to write its verdict back after the assistant turn has already been\n// persisted (the checker runs async post-insert so the chat UI sees the\n// content immediately and picks up the metadata on the next refresh).\nexport function setMessageMetadata(msg_id: string, metadata: Record<string, unknown> | null): void {\n const json = metadata && Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null;\n getDb().prepare(\"UPDATE messages SET metadata=? WHERE msg_id=?\").run(json, msg_id);\n}\n\nexport function getOrCreateAgentThread(agentId: string): ThreadRow {\n const existing = getDb()\n .prepare(\"SELECT * FROM threads WHERE agent_id=? LIMIT 1\")\n .get(agentId) as ThreadRow | undefined;\n if (existing) return existing;\n return createThread(agentId);\n}\n\nexport function clearThreadMessages(threadId: string): void {\n const db = getDb();\n db.prepare(\"DELETE FROM messages WHERE thread_id=?\").run(threadId);\n db.prepare(\"UPDATE threads SET message_count=0, updated_at=? WHERE thread_id=?\")\n .run(new Date().toISOString(), threadId);\n}\n\nexport function touchThread(thread_id: string, firstMsg?: string): void {\n const t = now();\n getDb()\n .prepare(\"UPDATE threads SET updated_at=?, title=COALESCE(title,?) WHERE thread_id=?\")\n .run(t, firstMsg ? firstMsg.slice(0, 80) : null, thread_id);\n}\n\n// ADR-0042. Move the user's explicit boundary between hot and warm context.\n// Pass `null` to clear the pin and let the agent's default window apply\n// again. Persisting the pin here keeps it stable across reloads and devices.\nexport function setThreadContextPin(thread_id: string, hot_since: string | null): void {\n getDb()\n .prepare(\"UPDATE threads SET hot_since=? WHERE thread_id=?\")\n .run(hot_since, thread_id);\n}\n\n// Cache the latest warm-tier summary alongside the boundary it covers. The\n// chat UI considers the summary fresh only when `warm_summary_before` matches\n// the current `hot_since`; any boundary change triggers a re-summarise on the\n// next run rather than a synchronous LLM call here.\nexport function setThreadWarmSummary(\n thread_id: string,\n summary: string,\n before: string | null,\n): void {\n getDb()\n .prepare(\n \"UPDATE threads SET warm_summary=?, warm_summary_before=?, warm_summary_computed_at=? WHERE thread_id=?\",\n )\n .run(summary, before, now(), thread_id);\n}\n"],"names":["getDb","recordMessageUsage","input","db","t","tier_usage","prepare","run","message_id","thread_id","agent_id","agent_name","provider","model_id","model_config_name","input_tokens","output_tokens","input_rate_usd_per_mtok","output_rate_usd_per_mtok","cost_usd","Date","toISOString","hot_tokens","warm_tokens","facts_tokens","overhead_tokens","hot_budget_tokens","warm_budget_tokens","facts_budget_tokens","context_window_tokens","cache_creation_input_tokens","cache_read_input_tokens","getMessageUsage","messageId","get","getMessageUsageByIds","messageIds","out","Map","length","placeholders","map","join","rows","all","row","set","computeCostUsd","inputTokens","outputTokens","inputRatePerMTok","outputRatePerMTok","inCost","outCost","DatabaseSync","BaseCheckpointSaver","TASKS","copyCheckpoint","maxChannelVersion","VALID_FILTER_KEYS","buildLatestSelect","includeCheckpointId","nv","v","undefined","NodeSqliteSaver","serde","isSetup","fromConnString","connStringOrLocalPath","setup","exec","withoutCheckpoint","withCheckpoint","txn","fn","result","err","decodeRowState","pendingWrites","Promise","JSON","parse","String","pending_writes","write","task_id","channel","loadsTyped","type","value","checkpoint","parent_checkpoint_id","migratePendingSends","metadata","getTuple","config","checkpoint_ns","checkpoint_id","configurable","args","push","stmt","finalConfig","Error","parentConfig","list","options","limit","before","filter","sql","whereClause","sanitizedFilter","Object","fromEntries","entries","key","includes","parseInt","values","stringify","put","preparedCheckpoint","type1","serializedCheckpoint","type2","serializedMetadata","dumpsTyped","id","putWrites","writes","taskId","idx","serializedWrite","deleteThread","threadId","parentCheckpointId","pendingSendsJson","pending_sends","mutable","channel_values","channel_versions","keys","getNextVersion","getDataDir","CHECKPOINT_PATH","_saver","getCheckpointer","randomUUID","embedOne","now","MSG_COLS_SQL","listThreads","offset","listThreadsByAgent","getThread","createThread","title","created_at","updated_at","message_count","r","changes","getMessages","getRecentMessagesWindow","sinceISO","params","reverse","getMessagesAfter","afterISO","getMessagesPage","beforeISO","has_more","messages","slice","addMessage","role","content","toolEvents","category","msg_id","toolEventsJson","metadataJson","trim","then","vec","catch","tool_events","setMessageMetadata","json","getOrCreateAgentThread","agentId","existing","clearThreadMessages","touchThread","firstMsg","setThreadContextPin","hot_since","setThreadWarmSummary","summary"],"sourceRoot":"","ignoreList":[]}
|