@checkstack/ai-backend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/drizzle/0000_productive_jackpot.sql +26 -0
  3. package/drizzle/0001_puzzling_purple_man.sql +26 -0
  4. package/drizzle/0002_sparkling_paper_doll.sql +15 -0
  5. package/drizzle/0003_married_senator_kelly.sql +1 -0
  6. package/drizzle/0004_crazy_miek.sql +2 -0
  7. package/drizzle/0005_tearful_randall_flagg.sql +1 -0
  8. package/drizzle/meta/0000_snapshot.json +232 -0
  9. package/drizzle/meta/0001_snapshot.json +434 -0
  10. package/drizzle/meta/0002_snapshot.json +551 -0
  11. package/drizzle/meta/0003_snapshot.json +557 -0
  12. package/drizzle/meta/0004_snapshot.json +573 -0
  13. package/drizzle/meta/0005_snapshot.json +574 -0
  14. package/drizzle/meta/_journal.json +48 -0
  15. package/drizzle.config.ts +7 -0
  16. package/package.json +42 -0
  17. package/src/agent-runner.test.ts +262 -0
  18. package/src/agent-runner.ts +262 -0
  19. package/src/chat/agent-loop.test.ts +119 -0
  20. package/src/chat/agent-loop.ts +73 -0
  21. package/src/chat/auto-apply.test.ts +237 -0
  22. package/src/chat/chat-handler.ts +111 -0
  23. package/src/chat/chat-service.streamturn.test.ts +417 -0
  24. package/src/chat/chat-service.test.ts +250 -0
  25. package/src/chat/chat-service.ts +923 -0
  26. package/src/chat/classifier-service.ts +64 -0
  27. package/src/chat/classifier.logic.test.ts +92 -0
  28. package/src/chat/classifier.logic.ts +71 -0
  29. package/src/chat/conversation-store.it.test.ts +203 -0
  30. package/src/chat/conversation-store.test.ts +248 -0
  31. package/src/chat/conversation-store.ts +237 -0
  32. package/src/chat/decision.logic.test.ts +45 -0
  33. package/src/chat/decision.logic.ts +54 -0
  34. package/src/chat/llm-provider.test.ts +63 -0
  35. package/src/chat/llm-provider.ts +67 -0
  36. package/src/chat/model-error.logic.test.ts +60 -0
  37. package/src/chat/model-error.logic.ts +65 -0
  38. package/src/chat/normalize-messages.logic.test.ts +101 -0
  39. package/src/chat/normalize-messages.logic.ts +65 -0
  40. package/src/chat/permission-mode.logic.test.ts +70 -0
  41. package/src/chat/permission-mode.logic.ts +45 -0
  42. package/src/chat/read-invoker.ts +72 -0
  43. package/src/chat/replay.test.ts +174 -0
  44. package/src/chat/scrub-content.test.ts +183 -0
  45. package/src/chat/scrub-content.ts +154 -0
  46. package/src/chat/sdk-tools.test.ts +168 -0
  47. package/src/chat/sdk-tools.ts +181 -0
  48. package/src/chat/title-service.test.ts +146 -0
  49. package/src/chat/title-service.ts +111 -0
  50. package/src/chat/title.logic.test.ts +98 -0
  51. package/src/chat/title.logic.ts +102 -0
  52. package/src/extension-points.ts +41 -0
  53. package/src/generated/docs-index.ts +3020 -0
  54. package/src/hardening/handler-authz.test.ts +282 -0
  55. package/src/hardening/no-secret-leak.test.ts +303 -0
  56. package/src/hooks.ts +33 -0
  57. package/src/index.ts +542 -0
  58. package/src/mcp/connection-registry.test.ts +25 -0
  59. package/src/mcp/connection-registry.ts +54 -0
  60. package/src/mcp/mcp-conformance.it.test.ts +128 -0
  61. package/src/mcp/server.test.ts +285 -0
  62. package/src/mcp/server.ts +300 -0
  63. package/src/mcp/tool-invoker.ts +65 -0
  64. package/src/openai-provider.test.ts +64 -0
  65. package/src/openai-provider.ts +146 -0
  66. package/src/projection.test.ts +97 -0
  67. package/src/projection.ts +132 -0
  68. package/src/propose-apply/args-hash.test.ts +26 -0
  69. package/src/propose-apply/args-hash.ts +30 -0
  70. package/src/propose-apply/service.test.ts +423 -0
  71. package/src/propose-apply/service.ts +419 -0
  72. package/src/propose-apply/store.test.ts +136 -0
  73. package/src/propose-apply/store.ts +224 -0
  74. package/src/propose-apply/token.test.ts +52 -0
  75. package/src/propose-apply/token.ts +71 -0
  76. package/src/rate-limit/spend-ledger.it.test.ts +224 -0
  77. package/src/rate-limit/spend-ledger.test.ts +176 -0
  78. package/src/rate-limit/spend-ledger.ts +162 -0
  79. package/src/rate-limit/tool-budget.it.test.ts +173 -0
  80. package/src/rate-limit/tool-budget.test.ts +58 -0
  81. package/src/rate-limit/tool-budget.ts +107 -0
  82. package/src/registry-wiring.test.ts +131 -0
  83. package/src/registry-wiring.ts +68 -0
  84. package/src/resolver.test.ts +156 -0
  85. package/src/resolver.ts +78 -0
  86. package/src/router.test.ts +78 -0
  87. package/src/router.ts +345 -0
  88. package/src/schema.ts +284 -0
  89. package/src/serializer.test.ts +88 -0
  90. package/src/serializer.ts +42 -0
  91. package/src/tool-registry.ts +58 -0
  92. package/src/tools/composite-tools.ts +24 -0
  93. package/src/tools/docs-tools.test.ts +150 -0
  94. package/src/tools/docs-tools.ts +115 -0
  95. package/src/tools/probe-url.test.ts +51 -0
  96. package/src/tools/probe-url.ts +146 -0
  97. package/src/tools/rank-docs.test.ts +153 -0
  98. package/src/tools/rank-docs.ts +209 -0
  99. package/src/tools/script-context-extract.test.ts +93 -0
  100. package/src/tools/script-context-extract.ts +283 -0
  101. package/src/tools/ssrf-guard.test.ts +69 -0
  102. package/src/tools/ssrf-guard.ts +108 -0
  103. package/src/tools/tool-set.e2e.test.ts +64 -0
  104. package/src/user-rpc-client.test.ts +45 -0
  105. package/src/user-rpc-client.ts +60 -0
  106. package/tsconfig.json +26 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,97 @@
1
+ # @checkstack/ai-backend
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9dcc848: AI chat UX: ordered turns, readable diffs, persistent errors, auto-titles, decision acknowledgments, and a smarter topical guard.
8
+
9
+ - Turns render as ordered parts (text / tool-call status / confirm card) in chronological order, with inline tool-error lines and a mid-turn "Thinking..." indicator, instead of one text blob plus a flat tool list. The confirm card and tool-step parts no longer vanish after a turn finishes (hydration seeds once per conversation id via `useInitOnceForKey`, so background refetches are no-ops).
10
+ - Errors persist: in-stream provider errors are lifted into the chat hook's durable error state and shown in a dismissible banner with selectable text and a Copy button (single-line digest, full text on hover); it clears on send / open / new chat. The backend installs an `onError` handler that logs the provider's full HTTP response and returns a readable message, and normalizes the model message history (drop empty rows, merge consecutive same-role rows, strip a leading non-user row) so a single provider hiccup can no longer brick a conversation.
11
+ - Confirm/applied card diffs render as a GitHub-style split diff (line-number gutters, per-line tint, word-level highlighting, an "Expand" pop-out). `computeFieldDiff` recurses into arrays element-wise so a single changed leaf is pinpointed instead of dumping whole serialized arrays.
12
+ - Conversations auto-title after the first user message (cheap `generateText` reusing the turn's model, fire-and-forget, heuristic fallback). "New chat" opens immediately and reuses an empty untitled draft instead of spawning duplicates; "Delete" is a soft archive (`archived_at` on `ai_conversations`, data retained). A clean model picker always renders a `Select` of `[defaultModel, ...availableModels]` de-duplicated.
13
+ - The assistant acknowledges a confirm-card decision (a new `decision` mode -> `streamDecision`) instead of going silent after an apply/decline; the decision note is derived server-side from the stored proposal and is ephemeral.
14
+ - A cheap topical pre-classifier short-circuits off-topic turns with a canned refusal (fail-open, spend recorded). It marks meta/capability/greeting/how-to questions as ON_TOPIC; only clearly unrelated requests (coding help, creative writing, trivia) are refused.
15
+ - The chat agent no longer emits duplicate proposals for one request: propose/auto-apply results carry an explicit model-facing "stop and wait" note, and a per-turn `<tool>:<argsHash>` dedupe short-circuits repeated identical mutating calls.
16
+ - Assistant messages render through the shared `<MarkdownBlock>`: it now parses a SAFE subset of raw HTML (`rehype-raw` + `rehype-sanitize`) so native `<details>`/`<summary>` widgets render, and enables `remark-gfm` so GFM tables, strikethrough, and autolinks render (the assistant often summarizes drafts as tables).
17
+
18
+ State and scale: the archive marker, titles, and permission mode all live in the shared `ai_conversations` table, read identically on every pod; the classifier holds no state and its spend is recorded in the shared `ai_spend` ledger. No new pod-local state.
19
+
20
+ This is a beta minor.
21
+
22
+ - 9dcc848: Add the AI platform: a transport-agnostic tool spine, an OAuth Authorization Server + read-only MCP server, a propose/apply flow with audit log, a streaming in-app chat agent, per-conversation permission modes, per-integration spend caps, and user-scoped tool authorization.
23
+
24
+ Two new packages, `@checkstack/ai-common` (the `AiTool` contract, `read`/`mutate`/`destructive` effect classification, the `ai.*` access rules, the OpenAI-compatible connection shape, and the wire contracts) and `@checkstack/ai-backend` (the tool registry, extension points, principal-to-tool resolver, shared zod-to-JSON-Schema serializer, and all transports). The OpenAI-compatible integration provider registers through the existing integration provider extension point, so its API key is stored in the Secrets Vault and configured in the generic Connections UI.
25
+
26
+ What ships:
27
+
28
+ - Tool spine and extension points: `aiToolExtensionPoint.registerTool` (hand-authored composite tools) and `aiToolProjectionExtensionPoint.expose` (opt-in projections of existing oRPC procedures). Authorization mirrors `autoAuthMiddleware` exactly - a tool is surfaced only when every `requiredAccessRules` entry is satisfied, so a scope-narrowed principal can only ever see fewer tools.
29
+ - OAuth + MCP: Checkstack can act as its own OAuth 2.1 Authorization Server (authorization code + PKCE, consent screen, Dynamic Client Registration) and expose a read-only MCP server over Streamable HTTP at `/api/ai/mcp`. Off by default, enabled by the admin `ai.mcp-oauth` setting. A Bearer OAuth-token branch is added to the auth strategy; token scopes are intersected live with the bound user's access rules on every call. A shared-Postgres rate limiter throttles the DCR endpoint per client IP. `getMcpOAuthSettings` / `setMcpOAuthSettings` contracts added to `@checkstack/auth-common`. A minimal OAuth consent page (`/auth/oauth-consent`) renders the requesting client and scopes.
30
+ - Propose/apply + audit: a transport-agnostic two-step service - `propose` re-checks authz, runs the tool's `dryRun` without mutating, and returns a single-use proposal token (the `proposed` audit row IS the token store, 10-minute TTL, atomic single-use); `apply` re-parses the server-stored payload, re-checks authz, and atomically commits. The `ai_tool_calls` audit table records every call across both transports with a SHA-256 args hash (never raw arguments) and stamps who proposed and who applied. An `ai.toolCalled` event carries metadata only.
31
+ - In-app chat: a server-side, provider-agnostic Vercel AI SDK agent loop (OpenAI, Azure, OpenRouter, Ollama, vLLM, LM Studio, ...). The model provider is built on the backend from the integration credentials, so the API key never leaves the backend. The loop offers only resolver-allowed tools, auto-runs read tools (re-entering the live router as the logged-in user) and routes mutating / destructive tools through propose/apply. Durable conversation persistence (`ai_conversations`, `ai_messages`, owner-scoped RPCs) plus a streaming chat UI with a confirm-card component and per-integration model picker.
32
+ - Per-conversation permission mode (Claude-Code-style approve/auto), a durable `permission_mode` column on `ai_conversations` (default `approve`). `read` always auto-runs in both modes; `mutate` inherits the mode (auto-applies server-side in `auto`, confirm-carded in `approve`); `destructive` ALWAYS requires the human `applyTool` in both modes. Security invariant (structural + tested): the mode is consulted only on the `mutate` branch, so no `(effect, mode)` pair routes a destructive tool to auto-apply.
33
+ - Per-integration LLM spend cap (optional `spendCap` = `tokenBudget` + `windowMinutes`, default OFF). Spend is tracked in a shared-Postgres `ai_spend` ledger; enforcement is a rolling-window SUM run before each turn (HTTP 429 over budget). Per-principal tool rate-limit budgets are a rolling COUNT over `ai_tool_calls`, enforced on both transports. An absent / empty / incomplete `spendCap` is treated as "no cap" rather than rejected.
34
+ - Full tool-call replay: `ai_messages.model_messages` (jsonb) persists the canonical AI-SDK `ResponseMessage[]` per turn and replays them verbatim on the next turn; legacy rows fall back to text-only replay.
35
+ - Enforced no-secret-leak scrubbing: `appendMessage` runs `scrubContent` on every write, redacting credential-shaped keys and high-confidence credential values; a canary regression test asserts injected secrets are stripped. A hardening test suite asserts no secret appears in any AI-surface DTO and that handler-side authz holds when the model misbehaves.
36
+ - Provider correctness: the chat provider uses `@ai-sdk/openai-compatible`'s `chatModel` (plain `/chat/completions`), so OpenAI-compatible gateways (OpenRouter, DeepSeek, Ollama, vLLM) no longer reject turns with `invalid_prompt`; `@ai-sdk/openai` is removed.
37
+
38
+ BREAKING CHANGES:
39
+
40
+ - The `AiTool` contract (`@checkstack/ai-common`) gained a `TRpc` type parameter, and both `dryRun` and `execute` now receive a USER-SCOPED `rpcClient` arg bound to the originating user. Every plugin procedure a tool calls re-enters the live router AS THAT USER, so handler-side authorization (access rules AND per-resource/team scope) is enforced exactly as a direct UI/RPC call - closing a prior privilege-escalation where tools captured a trusted service client at construction. A hand-authored tool MUST resolve its plugin client from this per-call arg and MUST NOT capture a trusted service client at factory scope. Tool factories that previously took `{ rpcClient }` should drop that parameter.
41
+ - `AiToolProjectionExtensionPoint.expose` no longer takes a second `pluginMetadata` argument; the owning metadata lives on `input.sourcePluginMetadata`. Callers must drop the second argument.
42
+
43
+ State and scale: conversations, messages, the audit log, proposal tokens, the rate-limit counter, and the spend ledger all live in shared Postgres, so every pod answers identically and the agent loop is resumable on any pod. The only pod-local state is the live MCP connection registry (bookkeeping, never a source of truth). Cross-pod conversation readback, the spend cap, and the tool budget are verified by env-gated two-pod integration tests.
44
+
45
+ This is a beta minor.
46
+
47
+ - 9dcc848: Plugin-owned AI tools: every domain plugin contributes its own AI tools (chat assistant + automation AI action), and `ai-backend` is platform-only.
48
+
49
+ Every plugin-specific AI tool is owned by the plugin whose domain it acts on, registered via that plugin's own `aiToolExtensionPoint` / `aiToolProjectionExtensionPoint` from its init - the same path an external plugin author uses. `ai-backend` no longer imports or depends on any capability plugin's `*-common`; the dependency direction is strictly plugin -> ai-platform. Pure helpers (`computeFieldDiff`, capability-summary, `ScriptContextKind`) live in `@checkstack/ai-common`.
50
+
51
+ Tools shipped:
52
+
53
+ - Health checks and automations: full CRUD - `healthcheck.propose` / `automation.propose` and `*.update` (`mutate`, deep-validated) and `*.delete` (`destructive`, always confirm-gated). `healthcheck.propose`'s dry-run calls the new deep `validateConfiguration` so propose-time validation matches apply-time. Assertions are validated against the collector's result schema and the canonical operator vocabulary. Capability-catalog tools (`ai.listCapabilities`, `ai.getCapabilitySchema`), script context tools (`ai.getScriptContext`, `ai.testScript`), and notify-subscriber tools (`healthcheck.notifySystemSubscribers` / `...GroupSubscribers`).
54
+ - Catalog: `catalog.createSystem` / `updateSystem` / `createGroup` / `updateGroup` (`mutate`), `catalog.deleteSystem` / `deleteGroup` (`destructive`), membership tools (`mutate`), plus `catalog.listSystems` / `listGroups` read projections.
55
+ - Incident: `incident.create` / `update` / `addUpdate` / `resolve` / `addLink` (`mutate`), `incident.delete` / `removeLink` (`destructive`), and `incident.get` / `incident.list` read projections.
56
+ - Maintenance: `maintenance.create` / `update` / `addUpdate` / `close` / `addLink` (`mutate`), `maintenance.delete` / `removeLink` (`destructive`), and `maintenance.list` / `get` read projections.
57
+ - Read projections for SLO (`slo.listObjectives`), dependency (`dependency.list`), incident (`incident.list`), healthcheck (`healthcheck.status`), and anomaly (`anomaly.explain`), each gated by the source procedure's own access rule and routed as the principal.
58
+ - Documentation grounding: `ai.searchDocs` / `ai.getDoc` over a build-time bundled docs index (BM25-ish ranking), so the assistant grounds how-to answers in Checkstack's own docs offline.
59
+ - URL introspection: `ai.probeUrl`, an SSRF-guarded read tool the assistant uses to inspect a real endpoint before drafting a health check. Update tools compute a before -> after field diff rendered on the confirm card (approve mode) or an "Applied" card (auto mode), so a change is never silent.
60
+
61
+ `ai_analyze` automation action (automation-backend, with an editor connection picker + audited tool calls): runs a bounded AI agent on the run context as the automation's `runAs` service account, so it can never exceed that identity's permissions; destructive tools are never offered; mutating tools auto-apply through the service account's client. Produces an `automation.analysis` artifact downstream actions can branch on. The agent loop is exposed as a headless `aiAgentRunnerRef` service so automation-backend can drive it without depending on ai-backend.
62
+
63
+ `notification.notifyForSubscription` is now callable by user / application principals holding `notification.send` (previously service-only). Every tool routes through the user-scoped client, so handler-side authorization is enforced exactly as a direct UI/RPC action; the resolver gate plus the propose/apply re-check at propose AND apply are the additional authority. A systemic authz regression test asserts every registered tool falls into exactly one safe authorization category.
64
+
65
+ A new `ai_transport` enum value `automation` records the AI action's tool calls in the `ai_tool_calls` audit log. No new durable state beyond that; each tool is a thin, deterministic wrapper over an existing RPC, so every pod behaves identically.
66
+
67
+ This is a beta minor.
68
+
69
+ - 9dcc848: Add a deep `validateConfiguration` RPC to the health-check plugin so propose-time validation matches apply-time validation.
70
+
71
+ - `validateConfiguration` (`@checkstack/healthcheck-common`): a new mutation procedure gated by `healthcheck.healthcheck.manage`, taking a proposed configuration (reusing the create skeleton) and returning `{ valid, errors: [{ path, message }] }`, mirroring automation's `validateDefinition`. It persists nothing.
72
+ - Shared deep validation (`@checkstack/healthcheck-backend`): `collectConfigurationIssues` resolves strategy + collectors by fully-qualified id then migrate-then-validate-strict each config via `parseStrictAssumingV1`. The GitOps reconcile path is refactored to call the same `validateVersionedConfigStrict`, so create / gitops-apply / the new RPC share one implementation.
73
+ - `healthcheck.propose`'s dry-run (`@checkstack/ai-backend`) now calls `validateConfiguration` as its validation authority, so a wrong config type or a typo'd key surfaces at propose time, bringing it to the same deep-validate level `automation.propose` already has.
74
+
75
+ State and scale: no durable state; `validateConfiguration` is a pure read against the in-process registries plus zod validation, identical on every pod.
76
+
77
+ This is a beta minor.
78
+
79
+ ### Patch Changes
80
+
81
+ - Updated dependencies [9dcc848]
82
+ - Updated dependencies [9dcc848]
83
+ - Updated dependencies [9dcc848]
84
+ - Updated dependencies [9dcc848]
85
+ - Updated dependencies [9dcc848]
86
+ - Updated dependencies [9dcc848]
87
+ - Updated dependencies [9dcc848]
88
+ - Updated dependencies [9dcc848]
89
+ - Updated dependencies [9dcc848]
90
+ - Updated dependencies [9dcc848]
91
+ - Updated dependencies [9dcc848]
92
+ - Updated dependencies [9dcc848]
93
+ - @checkstack/ai-common@0.1.0
94
+ - @checkstack/backend-api@0.21.0
95
+ - @checkstack/integration-backend@0.4.0
96
+ - @checkstack/common@0.13.0
97
+ - @checkstack/sdk@0.93.1
@@ -0,0 +1,26 @@
1
+ CREATE TYPE "ai_tool_call_status" AS ENUM('proposed', 'applied', 'executed', 'failed', 'expired', 'rejected');--> statement-breakpoint
2
+ CREATE TYPE "ai_tool_effect" AS ENUM('read', 'mutate', 'destructive');--> statement-breakpoint
3
+ CREATE TYPE "ai_transport" AS ENUM('chat', 'mcp');--> statement-breakpoint
4
+ CREATE TABLE "ai_tool_calls" (
5
+ "id" text PRIMARY KEY NOT NULL,
6
+ "principal_kind" text NOT NULL,
7
+ "principal_id" text NOT NULL,
8
+ "transport" "ai_transport" NOT NULL,
9
+ "conversation_id" text,
10
+ "tool_name" text NOT NULL,
11
+ "effect" "ai_tool_effect" NOT NULL,
12
+ "args_hash" text NOT NULL,
13
+ "status" "ai_tool_call_status" NOT NULL,
14
+ "proposal_nonce" text,
15
+ "proposal_expires_at" timestamp,
16
+ "result_snapshot" jsonb,
17
+ "proposed_payload" jsonb,
18
+ "error" text,
19
+ "proposed_at" timestamp,
20
+ "applied_at" timestamp,
21
+ "created_at" timestamp DEFAULT now() NOT NULL
22
+ );
23
+ --> statement-breakpoint
24
+ CREATE INDEX "ai_tool_calls_principal_created_idx" ON "ai_tool_calls" USING btree ("principal_kind","principal_id","created_at");--> statement-breakpoint
25
+ CREATE INDEX "ai_tool_calls_status_expires_idx" ON "ai_tool_calls" USING btree ("status","proposal_expires_at");--> statement-breakpoint
26
+ CREATE INDEX "ai_tool_calls_conversation_idx" ON "ai_tool_calls" USING btree ("conversation_id");
@@ -0,0 +1,26 @@
1
+ CREATE TYPE "ai_message_role" AS ENUM('system', 'user', 'assistant', 'tool');--> statement-breakpoint
2
+ CREATE TABLE "ai_conversations" (
3
+ "id" text PRIMARY KEY NOT NULL,
4
+ "user_id" text NOT NULL,
5
+ "title" text,
6
+ "integration_id" text,
7
+ "model" text,
8
+ "created_at" timestamp DEFAULT now() NOT NULL,
9
+ "updated_at" timestamp DEFAULT now() NOT NULL
10
+ );
11
+ --> statement-breakpoint
12
+ CREATE TABLE "ai_messages" (
13
+ "id" text PRIMARY KEY NOT NULL,
14
+ "conversation_id" text NOT NULL,
15
+ "role" "ai_message_role" NOT NULL,
16
+ "content" jsonb NOT NULL,
17
+ "tool_calls" jsonb,
18
+ "created_at" timestamp DEFAULT now() NOT NULL
19
+ );
20
+ --> statement-breakpoint
21
+ ALTER TABLE "ai_tool_calls" ADD COLUMN "applied_by_kind" text;--> statement-breakpoint
22
+ ALTER TABLE "ai_tool_calls" ADD COLUMN "applied_by_id" text;--> statement-breakpoint
23
+ ALTER TABLE "ai_messages" ADD CONSTRAINT "ai_messages_conversation_id_ai_conversations_id_fk" FOREIGN KEY ("conversation_id") REFERENCES "ai_conversations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
24
+ CREATE INDEX "ai_conversations_user_idx" ON "ai_conversations" USING btree ("user_id","updated_at");--> statement-breakpoint
25
+ CREATE INDEX "ai_messages_conversation_idx" ON "ai_messages" USING btree ("conversation_id","created_at");--> statement-breakpoint
26
+ ALTER TABLE "ai_tool_calls" ADD CONSTRAINT "ai_tool_calls_conversation_id_ai_conversations_id_fk" FOREIGN KEY ("conversation_id") REFERENCES "ai_conversations"("id") ON DELETE set null ON UPDATE no action;
@@ -0,0 +1,15 @@
1
+ CREATE TABLE "ai_spend" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "integration_id" text NOT NULL,
4
+ "principal_kind" text NOT NULL,
5
+ "principal_id" text NOT NULL,
6
+ "conversation_id" text,
7
+ "model" text,
8
+ "input_tokens" integer DEFAULT 0 NOT NULL,
9
+ "output_tokens" integer DEFAULT 0 NOT NULL,
10
+ "total_tokens" integer DEFAULT 0 NOT NULL,
11
+ "created_at" timestamp DEFAULT now() NOT NULL
12
+ );
13
+ --> statement-breakpoint
14
+ ALTER TABLE "ai_messages" ADD COLUMN "model_messages" jsonb;--> statement-breakpoint
15
+ CREATE INDEX "ai_spend_integration_principal_created_idx" ON "ai_spend" USING btree ("integration_id","principal_kind","principal_id","created_at");
@@ -0,0 +1 @@
1
+ ALTER TABLE "ai_conversations" ADD COLUMN "archived_at" timestamp;
@@ -0,0 +1,2 @@
1
+ CREATE TYPE "ai_permission_mode" AS ENUM('approve', 'auto');--> statement-breakpoint
2
+ ALTER TABLE "ai_conversations" ADD COLUMN "permission_mode" "ai_permission_mode" DEFAULT 'approve' NOT NULL;
@@ -0,0 +1 @@
1
+ ALTER TYPE "ai_transport" ADD VALUE 'automation';
@@ -0,0 +1,232 @@
1
+ {
2
+ "id": "2b6f36f7-1660-4cb7-95ab-abb0221396d8",
3
+ "prevId": "00000000-0000-0000-0000-000000000000",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.ai_tool_calls": {
8
+ "name": "ai_tool_calls",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "text",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ },
17
+ "principal_kind": {
18
+ "name": "principal_kind",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true
22
+ },
23
+ "principal_id": {
24
+ "name": "principal_id",
25
+ "type": "text",
26
+ "primaryKey": false,
27
+ "notNull": true
28
+ },
29
+ "transport": {
30
+ "name": "transport",
31
+ "type": "ai_transport",
32
+ "typeSchema": "public",
33
+ "primaryKey": false,
34
+ "notNull": true
35
+ },
36
+ "conversation_id": {
37
+ "name": "conversation_id",
38
+ "type": "text",
39
+ "primaryKey": false,
40
+ "notNull": false
41
+ },
42
+ "tool_name": {
43
+ "name": "tool_name",
44
+ "type": "text",
45
+ "primaryKey": false,
46
+ "notNull": true
47
+ },
48
+ "effect": {
49
+ "name": "effect",
50
+ "type": "ai_tool_effect",
51
+ "typeSchema": "public",
52
+ "primaryKey": false,
53
+ "notNull": true
54
+ },
55
+ "args_hash": {
56
+ "name": "args_hash",
57
+ "type": "text",
58
+ "primaryKey": false,
59
+ "notNull": true
60
+ },
61
+ "status": {
62
+ "name": "status",
63
+ "type": "ai_tool_call_status",
64
+ "typeSchema": "public",
65
+ "primaryKey": false,
66
+ "notNull": true
67
+ },
68
+ "proposal_nonce": {
69
+ "name": "proposal_nonce",
70
+ "type": "text",
71
+ "primaryKey": false,
72
+ "notNull": false
73
+ },
74
+ "proposal_expires_at": {
75
+ "name": "proposal_expires_at",
76
+ "type": "timestamp",
77
+ "primaryKey": false,
78
+ "notNull": false
79
+ },
80
+ "result_snapshot": {
81
+ "name": "result_snapshot",
82
+ "type": "jsonb",
83
+ "primaryKey": false,
84
+ "notNull": false
85
+ },
86
+ "proposed_payload": {
87
+ "name": "proposed_payload",
88
+ "type": "jsonb",
89
+ "primaryKey": false,
90
+ "notNull": false
91
+ },
92
+ "error": {
93
+ "name": "error",
94
+ "type": "text",
95
+ "primaryKey": false,
96
+ "notNull": false
97
+ },
98
+ "proposed_at": {
99
+ "name": "proposed_at",
100
+ "type": "timestamp",
101
+ "primaryKey": false,
102
+ "notNull": false
103
+ },
104
+ "applied_at": {
105
+ "name": "applied_at",
106
+ "type": "timestamp",
107
+ "primaryKey": false,
108
+ "notNull": false
109
+ },
110
+ "created_at": {
111
+ "name": "created_at",
112
+ "type": "timestamp",
113
+ "primaryKey": false,
114
+ "notNull": true,
115
+ "default": "now()"
116
+ }
117
+ },
118
+ "indexes": {
119
+ "ai_tool_calls_principal_created_idx": {
120
+ "name": "ai_tool_calls_principal_created_idx",
121
+ "columns": [
122
+ {
123
+ "expression": "principal_kind",
124
+ "isExpression": false,
125
+ "asc": true,
126
+ "nulls": "last"
127
+ },
128
+ {
129
+ "expression": "principal_id",
130
+ "isExpression": false,
131
+ "asc": true,
132
+ "nulls": "last"
133
+ },
134
+ {
135
+ "expression": "created_at",
136
+ "isExpression": false,
137
+ "asc": true,
138
+ "nulls": "last"
139
+ }
140
+ ],
141
+ "isUnique": false,
142
+ "concurrently": false,
143
+ "method": "btree",
144
+ "with": {}
145
+ },
146
+ "ai_tool_calls_status_expires_idx": {
147
+ "name": "ai_tool_calls_status_expires_idx",
148
+ "columns": [
149
+ {
150
+ "expression": "status",
151
+ "isExpression": false,
152
+ "asc": true,
153
+ "nulls": "last"
154
+ },
155
+ {
156
+ "expression": "proposal_expires_at",
157
+ "isExpression": false,
158
+ "asc": true,
159
+ "nulls": "last"
160
+ }
161
+ ],
162
+ "isUnique": false,
163
+ "concurrently": false,
164
+ "method": "btree",
165
+ "with": {}
166
+ },
167
+ "ai_tool_calls_conversation_idx": {
168
+ "name": "ai_tool_calls_conversation_idx",
169
+ "columns": [
170
+ {
171
+ "expression": "conversation_id",
172
+ "isExpression": false,
173
+ "asc": true,
174
+ "nulls": "last"
175
+ }
176
+ ],
177
+ "isUnique": false,
178
+ "concurrently": false,
179
+ "method": "btree",
180
+ "with": {}
181
+ }
182
+ },
183
+ "foreignKeys": {},
184
+ "compositePrimaryKeys": {},
185
+ "uniqueConstraints": {},
186
+ "policies": {},
187
+ "checkConstraints": {},
188
+ "isRLSEnabled": false
189
+ }
190
+ },
191
+ "enums": {
192
+ "public.ai_tool_call_status": {
193
+ "name": "ai_tool_call_status",
194
+ "schema": "public",
195
+ "values": [
196
+ "proposed",
197
+ "applied",
198
+ "executed",
199
+ "failed",
200
+ "expired",
201
+ "rejected"
202
+ ]
203
+ },
204
+ "public.ai_tool_effect": {
205
+ "name": "ai_tool_effect",
206
+ "schema": "public",
207
+ "values": [
208
+ "read",
209
+ "mutate",
210
+ "destructive"
211
+ ]
212
+ },
213
+ "public.ai_transport": {
214
+ "name": "ai_transport",
215
+ "schema": "public",
216
+ "values": [
217
+ "chat",
218
+ "mcp"
219
+ ]
220
+ }
221
+ },
222
+ "schemas": {},
223
+ "sequences": {},
224
+ "roles": {},
225
+ "policies": {},
226
+ "views": {},
227
+ "_meta": {
228
+ "columns": {},
229
+ "schemas": {},
230
+ "tables": {}
231
+ }
232
+ }