- Updated `.env.example` to specify that `agentfs-local` is for self-hosted deployments only, while hosted Convex deployments should use `agentfs-cloudflare`. - Expanded `README.md` to include detailed environment variable descriptions for agent storage, including new variables for Cloudflare integration. - Improved the `StoragePanel` component to manage storage instances more effectively, including state handling for file previews and SQL queries. - Added new storage-related actions in `executorNode.ts` for listing directories, reading files, and querying SQL, ensuring better integration with the storage provider. - Enhanced validation and error handling in storage-related database functions to prevent unsupported configurations in hosted environments. - Updated tool contracts to include new storage capabilities and improved JSON schema handling for key-value operations.
Unified · Split
| @@ -15,6 +15,8 @@ CONVEX_SITE_URL=https://your-deployment.convex.site · 14 unmodified lines | |||
| 15 | 15 | ||
| 16 | # ── Agent storage backend (optional) ── | 16 | # ── Agent storage backend (optional) ── |
| 17 | # Default: agentfs-local | 17 | # Default: agentfs-local |
| 18 | # NOTE: agentfs-local is for self-hosted/single-host deployments only. | ||
| 19 | # Hosted Convex deployments must use AGENT_STORAGE_PROVIDER=agentfs-cloudflare. | ||
| 18 | # AGENT_STORAGE_PROVIDER=agentfs-local | 20 | # AGENT_STORAGE_PROVIDER=agentfs-local |
| 19 | # AGENT_STORAGE_ROOT=/tmp/executor-agentfs | 21 | # AGENT_STORAGE_ROOT=/tmp/executor-agentfs |
| 20 | # For Cloudflare Durable Objects storage proxy (served by runner-sandbox-host): | 22 | # For Cloudflare Durable Objects storage proxy (served by runner-sandbox-host): |
| @@ -148,6 +148,10 @@ Important env vars (see root `.env.example` for the base template): · 147 unmodified lines | |||
| 148 | - `CONVEX_URL` | 148 | - `CONVEX_URL` |
| 149 | - `CONVEX_SITE_URL` | 149 | - `CONVEX_SITE_URL` |
| 150 | - `DANGEROUSLY_ALLOW_LOCAL_VM` (`1` in local/self-hosted deployments to allow `local-bun` runtime; production leaves unset for Cloudflare-first behavior) | 150 | - `DANGEROUSLY_ALLOW_LOCAL_VM` (`1` in local/self-hosted deployments to allow `local-bun` runtime; production leaves unset for Cloudflare-first behavior) |
| 151 | - Agent storage: | ||
| 152 | - `AGENT_STORAGE_PROVIDER` (`agentfs-local` for self-hosted/single-host only; hosted Convex requires `agentfs-cloudflare`) | ||
| 153 | - `AGENT_STORAGE_ROOT` (optional local storage root for `agentfs-local`) | ||
| 154 | - `CLOUDFLARE_SANDBOX_RUN_URL` and `CLOUDFLARE_SANDBOX_AUTH_TOKEN` (required when using `agentfs-cloudflare`) | ||
| 151 | - WorkOS (optional auth/org features): | 155 | - WorkOS (optional auth/org features): |
| 152 | - `WORKOS_CLIENT_ID` | 156 | - `WORKOS_CLIENT_ID` |
| 153 | - `WORKOS_API_KEY` | 157 | - `WORKOS_API_KEY` |
| @@ -225,4 +229,5 @@ executor/ · 71 unmodified lines | |||
| 225 | - `401` on `/mcp`: verify your bearer token issuer matches `MCP_AUTHORIZATION_SERVER`. | 229 | - `401` on `/mcp`: verify your bearer token issuer matches `MCP_AUTHORIZATION_SERVER`. |
| 226 | - `401` on `/mcp/anonymous`: pass `API_KEY` from the setup card as `Authorization: Bearer <API_KEY>` (or `x-api-key`). | 230 | - `401` on `/mcp/anonymous`: pass `API_KEY` from the setup card as `Authorization: Bearer <API_KEY>` (or `x-api-key`). |
| 227 | - Web UI cannot load data: verify `CONVEX_URL` / `CONVEX_SITE_URL` and that Convex dev is running. | 231 | - Web UI cannot load data: verify `CONVEX_URL` / `CONVEX_SITE_URL` and that Convex dev is running. |
| 232 | - `ENOENT` after `fs.write` on hosted Convex: `agentfs-local` is unsupported on hosted Convex because local filesystem state is not shared across workers. Use `AGENT_STORAGE_PROVIDER=agentfs-cloudflare`. | ||
| 228 | - Release build missing managed artifacts: run `bun run build:release` and verify `executor/dist/release/` contains `executor-*.tar.gz`, `executor-web-*.tar.gz`, and `executor-runtime-*.tar.gz`. | 233 | - Release build missing managed artifacts: run `bun run build:release` and verify `executor/dist/release/` contains `executor-*.tar.gz`, `executor-web-*.tar.gz`, and `executor-runtime-*.tar.gz`. |
| @@ -1,12 +1,24 @@ | |||
| 1 | "use client"; | 1 | "use client"; |
| 2 | 2 | ||
| 3 | import { useMemo, useState } from "react"; | 3 | import { useEffect, useMemo, useState } from "react"; |
| 4 | import { Database, FolderOpen, Plus, Trash2, Power } from "lucide-react"; | 4 | import { useAction } from "convex/react"; |
| 5 | import { | ||
| 6 | Database, | ||
| 7 | FolderOpen, | ||
| 8 | HardDrive, | ||
| 9 | KeyRound, | ||
| 10 | Play, | ||
| 11 | Plus, | ||
| 12 | Power, | ||
| 13 | Table, | ||
| 14 | Trash2, | ||
| 15 | } from "lucide-react"; | ||
| 5 | import { Badge } from "@/components/ui/badge"; | 16 | import { Badge } from "@/components/ui/badge"; |
| 6 | import { Button } from "@/components/ui/button"; | 17 | import { Button } from "@/components/ui/button"; |
| 7 | import { Input } from "@/components/ui/input"; | 18 | import { Input } from "@/components/ui/input"; |
| 8 | import { Skeleton } from "@/components/ui/skeleton"; | 19 | import { Skeleton } from "@/components/ui/skeleton"; |
| 9 | import type { StorageDurability, StorageInstanceRecord, StorageScopeType } from "@/lib/types"; | 20 | import type { StorageDurability, StorageInstanceRecord, StorageScopeType } from "@/lib/types"; |
| 21 | import { convexApi } from "@/lib/convex-api"; | ||
| 10 | import { cn } from "@/lib/utils"; | 22 | import { cn } from "@/lib/utils"; |
| 11 | 23 | ||
| 12 | type CreateStorageArgs = { | 24 | type CreateStorageArgs = { |
| @@ -16,6 +28,49 @@ type CreateStorageArgs = { · 3 unmodified lines | |||
| 16 | ttlHours?: number; | 28 | ttlHours?: number; |
| 17 | }; | 29 | }; |
| 18 | 30 | ||
| 31 | type StorageDirectoryEntry = { | ||
| 32 | name: string; | ||
| 33 | type: "file" | "directory" | "symlink" | "unknown"; | ||
| 34 | size?: number; | ||
| 35 | mtime?: number; | ||
| 36 | }; | ||
| 37 | |||
| 38 | type StorageSqlResult = { | ||
| 39 | mode: "read" | "write"; | ||
| 40 | rows?: Record<string, unknown>[]; | ||
| 41 | rowCount: number; | ||
| 42 | changes?: number; | ||
| 43 | }; | ||
| 44 | |||
| 45 | type StorageSqlObject = { | ||
| 46 | name: string; | ||
| 47 | type: "table" | "view" | "unknown"; | ||
| 48 | }; | ||
| 49 | |||
| 50 | const USER_TABLES_QUERY = [ | ||
| 51 | "SELECT name", | ||
| 52 | "FROM sqlite_master", | ||
| 53 | "WHERE type = 'table'", | ||
| 54 | " AND name NOT LIKE 'sqlite_%'", | ||
| 55 | " AND name NOT IN ('fs_config', 'fs_data', 'fs_dentry', 'fs_inode', 'fs_symlink', 'kv_store')", | ||
| 56 | "ORDER BY name", | ||
| 57 | ].join("\n"); | ||
| 58 | |||
| 59 | const ALL_OBJECTS_QUERY = "SELECT name, type FROM sqlite_master ORDER BY name"; | ||
| 60 | const KV_DATA_QUERY = "SELECT key, value, updated_at FROM kv_store ORDER BY key LIMIT 200"; | ||
| 61 | const FS_ENTRIES_QUERY = "SELECT * FROM fs_dentry LIMIT 200"; | ||
| 62 | const SQL_OBJECTS_QUERY = "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') ORDER BY name"; | ||
| 63 | |||
| 64 | const INTERNAL_SQL_OBJECT_NAMES = new Set([ | ||
| 65 | "fs_config", | ||
| 66 | "fs_data", | ||
| 67 | "fs_dentry", | ||
| 68 | "fs_inode", | ||
| 69 | "fs_symlink", | ||
| 70 | "kv_store", | ||
| 71 | "sqlite_sequence", | ||
| 72 | ]); | ||
| 73 | |||
| 19 | function prettyBytes(value?: number): string { | 74 | function prettyBytes(value?: number): string { |
| 20 | if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) { | 75 | if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) { |
| 21 | return "-"; | 76 | return "-"; |
| @@ -39,7 +94,74 @@ function asLocalDate(value?: number): string { · 17 unmodified lines | |||
| 39 | return new Date(value).toLocaleString(); | 94 | return new Date(value).toLocaleString(); |
| 40 | } | 95 | } |
| 41 | 96 | ||
| 97 | function joinStoragePath(basePath: string, name: string): string { | ||
| 98 | const base = (basePath.trim() || "/").replace(/\/+$/, ""); | ||
| 99 | if (!base || base === "/") { | ||
| 100 | return `/${name}`; | ||
| 101 | } | ||
| 102 | return `${base}/${name}`; | ||
| 103 | } | ||
| 104 | |||
| 105 | function previewJson(value: unknown): string { | ||
| 106 | try { | ||
| 107 | return JSON.stringify(value, null, 2); | ||
| 108 | } catch { | ||
| 109 | return String(value); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | function truncateText(text: string, maxChars: number): string { | ||
| 114 | if (text.length <= maxChars) { | ||
| 115 | return text; | ||
| 116 | } | ||
| 117 | return `${text.slice(0, Math.max(1, maxChars))}\n\n...truncated...`; | ||
| 118 | } | ||
| 119 | |||
| 120 | function sqlCellText(value: unknown): string { | ||
| 121 | if (value === null) return "NULL"; | ||
| 122 | if (value === undefined) return ""; | ||
| 123 | if (typeof value === "string") return value; | ||
| 124 | if (typeof value === "number" || typeof value === "boolean") return String(value); | ||
| 125 | return previewJson(value); | ||
| 126 | } | ||
| 127 | |||
| 128 | function collectSqlColumns(rows: Array<Record<string, unknown>>): string[] { | ||
| 129 | const seen = new Set<string>(); | ||
| 130 | const columns: string[] = []; | ||
| 131 | for (const row of rows) { | ||
| 132 | for (const key of Object.keys(row)) { | ||
| 133 | if (seen.has(key)) continue; | ||
| 134 | seen.add(key); | ||
| 135 | columns.push(key); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | return columns; | ||
| 139 | } | ||
| 140 | |||
| 141 | function isInternalSqlObject(name: string): boolean { | ||
| 142 | if (INTERNAL_SQL_OBJECT_NAMES.has(name)) { | ||
| 143 | return true; | ||
| 144 | } | ||
| 145 | if (name.startsWith("fs_")) { | ||
| 146 | return true; | ||
| 147 | } | ||
| 148 | return name.startsWith("sqlite_"); | ||
| 149 | } | ||
| 150 | |||
| 151 | function sqlObjectType(value: unknown): "table" | "view" | "unknown" { | ||
| 152 | if (value === "table" || value === "view") { | ||
| 153 | return value; | ||
| 154 | } | ||
| 155 | return "unknown"; | ||
| 156 | } | ||
| 157 | |||
| 158 | function escapeSqlIdentifier(value: string): string { | ||
| 159 | return value.replaceAll('"', '""'); | ||
| 160 | } | ||
| 161 | |||
| 42 | export function StoragePanel({ | 162 | export function StoragePanel({ |
| 163 | workspaceId, | ||
| 164 | sessionId, | ||
| 43 | instances, | 165 | instances, |
| 44 | loading, | 166 | loading, |
| 45 | creating, | 167 | creating, |
| @@ -48,6 +170,8 @@ export function StoragePanel({ · 2 unmodified lines | |||
| 48 | onClose, | 170 | onClose, |
| 49 | onDelete, | 171 | onDelete, |
| 50 | }: { | 172 | }: { |
| 173 | workspaceId?: string; | ||
| 174 | sessionId?: string; | ||
| 51 | instances: StorageInstanceRecord[]; | 175 | instances: StorageInstanceRecord[]; |
| 52 | loading: boolean; | 176 | loading: boolean; |
| 53 | creating: boolean; | 177 | creating: boolean; |
| @@ -60,12 +184,252 @@ export function StoragePanel({ · 6 unmodified lines | |||
| 60 | const [durability, setDurability] = useState<StorageDurability>("ephemeral"); | 184 | const [durability, setDurability] = useState<StorageDurability>("ephemeral"); |
| 61 | const [purpose, setPurpose] = useState(""); | 185 | const [purpose, setPurpose] = useState(""); |
| 62 | const [ttlHours, setTtlHours] = useState("24"); | 186 | const [ttlHours, setTtlHours] = useState("24"); |
| 187 | const [selectedInstanceId, setSelectedInstanceId] = useState<string | undefined>(undefined); | ||
| 188 | const [activeInspectorTab, setActiveInspectorTab] = useState<"fs" | "kv" | "sql">("fs"); | ||
| 189 | |||
| 190 | const [fsPath, setFsPath] = useState("/"); | ||
| 191 | const [fsEntries, setFsEntries] = useState<StorageDirectoryEntry[]>([]); | ||
| 192 | const [fsLoading, setFsLoading] = useState(false); | ||
| 193 | const [fsError, setFsError] = useState<string | null>(null); | ||
| 194 | const [filePreviewPath, setFilePreviewPath] = useState<string | null>(null); | ||
| 195 | const [filePreviewContent, setFilePreviewContent] = useState<string>(""); | ||
| 196 | const [filePreviewBytes, setFilePreviewBytes] = useState<number | null>(null); | ||
| 197 | const [filePreviewLoading, setFilePreviewLoading] = useState(false); | ||
| 198 | |||
| 199 | const [kvPrefix, setKvPrefix] = useState(""); | ||
| 200 | const [kvLimit, setKvLimit] = useState("100"); | ||
| 201 | const [kvItems, setKvItems] = useState<Array<{ key: string; value: unknown }>>([]); | ||
| 202 | const [kvLoading, setKvLoading] = useState(false); | ||
| 203 | const [kvError, setKvError] = useState<string | null>(null); | ||
| 204 | |||
| 205 | const [sqlText, setSqlText] = useState(USER_TABLES_QUERY); | ||
| 206 | const [sqlMaxRows, setSqlMaxRows] = useState("200"); | ||
| 207 | const [sqlViewMode, setSqlViewMode] = useState<"table" | "json">("table"); | ||
| 208 | const [sqlResult, setSqlResult] = useState<StorageSqlResult | null>(null); | ||
| 209 | const [sqlLoading, setSqlLoading] = useState(false); | ||
| 210 | const [sqlError, setSqlError] = useState<string | null>(null); | ||
| 211 | const [sqlObjects, setSqlObjects] = useState<StorageSqlObject[]>([]); | ||
| 212 | const [sqlObjectsLoading, setSqlObjectsLoading] = useState(false); | ||
| 213 | const [sqlObjectsError, setSqlObjectsError] = useState<string | null>(null); | ||
| 214 | const [sqlShowInternalObjects, setSqlShowInternalObjects] = useState(false); | ||
| 215 | const [selectedSqlObjectName, setSelectedSqlObjectName] = useState<string | null>(null); | ||
| 216 | |||
| 217 | const listDirectory = useAction(convexApi.executorNode.storageListDirectory); | ||
| 218 | const readFileAction = useAction(convexApi.executorNode.storageReadFile); | ||
| 219 | const listKv = useAction(convexApi.executorNode.storageListKv); | ||
| 220 | const querySql = useAction(convexApi.executorNode.storageQuerySql); | ||
| 63 | 221 | ||
| 64 | const visibleInstances = useMemo( | 222 | const visibleInstances = useMemo( |
| 65 | () => [...instances].sort((a, b) => b.updatedAt - a.updatedAt), | 223 | () => [...instances].sort((a, b) => b.updatedAt - a.updatedAt), |
| 66 | [instances], | 224 | [instances], |
| 67 | ); | 225 | ); |
| 68 | 226 | ||
| 227 | const selectedInstance = useMemo( | ||
| 228 | () => visibleInstances.find((instance) => instance.id === selectedInstanceId) ?? visibleInstances[0], | ||
| 229 | [selectedInstanceId, visibleInstances], | ||
| 230 | ); | ||
| 231 | |||
| 232 | useEffect(() => { | ||
| 233 | if (!selectedInstance && visibleInstances.length === 0) { | ||
| 234 | setSelectedInstanceId(undefined); | ||
| 235 | return; | ||
| 236 | } | ||
| 237 | if (!selectedInstance && visibleInstances.length > 0) { | ||
| 238 | setSelectedInstanceId(visibleInstances[0]?.id); | ||
| 239 | return; | ||
| 240 | } | ||
| 241 | if (selectedInstance && selectedInstanceId !== selectedInstance.id) { | ||
| 242 | setSelectedInstanceId(selectedInstance.id); | ||
| 243 | } | ||
| 244 | }, [selectedInstance, selectedInstanceId, visibleInstances]); | ||
| 245 | |||
| 246 | const canInspect = Boolean(workspaceId && selectedInstance); | ||
| 247 | const sqlRows = useMemo(() => (sqlResult?.rows ?? []) as Array<Record<string, unknown>>, [sqlResult]); | ||
| 248 | const sqlColumns = useMemo(() => collectSqlColumns(sqlRows), [sqlRows]); | ||
| 249 | const visibleSqlObjects = useMemo( | ||
| 250 | () => sqlObjects.filter((entry) => sqlShowInternalObjects || !isInternalSqlObject(entry.name)), | ||
| 251 | [sqlObjects, sqlShowInternalObjects], | ||
| 252 | ); | ||
| 253 | |||
| 254 | const refreshDirectory = async (nextPath?: string) => { | ||
| 255 | if (!workspaceId || !selectedInstance) { | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | |||
| 259 | const path = (nextPath ?? fsPath).trim() || "/"; | ||
| 260 | setFsLoading(true); | ||
| 261 | setFsError(null); | ||
| 262 | try { | ||
| 263 | const result = await listDirectory({ | ||
| 264 | workspaceId: workspaceId as never, | ||
| 265 | sessionId, | ||
| 266 | instanceId: selectedInstance.id, | ||
| 267 | path, | ||
| 268 | }); | ||
| 269 | setFsPath(result.path); | ||
| 270 | setFsEntries(result.entries as StorageDirectoryEntry[]); | ||
| 271 | } catch (error) { | ||
| 272 | setFsError(error instanceof Error ? error.message : "Failed to list directory"); | ||
| 273 | setFsEntries([]); | ||
| 274 | } finally { | ||
| 275 | setFsLoading(false); | ||
| 276 | } | ||
| 277 | }; | ||
| 278 | |||
| 279 | const readFilePreview = async (path: string) => { | ||
| 280 | if (!workspaceId || !selectedInstance) { | ||
| 281 | return; | ||
| 282 | } | ||
| 283 | |||
| 284 | setFilePreviewLoading(true); | ||
| 285 | try { | ||
| 286 | const result = await readFileAction({ | ||
| 287 | workspaceId: workspaceId as never, | ||
| 288 | sessionId, | ||
| 289 | instanceId: selectedInstance.id, | ||
| 290 | path, | ||
| 291 | encoding: "utf8", | ||
| 292 | }); | ||
| 293 | setFilePreviewPath(result.path); | ||
| 294 | setFilePreviewContent(truncateText(result.content, 20_000)); | ||
| 295 | setFilePreviewBytes(result.bytes); | ||
| 296 | } catch (error) { | ||
| 297 | setFilePreviewPath(path); | ||
| 298 | setFilePreviewContent(error instanceof Error ? error.message : "Failed to read file"); | ||
| 299 | setFilePreviewBytes(null); | ||
| 300 | } finally { | ||
| 301 | setFilePreviewLoading(false); | ||
| 302 | } | ||
| 303 | }; | ||
| 304 | |||
| 305 | const refreshKv = async () => { | ||
| 306 | if (!workspaceId || !selectedInstance) { | ||
| 307 | return; | ||
| 308 | } | ||
| 309 | |||
| 310 | const parsedLimit = Number.parseInt(kvLimit, 10); | ||
| 311 | setKvLoading(true); | ||
| 312 | setKvError(null); | ||
| 313 | try { | ||
| 314 | const result = await listKv({ | ||
| 315 | workspaceId: workspaceId as never, | ||
| 316 | sessionId, | ||
| 317 | instanceId: selectedInstance.id, | ||
| 318 | prefix: kvPrefix, | ||
| 319 | limit: Number.isFinite(parsedLimit) ? parsedLimit : 100, | ||
| 320 | }); | ||
| 321 | setKvItems(result.items as Array<{ key: string; value: unknown }>); | ||
| 322 | } catch (error) { | ||
| 323 | setKvError(error instanceof Error ? error.message : "Failed to list key-value entries"); | ||
| 324 | setKvItems([]); | ||
| 325 | } finally { | ||
| 326 | setKvLoading(false); | ||
| 327 | } | ||
| 328 | }; | ||
| 329 | |||
| 330 | const runSql = async (queryOverride?: string) => { | ||
| 331 | if (!workspaceId || !selectedInstance) { | ||
| 332 | return; | ||
| 333 | } | ||
| 334 | |||
| 335 | const sql = (queryOverride ?? sqlText).trim(); | ||
| 336 | if (!sql) { | ||
| 337 | return; | ||
| 338 | } | ||
| 339 | |||
| 340 | const parsedMaxRows = Number.parseInt(sqlMaxRows, 10); | ||
| 341 | setSqlLoading(true); | ||
| 342 | setSqlError(null); | ||
| 343 | try { | ||
| 344 | const result = await querySql({ | ||
| 345 | workspaceId: workspaceId as never, | ||
| 346 | sessionId, | ||
| 347 | instanceId: selectedInstance.id, | ||
| 348 | sql, | ||
| 349 | maxRows: Number.isFinite(parsedMaxRows) ? parsedMaxRows : 200, | ||
| 350 | }); | ||
| 351 | if (queryOverride) { | ||
| 352 | setSqlText(sql); | ||
| 353 | } | ||
| 354 | setSqlResult(result as StorageSqlResult); | ||
| 355 | } catch (error) { | ||
| 356 | setSqlError(error instanceof Error ? error.message : "Failed to query SQLite"); | ||
| 357 | setSqlResult(null); | ||
| 358 | } finally { | ||
| 359 | setSqlLoading(false); | ||
| 360 | } | ||
| 361 | }; | ||
| 362 | |||
| 363 | const refreshSqlObjects = async () => { | ||
| 364 | if (!workspaceId || !selectedInstance) { | ||
| 365 | return [] as StorageSqlObject[]; | ||
| 366 | } | ||
| 367 | |||
| 368 | setSqlObjectsLoading(true); | ||
| 369 | setSqlObjectsError(null); | ||
| 370 | try { | ||
| 371 | const result = await querySql({ | ||
| 372 | workspaceId: workspaceId as never, | ||
| 373 | sessionId, | ||
| 374 | instanceId: selectedInstance.id, | ||
| 375 | sql: SQL_OBJECTS_QUERY, | ||
| 376 | maxRows: 1000, | ||
| 377 | }); | ||
| 378 | |||
| 379 | const rows = ((result as StorageSqlResult).rows ?? []) as Array<Record<string, unknown>>; | ||
| 380 | const objects = rows | ||
| 381 | .map((row) => ({ | ||
| 382 | name: typeof row.name === "string" ? row.name : "", | ||
| 383 | type: sqlObjectType(row.type), | ||
| 384 | })) | ||
| 385 | .filter((entry) => entry.name.length > 0); | ||
| 386 | |||
| 387 | setSqlObjects(objects); | ||
| 388 | return objects; | ||
| 389 | } catch (error) { | ||
| 390 | setSqlObjectsError(error instanceof Error ? error.message : "Failed to list SQLite tables"); | ||
| 391 | setSqlObjects([]); | ||
| 392 | return [] as StorageSqlObject[]; | ||
| 393 | } finally { | ||
| 394 | setSqlObjectsLoading(false); | ||
| 395 | } | ||
| 396 | }; | ||
| 397 | |||
| 398 | const openSqlObject = async (objectName: string) => { | ||
| 399 | const parsedMaxRows = Number.parseInt(sqlMaxRows, 10); | ||
| 400 | const limit = Number.isFinite(parsedMaxRows) ? Math.max(1, parsedMaxRows) : 200; | ||
| 401 | setSelectedSqlObjectName(objectName); | ||
| 402 | setSqlViewMode("table"); | ||
| 403 | await runSql(`SELECT * FROM "${escapeSqlIdentifier(objectName)}" LIMIT ${limit}`); | ||
| 404 | }; | ||
| 405 | |||
| 406 | useEffect(() => { | ||
| 407 | if (!canInspect) { | ||
| 408 | setFsEntries([]); | ||
| 409 | setKvItems([]); | ||
| 410 | setSqlResult(null); | ||
| 411 | setSqlObjects([]); | ||
| 412 | setSqlObjectsError(null); | ||
| 413 | setSelectedSqlObjectName(null); | ||
| 414 | return; | ||
| 415 | } | ||
| 416 | |||
| 417 | void refreshDirectory("/"); | ||
| 418 | void refreshKv(); | ||
| 419 | void (async () => { | ||
| 420 | const objects = await refreshSqlObjects(); | ||
| 421 | const preferred = objects.find((entry) => !isInternalSqlObject(entry.name)) ?? objects[0]; | ||
| 422 | if (preferred) { | ||
| 423 | await openSqlObject(preferred.name); | ||
| 424 | return; | ||
| 425 | } | ||
| 426 | await runSql(); | ||
| 427 | })(); | ||
| 428 | setFilePreviewPath(null); | ||
| 429 | setFilePreviewContent(""); | ||
| 430 | setFilePreviewBytes(null); | ||
| 431 | }, [canInspect, selectedInstance?.id]); | ||
| 432 | |||
| 69 | const submitCreate = async () => { | 433 | const submitCreate = async () => { |
| 70 | const parsedTtl = Number.parseInt(ttlHours, 10); | 434 | const parsedTtl = Number.parseInt(ttlHours, 10); |
| 71 | await onCreate({ | 435 | await onCreate({ |
| @@ -78,159 +442,486 @@ export function StoragePanel({ · 6 unmodified lines | |||
| 78 | }; | 442 | }; |
| 79 | 443 | ||
| 80 | return ( | 444 | return ( |
| 81 | <section className="mx-auto flex h-full min-h-0 w-full max-w-6xl flex-col rounded-none border border-border/50 bg-card/30"> | 445 | <section className="flex h-full min-h-0 w-full overflow-hidden rounded-none border border-border/50 bg-card/30"> |
| 82 | <div className="flex items-center justify-between border-b border-border/40 px-4 py-3 sm:px-5"> | 446 | <aside className="flex h-full w-72 shrink-0 flex-col border-r border-border/40 lg:w-80 xl:w-[22rem]"> |
| 83 | <div className="flex items-center gap-2"> | 447 | <div className="flex items-center justify-between border-b border-border/40 px-3 py-2.5"> |
| 84 | <div className="flex h-7 w-7 items-center justify-center border border-border/60 bg-background/80"> | 448 | <div className="flex items-center gap-2"> |
| 85 | <Database className="h-4 w-4 text-muted-foreground" /> | 449 | <div className="flex h-7 w-7 items-center justify-center border border-border/60 bg-background/80"> |
| 450 | <Database className="h-4 w-4 text-muted-foreground" /> | ||
| 451 | </div> | ||
| 452 | <h2 className="text-sm font-medium">Storage</h2> | ||
| 86 | </div> | 453 | </div> |
| 87 | <h2 className="text-sm font-medium">Storage Instances</h2> | 454 | <Badge variant="outline" className="h-5 px-2 text-[10px] font-mono uppercase tracking-wide text-muted-foreground"> |
| 455 | {visibleInstances.length} | ||
| 456 | </Badge> | ||
| 88 | </div> | 457 | </div> |
| 89 | <Badge variant="outline" className="h-5 px-2 text-[10px] font-mono uppercase tracking-wide text-muted-foreground"> | ||
| 90 | {visibleInstances.length} | ||
| 91 | </Badge> | ||
| 92 | </div> | ||
| 93 | 458 | ||
| 94 | <div className="border-b border-border/40 bg-background/40 px-4 py-3 sm:px-5"> | 459 | <div className="shrink-0 border-b border-border/40 bg-background/40 px-3 py-3"> |
| 95 | <div className="grid gap-2 md:grid-cols-5"> | 460 | <div className="space-y-2"> |
| 96 | <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> | 461 | <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> |
| 97 | Scope | 462 | Scope |
| 98 | <select | 463 | <select |
| 99 | className="h-8 rounded-none border border-border/60 bg-background px-2 text-xs text-foreground" | 464 | className="h-8 rounded-none border border-border/60 bg-background px-2 text-xs text-foreground" |
| 100 | value={scopeType} | 465 | value={scopeType} |
| 101 | onChange={(event) => { | 466 | onChange={(event) => { |
| 102 | const next = event.target.value as StorageScopeType; | 467 | const next = event.target.value as StorageScopeType; |
| 103 | setScopeType(next); | 468 | setScopeType(next); |
| 104 | if (next !== "scratch") { | 469 | if (next !== "scratch") { |
| 105 | setDurability("durable"); | 470 | setDurability("durable"); |
| 106 | } | 471 | } |
| 107 | }} | 472 | }} |
| 108 | > | 473 | > |
| 109 | <option value="scratch">scratch</option> | 474 | <option value="scratch">scratch</option> |
| 110 | <option value="account">account</option> | 475 | <option value="account">account</option> |
| 111 | <option value="workspace">workspace</option> | 476 | <option value="workspace">workspace</option> |
| 112 | <option value="organization">organization</option> | 477 | <option value="organization">organization</option> |
| 113 | </select> | 478 | </select> |
| 114 | </label> | 479 | </label> |
| 115 | 480 | ||
| 116 | <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> | 481 | <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> |
| 117 | Durability | 482 | Durability |
| 118 | <select | 483 | <select |
| 119 | className="h-8 rounded-none border border-border/60 bg-background px-2 text-xs text-foreground" | 484 | className="h-8 rounded-none border border-border/60 bg-background px-2 text-xs text-foreground" |
| 120 | value={durability} | 485 | value={durability} |
| 121 | onChange={(event) => setDurability(event.target.value as StorageDurability)} | 486 | onChange={(event) => setDurability(event.target.value as StorageDurability)} |
| 122 | > | 487 | > |
| 123 | <option value="ephemeral">ephemeral</option> | 488 | <option value="ephemeral">ephemeral</option> |
| 124 | <option value="durable">durable</option> | 489 | <option value="durable">durable</option> |
| 125 | </select> | 490 | </select> |
| 126 | </label> | 491 | </label> |
| 127 | 492 | ||
| 128 | <label className="flex flex-col gap-1 text-[11px] text-muted-foreground md:col-span-2"> | 493 | <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> |
| 129 | Purpose | 494 | Purpose |
| 130 | <Input | ||
| 131 | value={purpose} | ||
| 132 | onChange={(event) => setPurpose(event.target.value)} | ||
| 133 | placeholder="scratch for repo indexing" | ||
| 134 | className="h-8 rounded-none text-xs" | ||
| 135 | /> | ||
| 136 | </label> | ||
| 137 | |||
| 138 | <div className="flex items-end gap-2"> | ||
| 139 | <label className="flex flex-1 flex-col gap-1 text-[11px] text-muted-foreground"> | ||
| 140 | TTL (hours) | ||
| 141 | <Input | 495 | <Input |
| 142 | value={ttlHours} | 496 | value={purpose} |
| 143 | onChange={(event) => setTtlHours(event.target.value)} | 497 | onChange={(event) => setPurpose(event.target.value)} |
| 144 | disabled={durability !== "ephemeral"} | 498 | placeholder="scratch for repo indexing" |
| 145 | className="h-8 rounded-none text-xs" | 499 | className="h-8 rounded-none text-xs" |
| 146 | /> | 500 | /> |
| 147 | </label> | 501 | </label> |
| 148 | <Button size="sm" className="h-8 rounded-none text-xs" disabled={creating} onClick={() => void submitCreate()}> | 502 | |
| 149 | <Plus className="mr-1.5 h-3.5 w-3.5" /> | 503 | <div className="flex items-end gap-2"> |
| 150 | Create | 504 | <label className="flex flex-1 flex-col gap-1 text-[11px] text-muted-foreground"> |
| 151 | </Button> | 505 | TTL (hours) |
| 506 | <Input | ||
| 507 | value={ttlHours} | ||
| 508 | onChange={(event) => setTtlHours(event.target.value)} | ||
| 509 | disabled={durability !== "ephemeral"} | ||
| 510 | className="h-8 rounded-none text-xs" | ||
| 511 | /> | ||
| 512 | </label> | ||
| 513 | <Button size="sm" className="h-8 rounded-none text-xs" disabled={creating} onClick={() => void submitCreate()}> | ||
| 514 | <Plus className="mr-1.5 h-3.5 w-3.5" /> | ||
| 515 | Create | ||
| 516 | </Button> | ||
| 517 | </div> | ||
| 152 | </div> | 518 | </div> |
| 153 | </div> | 519 | </div> |
| 154 | </div> | ||
| 155 | 520 | ||
| 156 | <div className="min-h-0 flex-1 overflow-y-auto p-4 sm:p-5"> | 521 | <div className="min-h-0 flex-1 overflow-y-auto p-2"> |
| 157 | {loading ? ( | 522 | {loading ? ( |
| 158 | <div className="space-y-2"> | 523 | <div className="space-y-2"> |
| 159 | {Array.from({ length: 4 }).map((_, i) => ( | 524 | {Array.from({ length: 4 }).map((_, i) => ( |
| 160 | <Skeleton key={i} className="h-16 rounded-none" /> | 525 | <Skeleton key={i} className="h-16 rounded-none" /> |
| 161 | ))} | 526 | ))} |
| 162 | </div> | ||
| 163 | ) : visibleInstances.length === 0 ? ( | ||
| 164 | <div className="flex h-full min-h-[260px] flex-col items-center justify-center gap-2 border border-dashed border-border/50 bg-background/50"> | ||
| 165 | <div className="flex h-10 w-10 items-center justify-center border border-border/60 bg-muted/40"> | ||
| 166 | <FolderOpen className="h-5 w-5 text-muted-foreground/50" /> | ||
| 167 | </div> | 527 | </div> |
| 168 | <p className="text-sm text-muted-foreground">No storage instances yet</p> | 528 | ) : visibleInstances.length === 0 ? ( |
| 169 | <p className="text-[11px] text-muted-foreground/70 text-center max-w-md"> | 529 | <div className="flex h-full min-h-[220px] flex-col items-center justify-center gap-2 border border-dashed border-border/50 bg-background/50 p-3"> |
| 170 | Create a scratch instance for short-lived runs or durable instances for shared state. | 530 | <div className="flex h-10 w-10 items-center justify-center border border-border/60 bg-muted/40"> |
| 171 | </p> | 531 | <FolderOpen className="h-5 w-5 text-muted-foreground/50" /> |
| 172 | </div> | 532 | </div> |
| 173 | ) : ( | 533 | <p className="text-sm text-muted-foreground">No storage instances yet</p> |
| 174 | <div className="space-y-2"> | 534 | </div> |
| 175 | {visibleInstances.map((instance) => { | 535 | ) : ( |
| 176 | const busy = busyInstanceId === instance.id; | 536 | <div className="space-y-2"> |
| 177 | return ( | 537 | {visibleInstances.map((instance) => { |
| 178 | <div | 538 | const busy = busyInstanceId === instance.id; |
| 179 | key={instance.id} | 539 | const active = selectedInstance?.id === instance.id; |
| 180 | className={cn( | 540 | return ( |
| 181 | "group flex items-center gap-3 border border-border/50 bg-background/70 px-3 py-2.5 transition-colors", | 541 | <div |
| 182 | busy ? "opacity-60" : "hover:border-border hover:bg-accent/20", | 542 | key={instance.id} |
| 183 | )} | 543 | className={cn( |
| 184 | style={{ boxShadow: "inset 2px 0 0 0 color-mix(in oklch, var(--border) 70%, transparent)" }} | 544 | "group flex items-center gap-2 border px-2 py-2 transition-colors", |
| 545 | active ? "border-primary/40 bg-primary/5" : "border-border/50 bg-background/70", | ||
| 546 | busy ? "opacity-60" : "hover:border-border hover:bg-accent/20", | ||
| 547 | )} | ||
| 548 | > | ||
| 549 | <button | ||
| 550 | type="button" | ||
| 551 | className="flex min-w-0 flex-1 items-center gap-2 text-left" | ||
| 552 | onClick={() => setSelectedInstanceId(instance.id)} | ||
| 553 | > | ||
| 554 | <div className="flex h-7 w-7 shrink-0 items-center justify-center border border-border/60 bg-muted/50"> | ||
| 555 | <Database className="h-3.5 w-3.5 text-muted-foreground" /> | ||
| 556 | </div> | ||
| 557 | <div className="min-w-0 flex-1"> | ||
| 558 | <p className="truncate text-[12px] font-medium">{instance.purpose || instance.id}</p> | ||
| 559 | <p className="text-[10px] text-muted-foreground"> | ||
| 560 | {instance.scopeType} · {instance.durability} · {instance.status} | ||
| 561 | </p> | ||
| 562 | <p className="text-[10px] text-muted-foreground"> | ||
| 563 | {prettyBytes(instance.sizeBytes)} · {instance.fileCount ?? "-"} inode{instance.fileCount === 1 ? "" : "s"} | ||
| 564 | </p> | ||
| 565 | </div> | ||
| 566 | </button> | ||
| 567 | <div className="flex items-center gap-1"> | ||
| 568 | <Button | ||
| 569 | variant="outline" | ||
| 570 | size="sm" | ||
| 571 | className="h-7 rounded-none px-2 text-[11px]" | ||
| 572 | disabled={busy} | ||
| 573 | onClick={() => void onClose(instance.id)} | ||
| 574 | > | ||
| 575 | <Power className="h-3 w-3" /> | ||
| 576 | </Button> | ||
| 577 | <Button | ||
| 578 | variant="outline" | ||
| 579 | size="sm" | ||
| 580 | className="h-7 rounded-none border-destructive/40 px-2 text-[11px] text-destructive hover:bg-destructive/10" | ||
| 581 | disabled={busy} | ||
| 582 | onClick={() => void onDelete(instance.id)} | ||
| 583 | > | ||
| 584 | <Trash2 className="h-3 w-3" /> | ||
| 585 | </Button> | ||
| 586 | </div> | ||
| 587 | </div> | ||
| 588 | ); | ||
| 589 | })} | ||
| 590 | </div> | ||
| 591 | )} | ||
| 592 | </div> | ||
| 593 | </aside> | ||
| 594 | |||
| 595 | <div className="flex min-h-0 flex-1 min-w-0 flex-col bg-background/50"> | ||
| 596 | {selectedInstance ? ( | ||
| 597 | <> | ||
| 598 | <div className="flex flex-wrap items-center justify-between gap-2 border-b border-border/40 px-4 py-2.5"> | ||
| 599 | <div> | ||
| 600 | <p className="text-xs font-medium">Inspector</p> | ||
| 601 | <p className="text-[11px] text-muted-foreground">{selectedInstance.id} · last used {asLocalDate(selectedInstance.lastSeenAt)}</p> | ||
| 602 | </div> | ||
| 603 | <div className="flex items-center gap-1"> | ||
| 604 | <Button | ||
| 605 | variant={activeInspectorTab === "fs" ? "default" : "outline"} | ||
| 606 | size="sm" | ||
| 607 | className="h-7 rounded-none px-2 text-[11px]" | ||
| 608 | onClick={() => setActiveInspectorTab("fs")} | ||
| 609 | > | ||
| 610 | <HardDrive className="mr-1 h-3 w-3" /> FS | ||
| 611 | </Button> | ||
| 612 | <Button | ||
| 613 | variant={activeInspectorTab === "kv" ? "default" : "outline"} | ||
| 614 | size="sm" | ||
| 615 | className="h-7 rounded-none px-2 text-[11px]" | ||
| 616 | onClick={() => setActiveInspectorTab("kv")} | ||
| 185 | > | 617 | > |
| 186 | <div className="flex h-8 w-8 shrink-0 items-center justify-center border border-border/60 bg-muted/50 overflow-hidden"> | 618 | <KeyRound className="mr-1 h-3 w-3" /> KV |
| 187 | <Database className="h-4 w-4 text-muted-foreground" /> | 619 | </Button> |
| 620 | <Button | ||
| 621 | variant={activeInspectorTab === "sql" ? "default" : "outline"} | ||
| 622 | size="sm" | ||
| 623 | className="h-7 rounded-none px-2 text-[11px]" | ||
| 624 | onClick={() => setActiveInspectorTab("sql")} | ||
| 625 | > | ||
| 626 | <Table className="mr-1 h-3 w-3" /> SQLite | ||
| 627 | </Button> | ||
| 628 | </div> | ||
| 629 | </div> | ||
| 630 | |||
| 631 | {!canInspect ? ( | ||
| 632 | <div className="p-4 text-xs text-muted-foreground">Select a signed-in workspace session to inspect contents.</div> | ||
| 633 | ) : null} | ||
| 634 | |||
| 635 | {activeInspectorTab === "fs" ? ( | ||
| 636 | <div className="space-y-2 p-4"> | ||
| 637 | <div className="flex items-center gap-2"> | ||
| 638 | <Input | ||
| 639 | value={fsPath} | ||
| 640 | onChange={(event) => setFsPath(event.target.value)} | ||
| 641 | className="h-8 rounded-none text-xs" | ||
| 642 | placeholder="/" | ||
| 643 | /> | ||
| 644 | <Button | ||
| 645 | size="sm" | ||
| 646 | className="h-8 rounded-none text-xs" | ||
| 647 | disabled={fsLoading || !canInspect} | ||
| 648 | onClick={() => void refreshDirectory()} | ||
| 649 | > | ||
| 650 | Refresh | ||
| 651 | </Button> | ||
| 652 | </div> | ||
| 653 | {fsError ? <p className="text-[11px] text-destructive">{fsError}</p> : null} | ||
| 654 | |||
| 655 | <div className="max-h-56 overflow-auto border border-border/40"> | ||
| 656 | {fsLoading ? ( | ||
| 657 | <div className="p-2 text-[11px] text-muted-foreground">Loading...</div> | ||
| 658 | ) : fsEntries.length === 0 ? ( | ||
| 659 | <div className="p-2 text-[11px] text-muted-foreground">No entries</div> | ||
| 660 | ) : ( | ||
| 661 | <div className="divide-y divide-border/30"> | ||
| 662 | {fsEntries.map((entry) => { | ||
| 663 | const nextPath = joinStoragePath(fsPath, entry.name); | ||
| 664 | return ( | ||
| 665 | <div key={`${entry.type}:${entry.name}`} className="flex items-center justify-between gap-2 px-2 py-1.5 text-[11px]"> | ||
| 666 | <div className="min-w-0"> | ||
| 667 | <p className="truncate font-medium">{entry.name}</p> | ||
| 668 | <p className="text-muted-foreground">{entry.type} {typeof entry.size === "number" ? `- ${prettyBytes(entry.size)}` : ""}</p> | ||
| 669 | </div> | ||
| 670 | {entry.type === "directory" ? ( | ||
| 671 | <Button variant="outline" size="sm" className="h-6 rounded-none px-2 text-[10px]" onClick={() => void refreshDirectory(nextPath)}> | ||
| 672 | Open | ||
| 673 | </Button> | ||
| 674 | ) : entry.type === "file" ? ( | ||
| 675 | <Button variant="outline" size="sm" className="h-6 rounded-none px-2 text-[10px]" onClick={() => void readFilePreview(nextPath)}> | ||
| 676 | Read | ||
| 677 | </Button> | ||
| 678 | ) : null} | ||
| 679 | </div> | ||
| 680 | ); | ||
| 681 | })} | ||
| 682 | </div> | ||
| 683 | )} | ||
| 684 | </div> | ||
| 685 | |||
| 686 | {filePreviewPath ? ( | ||
| 687 | <div className="border border-border/40"> | ||
| 688 | <div className="flex items-center justify-between border-b border-border/30 px-2 py-1 text-[11px]"> | ||
| 689 | <span className="truncate">{filePreviewPath}</span> | ||
| 690 | <span className="text-muted-foreground">{filePreviewBytes !== null ? prettyBytes(filePreviewBytes) : "-"}</span> | ||
| 691 | </div> | ||
| 692 | <pre className="max-h-[28rem] overflow-auto p-2 text-[11px] leading-5">{filePreviewLoading ? "Loading..." : filePreviewContent}</pre> | ||
| 188 | </div> | 693 | </div> |
| 694 | ) : null} | ||
| 695 | </div> | ||
| 696 | ) : null} | ||
| 189 | 697 | ||
| 190 | <div className="flex-1 min-w-0"> | 698 | {activeInspectorTab === "kv" ? ( |
| 191 | <div className="mb-1 flex items-center gap-1.5 flex-wrap"> | 699 | <div className="space-y-2 p-4"> |
| 192 | <span className="text-sm font-medium truncate">{instance.purpose || instance.id}</span> | 700 | <div className="flex items-center gap-2"> |
| 193 | <Badge variant="outline" className="text-[9px] font-mono uppercase tracking-wider">{instance.scopeType}</Badge> | 701 | <Input |
| 194 | <Badge variant="outline" className="text-[9px] font-mono uppercase tracking-wider">{instance.durability}</Badge> | 702 | value={kvPrefix} |
| 195 | <Badge variant="outline" className="text-[9px] font-mono uppercase tracking-wider">{instance.provider}</Badge> | 703 | onChange={(event) => setKvPrefix(event.target.value)} |
| 196 | <Badge variant="outline" className="text-[9px] font-mono uppercase tracking-wider">{instance.status}</Badge> | 704 | className="h-8 rounded-none text-xs" |
| 705 | placeholder="prefix" | ||
| 706 | /> | ||
| 707 | <Input | ||
| 708 | value={kvLimit} | ||
| 709 | onChange={(event) => setKvLimit(event.target.value)} | ||
| 710 | className="h-8 w-24 rounded-none text-xs" | ||
| 711 | placeholder="100" | ||
| 712 | /> | ||
| 713 | <Button | ||
| 714 | size="sm" | ||
| 715 | className="h-8 rounded-none text-xs" | ||
| 716 | disabled={kvLoading || !canInspect} | ||
| 717 | onClick={() => void refreshKv()} | ||
| 718 | > | ||
| 719 | Refresh | ||
| 720 | </Button> | ||
| 721 | </div> | ||
| 722 | {kvError ? <p className="text-[11px] text-destructive">{kvError}</p> : null} | ||
| 723 | |||
| 724 | <div className="max-h-[36rem] overflow-auto border border-border/40"> | ||
| 725 | {kvLoading ? ( | ||
| 726 | <div className="p-2 text-[11px] text-muted-foreground">Loading...</div> | ||
| 727 | ) : kvItems.length === 0 ? ( | ||
| 728 | <div className="p-2 text-[11px] text-muted-foreground">No key-value entries</div> | ||
| 729 | ) : ( | ||
| 730 | <div className="divide-y divide-border/30"> | ||
| 731 | {kvItems.map((item) => ( | ||
| 732 | <div key={item.key} className="px-2 py-1.5 text-[11px]"> | ||
| 733 | <p className="font-medium">{item.key}</p> | ||
| 734 | <pre className="overflow-auto text-muted-foreground">{truncateText(previewJson(item.value), 4000)}</pre> | ||
| 735 | </div> | ||
| 736 | ))} | ||
| 197 | </div> | 737 | </div> |
| 738 | )} | ||
| 739 | </div> | ||
| 740 | </div> | ||
| 741 | ) : null} | ||
| 198 | 742 | ||
| 199 | <p className="text-[11px] text-muted-foreground"> | 743 | {activeInspectorTab === "sql" ? ( |
| 200 | {prettyBytes(instance.sizeBytes)} - {instance.fileCount ?? "-"} inode{instance.fileCount === 1 ? "" : "s"} | 744 | <div className="grid h-full min-h-0 gap-3 p-4 lg:grid-cols-[15rem_minmax(0,1fr)]"> |
| 201 | </p> | 745 | <div className="flex min-h-0 flex-col border border-border/40"> |
| 202 | <p className="mt-0.5 text-[11px] text-muted-foreground"> | 746 | <div className="flex items-center justify-between border-b border-border/30 px-2 py-1 text-[11px] text-muted-foreground"> |
| 203 | Last used {asLocalDate(instance.lastSeenAt)} | 747 | <span>Tables</span> |
| 204 | {instance.expiresAt ? ` - expires ${asLocalDate(instance.expiresAt)}` : ""} | 748 | <div className="flex items-center gap-1"> |
| 205 | </p> | 749 | <Button |
| 750 | variant="outline" | ||
| 751 | size="sm" | ||
| 752 | className="h-6 rounded-none px-2 text-[10px]" | ||
| 753 | onClick={() => setSqlShowInternalObjects((current) => !current)} | ||
| 754 | > | ||
| 755 | {sqlShowInternalObjects ? "Hide internal" : "Show internal"} | ||
| 756 | </Button> | ||
| 757 | <Button | ||
| 758 | variant="outline" | ||
| 759 | size="sm" | ||
| 760 | className="h-6 rounded-none px-2 text-[10px]" | ||
| 761 | onClick={() => void refreshSqlObjects()} | ||
| 762 | disabled={sqlObjectsLoading} | ||
| 763 | > | ||
| 764 | Refresh | ||
| 765 | </Button> | ||
| 766 | </div> | ||
| 206 | </div> | 767 | </div> |
| 207 | 768 | ||
| 208 | <div className="flex items-center gap-1"> | 769 | {sqlObjectsError ? <p className="px-2 py-1 text-[11px] text-destructive">{sqlObjectsError}</p> : null} |
| 770 | |||
| 771 | <div className="min-h-0 flex-1 overflow-y-auto p-1"> | ||
| 772 | {sqlObjectsLoading ? ( | ||
| 773 | <p className="px-2 py-1 text-[11px] text-muted-foreground">Loading...</p> | ||
| 774 | ) : visibleSqlObjects.length === 0 ? ( | ||
| 775 | <p className="px-2 py-1 text-[11px] text-muted-foreground">No tables</p> | ||
| 776 | ) : ( | ||
| 777 | <div className="space-y-1"> | ||
| 778 | {visibleSqlObjects.map((entry) => ( | ||
| 779 | <Button | ||
| 780 | key={entry.name} | ||
| 781 | variant={selectedSqlObjectName === entry.name ? "default" : "outline"} | ||
| 782 | size="sm" | ||
| 783 | className="h-7 w-full justify-start rounded-none px-2 text-[11px]" | ||
| 784 | onClick={() => void openSqlObject(entry.name)} | ||
| 785 | > | ||
| 786 | <span className="truncate">{entry.name}</span> | ||
| 787 | </Button> | ||
| 788 | ))} | ||
| 789 | </div> | ||
| 790 | )} | ||
| 791 | </div> | ||
| 792 | </div> | ||
| 793 | |||
| 794 | <div className="flex min-h-0 flex-col gap-2"> | ||
| 795 | <textarea | ||
| 796 | value={sqlText} | ||
| 797 | onChange={(event) => { | ||
| 798 | setSqlText(event.target.value); | ||
| 799 | setSelectedSqlObjectName(null); | ||
| 800 | }} | ||
| 801 | className="h-24 w-full resize-y rounded-none border border-border/60 bg-background px-2 py-1.5 font-mono text-xs text-foreground" | ||
| 802 | placeholder="SELECT * FROM sqlite_master" | ||
| 803 | /> | ||
| 804 | <div className="flex flex-wrap items-center gap-2"> | ||
| 805 | <Input | ||
| 806 | value={sqlMaxRows} | ||
| 807 | onChange={(event) => setSqlMaxRows(event.target.value)} | ||
| 808 | className="h-8 w-24 rounded-none text-xs" | ||
| 809 | placeholder="200" | ||
| 810 | /> | ||
| 209 | <Button | 811 | <Button |
| 210 | variant="outline" | ||
| 211 | size="sm" | 812 | size="sm" |
| 212 | className="h-7 rounded-none text-[11px]" | 813 | className="h-8 rounded-none text-xs" |
| 213 | disabled={busy} | 814 | disabled={sqlLoading || !canInspect} |
| 214 | onClick={() => void onClose(instance.id)} | 815 | onClick={() => { |
| 816 | setSelectedSqlObjectName(null); | ||
| 817 | void runSql(); | ||
| 818 | }} | ||
| 215 | > | 819 | > |
| 216 | <Power className="mr-1 h-3 w-3" /> | 820 | <Play className="mr-1 h-3 w-3" /> Run |
| 217 | Close | ||
| 218 | </Button> | 821 | </Button> |
| 219 | <Button | 822 | <Button variant="outline" size="sm" className="h-8 rounded-none px-2 text-[11px]" onClick={() => { setSelectedSqlObjectName(null); void runSql(USER_TABLES_QUERY); }}> |
| 220 | variant="outline" | 823 | User tables |
| 221 | size="sm" | 824 | </Button> |
| 222 | className="h-7 rounded-none text-[11px] border-destructive/40 text-destructive hover:bg-destructive/10" | 825 | <Button variant="outline" size="sm" className="h-8 rounded-none px-2 text-[11px]" onClick={() => { setSelectedSqlObjectName(null); void runSql(ALL_OBJECTS_QUERY); }}> |
| 223 | disabled={busy} | 826 | All objects |
| 224 | onClick={() => void onDelete(instance.id)} | 827 | </Button> |
| 225 | > | 828 | <Button variant="outline" size="sm" className="h-8 rounded-none px-2 text-[11px]" onClick={() => { setSelectedSqlObjectName(null); void runSql("PRAGMA table_info('kv_store')"); }}> |
| 226 | <Trash2 className="mr-1 h-3 w-3" /> | 829 | KV schema |
| 227 | Delete | 830 | </Button> |
| 831 | <Button variant="outline" size="sm" className="h-8 rounded-none px-2 text-[11px]" onClick={() => { setSelectedSqlObjectName("kv_store"); void runSql(KV_DATA_QUERY); }}> | ||
| 832 | KV data | ||
| 833 | </Button> | ||
| 834 | <Button variant="outline" size="sm" className="h-8 rounded-none px-2 text-[11px]" onClick={() => { setSelectedSqlObjectName("fs_dentry"); void runSql(FS_ENTRIES_QUERY); }}> | ||
| 835 | FS entries | ||
| 228 | </Button> | 836 | </Button> |
| 229 | </div> | 837 | </div> |
| 838 | <p className="text-[11px] text-muted-foreground"> | ||
| 839 | Tip: click a table on the left to load rows instantly (no manual SQL needed). | ||
| 840 | </p> | ||
| 841 | {sqlError ? <p className="text-[11px] text-destructive">{sqlError}</p> : null} | ||
| 842 | |||
| 843 | <div className="min-h-0 flex-1 border border-border/40"> | ||
| 844 | {sqlLoading ? ( | ||
| 845 | <div className="p-2 text-[11px] text-muted-foreground">Running query...</div> | ||
| 846 | ) : sqlResult ? ( | ||
| 847 | <div className="h-full min-h-0"> | ||
| 848 | <div className="flex items-center justify-between gap-2 border-b border-border/30 px-2 py-1 text-[11px] text-muted-foreground"> | ||
| 849 | <span> | ||
| 850 | rows: {sqlResult.rowCount} | ||
| 851 | {typeof sqlResult.changes === "number" ? ` - changes: ${sqlResult.changes}` : ""} | ||
| 852 | {sqlColumns.length > 0 ? ` - columns: ${sqlColumns.length}` : ""} | ||
| 853 | </span> | ||
| 854 | <div className="flex items-center gap-1"> | ||
| 855 | <Button | ||
| 856 | variant={sqlViewMode === "table" ? "default" : "outline"} | ||
| 857 | size="sm" | ||
| 858 | className="h-6 rounded-none px-2 text-[10px]" | ||
| 859 | onClick={() => setSqlViewMode("table")} | ||
| 860 | > | ||
| 861 | Table | ||
| 862 | </Button> | ||
| 863 | <Button | ||
| 864 | variant={sqlViewMode === "json" ? "default" : "outline"} | ||
| 865 | size="sm" | ||
| 866 | className="h-6 rounded-none px-2 text-[10px]" | ||
| 867 | onClick={() => setSqlViewMode("json")} | ||
| 868 | > | ||
| 869 | JSON | ||
| 870 | </Button> | ||
| 871 | </div> | ||
| 872 | </div> | ||
| 873 | |||
| 874 | {sqlViewMode === "json" ? ( | ||
| 875 | <pre className="h-[calc(100%-2rem)] overflow-auto p-2 text-[11px] leading-5">{previewJson(sqlRows)}</pre> | ||
| 876 | ) : sqlRows.length === 0 ? ( | ||
| 877 | <div className="p-2 text-[11px] text-muted-foreground">Query returned no rows.</div> | ||
| 878 | ) : ( | ||
| 879 | <div className="h-[calc(100%-2rem)] overflow-auto"> | ||
| 880 | <table className="min-w-full border-collapse text-[11px]"> | ||
| 881 | <thead className="sticky top-0 z-10 bg-muted/50"> | ||
| 882 | <tr> | ||
| 883 | <th className="border-b border-r border-border/40 px-2 py-1 text-left font-medium text-muted-foreground">#</th> | ||
| 884 | {sqlColumns.map((column) => ( | ||
| 885 | <th | ||
| 886 | key={column} | ||
| 887 | className="border-b border-r border-border/40 px-2 py-1 text-left font-medium text-muted-foreground last:border-r-0" | ||
| 888 | > | ||
| 889 | {column} | ||
| 890 | </th> | ||
| 891 | ))} | ||
| 892 | </tr> | ||
| 893 | </thead> | ||
| 894 | <tbody> | ||
| 895 | {sqlRows.map((row, rowIndex) => ( | ||
| 896 | <tr key={`row-${rowIndex}`} className="odd:bg-background even:bg-muted/10"> | ||
| 897 | <td className="border-b border-r border-border/30 px-2 py-1 align-top text-muted-foreground">{rowIndex + 1}</td> | ||
| 898 | {sqlColumns.map((column) => ( | ||
| 899 | <td | ||
| 900 | key={`row-${rowIndex}-${column}`} | ||
| 901 | className="max-w-[420px] border-b border-r border-border/30 px-2 py-1 align-top last:border-r-0" | ||
| 902 | > | ||
| 903 | <div className="max-h-28 overflow-auto whitespace-pre-wrap break-words"> | ||
| 904 | {truncateText(sqlCellText(row[column]), 2000)} | ||
| 905 | </div> | ||
| 906 | </td> | ||
| 907 | ))} | ||
| 908 | </tr> | ||
| 909 | ))} | ||
| 910 | </tbody> | ||
| 911 | </table> | ||
| 912 | </div> | ||
| 913 | )} | ||
| 914 | </div> | ||
| 915 | ) : ( | ||
| 916 | <div className="p-2 text-[11px] text-muted-foreground">Select a table from the left or run a query.</div> | ||
| 917 | )} | ||
| 918 | </div> | ||
| 230 | </div> | 919 | </div> |
| 231 | ); | 920 | </div> |
| 232 | })} | 921 | ) : null} |
| 233 | </div> | 922 | </> |
| 923 | ) : ( | ||
| 924 | <div className="p-4 text-[11px] text-muted-foreground">Select a storage instance to inspect.</div> | ||
| 234 | )} | 925 | )} |
| 235 | </div> | 926 | </div> |
| 236 | </section> | 927 | </section> |
| @@ -558,8 +558,10 @@ export function ToolsView() { · 557 unmodified lines | |||
| 558 | ) : null} | 558 | ) : null} |
| 559 | 559 | ||
| 560 | {activeTab === "storage" ? ( | 560 | {activeTab === "storage" ? ( |
| 561 | <div className="flex-1 min-h-0 overflow-y-auto p-4 md:p-6 lg:p-8"> | 561 | <div className="flex-1 min-h-0 overflow-hidden p-0"> |
| 562 | <StoragePanel | 562 | <StoragePanel |
| 563 | workspaceId={context?.workspaceId} | ||
| 564 | sessionId={context?.sessionId} | ||
| 563 | instances={storageItems} | 565 | instances={storageItems} |
| 564 | loading={storageLoading} | 566 | loading={storageLoading} |
| 565 | creating={storageCreateBusy} | 567 | creating={storageCreateBusy} |
| @@ -8,8 +8,95 @@ · 7 unmodified lines | |||
| 8 | * @module | 8 | * @module |
| 9 | */ | 9 | */ |
| 10 | 10 | ||
| 11 | import type { FunctionReference } from "convex/server"; | 11 | import type * as accounts from "../accounts.js"; |
| 12 | import type { GenericId as Id } from "convex/values"; | 12 | import type * as app from "../app.js"; |
| 13 | import type * as auth from "../auth.js"; | ||
| 14 | import type * as billing from "../billing.js"; | ||
| 15 | import type * as billingInternal from "../billingInternal.js"; | ||
| 16 | import type * as billingSync from "../billingSync.js"; | ||
| 17 | import type * as credentialsNode from "../credentialsNode.js"; | ||
| 18 | import type * as database from "../database.js"; | ||
| 19 | import type * as database_anonymous_session from "../database/anonymous_session.js"; | ||
| 20 | import type * as database_approvals from "../database/approvals.js"; | ||
| 21 | import type * as database_credentials from "../database/credentials.js"; | ||
| 22 | import type * as database_policies from "../database/policies.js"; | ||
| 23 | import type * as database_storage_instances from "../database/storage_instances.js"; | ||
| 24 | import type * as database_task_events from "../database/task_events.js"; | ||
| 25 | import type * as database_tasks from "../database/tasks.js"; | ||
| 26 | import type * as database_tool_calls from "../database/tool_calls.js"; | ||
| 27 | import type * as database_tool_sources from "../database/tool_sources.js"; | ||
| 28 | import type * as executor from "../executor.js"; | ||
| 29 | import type * as executorNode from "../executorNode.js"; | ||
| 30 | import type * as executorRuntimeNode from "../executorRuntimeNode.js"; | ||
| 31 | import type * as http from "../http.js"; | ||
| 32 | import type * as http_anonymous_auth from "../http/anonymous_auth.js"; | ||
| 33 | import type * as http_mcp_auth from "../http/mcp_auth.js"; | ||
| 34 | import type * as http_mcp_handler from "../http/mcp_handler.js"; | ||
| 35 | import type * as http_mcp_service from "../http/mcp_service.js"; | ||
| 36 | import type * as http_oauth_handlers from "../http/oauth_handlers.js"; | ||
| 37 | import type * as http_openapi_handler from "../http/openapi_handler.js"; | ||
| 38 | import type * as http_openapi_spec from "../http/openapi_spec.js"; | ||
| 39 | import type * as http_openapi_spec_registry from "../http/openapi_spec_registry.js"; | ||
| 40 | import type * as invites from "../invites.js"; | ||
| 41 | import type * as migrations from "../migrations.js"; | ||
| 42 | import type * as openApiSpecCache from "../openApiSpecCache.js"; | ||
| 43 | import type * as organizationMembers from "../organizationMembers.js"; | ||
| 44 | import type * as organizations from "../organizations.js"; | ||
| 45 | import type * as runtimeCallbacks from "../runtimeCallbacks.js"; | ||
| 46 | import type * as runtimeNode from "../runtimeNode.js"; | ||
| 47 | import type * as toolRegistry from "../toolRegistry.js"; | ||
| 48 | import type * as workspace from "../workspace.js"; | ||
| 49 | import type * as workspaceAuthInternal from "../workspaceAuthInternal.js"; | ||
| 50 | import type * as workspaces from "../workspaces.js"; | ||
| 51 | |||
| 52 | import type { | ||
| 53 | ApiFromModules, | ||
| 54 | FilterApi, | ||
| 55 | FunctionReference, | ||
| 56 | } from "convex/server"; | ||
| 57 | |||
| 58 | declare const fullApi: ApiFromModules<{ | ||
| 59 | accounts: typeof accounts; | ||
| 60 | app: typeof app; | ||
| 61 | auth: typeof auth; | ||
| 62 | billing: typeof billing; | ||
| 63 | billingInternal: typeof billingInternal; | ||
| 64 | billingSync: typeof billingSync; | ||
| 65 | credentialsNode: typeof credentialsNode; | ||
| 66 | database: typeof database; | ||
| 67 | "database/anonymous_session": typeof database_anonymous_session; | ||
| 68 | "database/approvals": typeof database_approvals; | ||
| 69 | "database/credentials": typeof database_credentials; | ||
| 70 | "database/policies": typeof database_policies; | ||
| 71 | "database/storage_instances": typeof database_storage_instances; | ||
| 72 | "database/task_events": typeof database_task_events; | ||
| 73 | "database/tasks": typeof database_tasks; | ||
| 74 | "database/tool_calls": typeof database_tool_calls; | ||
| 75 | "database/tool_sources": typeof database_tool_sources; | ||
| 76 | executor: typeof executor; | ||
| 77 | executorNode: typeof executorNode; | ||
| 78 | executorRuntimeNode: typeof executorRuntimeNode; | ||
| 79 | http: typeof http; | ||
| 80 | "http/anonymous_auth": typeof http_anonymous_auth; | ||
| 81 | "http/mcp_auth": typeof http_mcp_auth; | ||
| 82 | "http/mcp_handler": typeof http_mcp_handler; | ||
| 83 | "http/mcp_service": typeof http_mcp_service; | ||
| 84 | "http/oauth_handlers": typeof http_oauth_handlers; | ||
| 85 | "http/openapi_handler": typeof http_openapi_handler; | ||
| 86 | "http/openapi_spec": typeof http_openapi_spec; | ||
| 87 | "http/openapi_spec_registry": typeof http_openapi_spec_registry; | ||
| 88 | invites: typeof invites; | ||
| 89 | migrations: typeof migrations; | ||
| 90 | openApiSpecCache: typeof openApiSpecCache; | ||
| 91 | organizationMembers: typeof organizationMembers; | ||
| 92 | organizations: typeof organizations; | ||
| 93 | runtimeCallbacks: typeof runtimeCallbacks; | ||
| 94 | runtimeNode: typeof runtimeNode; | ||
| 95 | toolRegistry: typeof toolRegistry; | ||
| 96 | workspace: typeof workspace; | ||
| 97 | workspaceAuthInternal: typeof workspaceAuthInternal; | ||
| 98 | workspaces: typeof workspaces; | ||
| 99 | }>; | ||
| 13 | 100 | ||
| 14 | /** | 101 | /** |
| 15 | * A utility for referencing Convex functions in your app's public API. | 102 | * A utility for referencing Convex functions in your app's public API. |
| @@ -19,556 +106,10 @@ import type { GenericId as Id } from "convex/values"; · 3 unmodified lines | |||
| 19 | * const myFunctionReference = api.myModule.myFunction; | 106 | * const myFunctionReference = api.myModule.myFunction; |
| 20 | * ``` | 107 | * ``` |
| 21 | */ | 108 | */ |
| 22 | export declare const api: { | 109 | export declare const api: FilterApi< |
| 23 | accounts: { | 110 | typeof fullApi, |
| 24 | deleteCurrentAccount: FunctionReference< | 111 | FunctionReference<any, "public"> |
| 25 | "mutation", | 112 | >; |
| 26 | "public", | ||
| 27 | { sessionId?: string }, | ||
| 28 | any | ||
| 29 | >; | ||
| 30 | }; | ||
| 31 | app: { | ||
| 32 | getClientConfig: FunctionReference<"query", "public", {}, any>; | ||
| 33 | getCurrentAccount: FunctionReference< | ||
| 34 | "query", | ||
| 35 | "public", | ||
| 36 | { sessionId?: string }, | ||
| 37 | any | ||
| 38 | >; | ||
| 39 | }; | ||
| 40 | auth: { | ||
| 41 | bootstrapCurrentWorkosAccount: FunctionReference< | ||
| 42 | "mutation", | ||
| 43 | "public", | ||
| 44 | {}, | ||
| 45 | any | ||
| 46 | >; | ||
| 47 | }; | ||
| 48 | billing: { | ||
| 49 | createCustomerPortal: FunctionReference< | ||
| 50 | "action", | ||
| 51 | "public", | ||
| 52 | { | ||
| 53 | organizationId: Id<"organizations">; | ||
| 54 | returnUrl?: string; | ||
| 55 | sessionId?: string; | ||
| 56 | }, | ||
| 57 | { url: string } | ||
| 58 | >; | ||
| 59 | createSubscriptionCheckout: FunctionReference< | ||
| 60 | "action", | ||
| 61 | "public", | ||
| 62 | { | ||
| 63 | cancelUrl?: string; | ||
| 64 | organizationId: Id<"organizations">; | ||
| 65 | priceId: string; | ||
| 66 | sessionId?: string; | ||
| 67 | successUrl?: string; | ||
| 68 | }, | ||
| 69 | any | ||
| 70 | >; | ||
| 71 | getSummary: FunctionReference< | ||
| 72 | "query", | ||
| 73 | "public", | ||
| 74 | { organizationId: Id<"organizations">; sessionId?: string }, | ||
| 75 | any | ||
| 76 | >; | ||
| 77 | retrySeatSync: FunctionReference< | ||
| 78 | "mutation", | ||
| 79 | "public", | ||
| 80 | { organizationId: Id<"organizations">; sessionId?: string }, | ||
| 81 | any | ||
| 82 | >; | ||
| 83 | }; | ||
| 84 | credentialsNode: { | ||
| 85 | upsertCredential: FunctionReference< | ||
| 86 | "action", | ||
| 87 | "public", | ||
| 88 | { | ||
| 89 | accountId?: Id<"accounts">; | ||
| 90 | id?: string; | ||
| 91 | provider?: "local-convex" | "workos-vault"; | ||
| 92 | scopeType?: "account" | "organization" | "workspace"; | ||
| 93 | secretJson: Record<string, any>; | ||
| 94 | sessionId?: string; | ||
| 95 | sourceKey: string; | ||
| 96 | workspaceId: Id<"workspaces">; | ||
| 97 | }, | ||
| 98 | any | ||
| 99 | >; | ||
| 100 | }; | ||
| 101 | executor: { | ||
| 102 | createTask: FunctionReference< | ||
| 103 | "action", | ||
| 104 | "public", | ||
| 105 | { | ||
| 106 | accountId?: Id<"accounts">; | ||
| 107 | code: string; | ||
| 108 | metadata?: Record<string, any>; | ||
| 109 | runtimeId?: string; | ||
| 110 | sessionId?: string; | ||
| 111 | timeoutMs?: number; | ||
| 112 | waitForResult?: boolean; | ||
| 113 | workspaceId: Id<"workspaces">; | ||
| 114 | }, | ||
| 115 | any | ||
| 116 | >; | ||
| 117 | resolveApproval: FunctionReference< | ||
| 118 | "mutation", | ||
| 119 | "public", | ||
| 120 | { | ||
| 121 | approvalId: string; | ||
| 122 | decision: "approved" | "denied"; | ||
| 123 | reason?: string; | ||
| 124 | reviewerId?: string; | ||
| 125 | sessionId?: string; | ||
| 126 | workspaceId: Id<"workspaces">; | ||
| 127 | }, | ||
| 128 | any | ||
| 129 | >; | ||
| 130 | }; | ||
| 131 | executorNode: { | ||
| 132 | listToolsWithWarnings: FunctionReference< | ||
| 133 | "action", | ||
| 134 | "public", | ||
| 135 | { | ||
| 136 | accountId?: Id<"accounts">; | ||
| 137 | buildId?: string; | ||
| 138 | cursor?: string; | ||
| 139 | fetchAll?: boolean; | ||
| 140 | includeDetails?: boolean; | ||
| 141 | includeSourceMeta?: boolean; | ||
| 142 | limit?: number; | ||
| 143 | rebuildInventory?: boolean; | ||
| 144 | sessionId?: string; | ||
| 145 | source?: string; | ||
| 146 | sourceName?: string; | ||
| 147 | toolPaths?: Array<string>; | ||
| 148 | workspaceId: Id<"workspaces">; | ||
| 149 | }, | ||
| 150 | any | ||
| 151 | >; | ||
| 152 | previewOpenApiSourceUpgrade: FunctionReference< | ||
| 153 | "action", | ||
| 154 | "public", | ||
| 155 | { | ||
| 156 | accountId?: Id<"accounts">; | ||
| 157 | config: Record<string, any>; | ||
| 158 | name: string; | ||
| 159 | sessionId?: string; | ||
| 160 | sourceId: string; | ||
| 161 | workspaceId: Id<"workspaces">; | ||
| 162 | }, | ||
| 163 | any | ||
| 164 | >; | ||
| 165 | }; | ||
| 166 | invites: { | ||
| 167 | create: FunctionReference< | ||
| 168 | "mutation", | ||
| 169 | "public", | ||
| 170 | { | ||
| 171 | email: string; | ||
| 172 | expiresInDays?: number; | ||
| 173 | organizationId: Id<"organizations">; | ||
| 174 | role: "owner" | "admin" | "member" | "billing_admin"; | ||
| 175 | sessionId?: string; | ||
| 176 | }, | ||
| 177 | any | ||
| 178 | >; | ||
| 179 | list: FunctionReference< | ||
| 180 | "query", | ||
| 181 | "public", | ||
| 182 | { organizationId: Id<"organizations">; sessionId?: string }, | ||
| 183 | any | ||
| 184 | >; | ||
| 185 | revoke: FunctionReference< | ||
| 186 | "mutation", | ||
| 187 | "public", | ||
| 188 | { | ||
| 189 | inviteId: Id<"invites">; | ||
| 190 | organizationId: Id<"organizations">; | ||
| 191 | sessionId?: string; | ||
| 192 | }, | ||
| 193 | any | ||
| 194 | >; | ||
| 195 | }; | ||
| 196 | organizationMembers: { | ||
| 197 | list: FunctionReference< | ||
| 198 | "query", | ||
| 199 | "public", | ||
| 200 | { organizationId: Id<"organizations">; sessionId?: string }, | ||
| 201 | any | ||
| 202 | >; | ||
| 203 | remove: FunctionReference< | ||
| 204 | "mutation", | ||
| 205 | "public", | ||
| 206 | { | ||
| 207 | accountId: Id<"accounts">; | ||
| 208 | organizationId: Id<"organizations">; | ||
| 209 | sessionId?: string; | ||
| 210 | }, | ||
| 211 | any | ||
| 212 | >; | ||
| 213 | updateBillable: FunctionReference< | ||
| 214 | "mutation", | ||
| 215 | "public", | ||
| 216 | { | ||
| 217 | accountId: Id<"accounts">; | ||
| 218 | billable: boolean; | ||
| 219 | organizationId: Id<"organizations">; | ||
| 220 | sessionId?: string; | ||
| 221 | }, | ||
| 222 | any | ||
| 223 | >; | ||
| 224 | updateRole: FunctionReference< | ||
| 225 | "mutation", | ||
| 226 | "public", | ||
| 227 | { | ||
| 228 | accountId: Id<"accounts">; | ||
| 229 | organizationId: Id<"organizations">; | ||
| 230 | role: "owner" | "admin" | "member" | "billing_admin"; | ||
| 231 | sessionId?: string; | ||
| 232 | }, | ||
| 233 | any | ||
| 234 | >; | ||
| 235 | }; | ||
| 236 | organizations: { | ||
| 237 | create: FunctionReference< | ||
| 238 | "mutation", | ||
| 239 | "public", | ||
| 240 | { name: string; sessionId?: string }, | ||
| 241 | any | ||
| 242 | >; | ||
| 243 | getNavigationState: FunctionReference< | ||
| 244 | "query", | ||
| 245 | "public", | ||
| 246 | { sessionId?: string }, | ||
| 247 | any | ||
| 248 | >; | ||
| 249 | getOrganizationAccess: FunctionReference< | ||
| 250 | "query", | ||
| 251 | "public", | ||
| 252 | { organizationId: Id<"organizations">; sessionId?: string }, | ||
| 253 | any | ||
| 254 | >; | ||
| 255 | listMine: FunctionReference<"query", "public", { sessionId?: string }, any>; | ||
| 256 | resolveWorkosOrganizationId: FunctionReference< | ||
| 257 | "query", | ||
| 258 | "public", | ||
| 259 | { organizationId: Id<"organizations">; sessionId?: string }, | ||
| 260 | any | ||
| 261 | >; | ||
| 262 | }; | ||
| 263 | runtimeCallbacks: { | ||
| 264 | completeRun: FunctionReference< | ||
| 265 | "mutation", | ||
| 266 | "public", | ||
| 267 | { | ||
| 268 | durationMs?: number; | ||
| 269 | error?: string; | ||
| 270 | exitCode?: number; | ||
| 271 | internalSecret: string; | ||
| 272 | runId: string; | ||
| 273 | status: "completed" | "failed" | "timed_out" | "denied"; | ||
| 274 | }, | ||
| 275 | any | ||
| 276 | >; | ||
| 277 | getApprovalStatus: FunctionReference< | ||
| 278 | "query", | ||
| 279 | "public", | ||
| 280 | { approvalId: string; internalSecret: string; runId: string }, | ||
| 281 | any | ||
| 282 | >; | ||
| 283 | getTaskWatchStatus: FunctionReference< | ||
| 284 | "query", | ||
| 285 | "public", | ||
| 286 | { internalSecret: string; runId: string; workspaceId: Id<"workspaces"> }, | ||
| 287 | any | ||
| 288 | >; | ||
| 289 | handleToolCall: FunctionReference< | ||
| 290 | "action", | ||
| 291 | "public", | ||
| 292 | { | ||
| 293 | callId: string; | ||
| 294 | input?: Record<string, any>; | ||
| 295 | internalSecret: string; | ||
| 296 | runId: string; | ||
| 297 | toolPath: string; | ||
| 298 | }, | ||
| 299 | any | ||
| 300 | >; | ||
| 301 | }; | ||
| 302 | workspace: { | ||
| 303 | bootstrapAnonymousSession: FunctionReference< | ||
| 304 | "mutation", | ||
| 305 | "public", | ||
| 306 | { accountId?: string; sessionId?: string }, | ||
| 307 | any | ||
| 308 | >; | ||
| 309 | closeStorageInstance: FunctionReference< | ||
| 310 | "mutation", | ||
| 311 | "public", | ||
| 312 | { instanceId: string; sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 313 | any | ||
| 314 | >; | ||
| 315 | deleteStorageInstance: FunctionReference< | ||
| 316 | "mutation", | ||
| 317 | "public", | ||
| 318 | { instanceId: string; sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 319 | any | ||
| 320 | >; | ||
| 321 | deleteToolPolicyAssignment: FunctionReference< | ||
| 322 | "mutation", | ||
| 323 | "public", | ||
| 324 | { bindingId: string; sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 325 | any | ||
| 326 | >; | ||
| 327 | deleteToolPolicyRule: FunctionReference< | ||
| 328 | "mutation", | ||
| 329 | "public", | ||
| 330 | { | ||
| 331 | roleId: string; | ||
| 332 | ruleId: string; | ||
| 333 | sessionId?: string; | ||
| 334 | workspaceId: Id<"workspaces">; | ||
| 335 | }, | ||
| 336 | any | ||
| 337 | >; | ||
| 338 | deleteToolPolicySet: FunctionReference< | ||
| 339 | "mutation", | ||
| 340 | "public", | ||
| 341 | { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 342 | any | ||
| 343 | >; | ||
| 344 | deleteToolSource: FunctionReference< | ||
| 345 | "mutation", | ||
| 346 | "public", | ||
| 347 | { sessionId?: string; sourceId: string; workspaceId: Id<"workspaces"> }, | ||
| 348 | any | ||
| 349 | >; | ||
| 350 | getMcpApiKey: FunctionReference< | ||
| 351 | "query", | ||
| 352 | "public", | ||
| 353 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 354 | any | ||
| 355 | >; | ||
| 356 | getToolDetails: FunctionReference< | ||
| 357 | "mutation", | ||
| 358 | "public", | ||
| 359 | { | ||
| 360 | sessionId?: string; | ||
| 361 | toolPaths: Array<string>; | ||
| 362 | workspaceId: Id<"workspaces">; | ||
| 363 | }, | ||
| 364 | any | ||
| 365 | >; | ||
| 366 | getToolInventoryProgress: FunctionReference< | ||
| 367 | "query", | ||
| 368 | "public", | ||
| 369 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 370 | any | ||
| 371 | >; | ||
| 372 | listCredentials: FunctionReference< | ||
| 373 | "query", | ||
| 374 | "public", | ||
| 375 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 376 | any | ||
| 377 | >; | ||
| 378 | listPendingApprovals: FunctionReference< | ||
| 379 | "query", | ||
| 380 | "public", | ||
| 381 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 382 | any | ||
| 383 | >; | ||
| 384 | listStorageInstances: FunctionReference< | ||
| 385 | "query", | ||
| 386 | "public", | ||
| 387 | { | ||
| 388 | includeDeleted?: boolean; | ||
| 389 | scopeType?: "scratch" | "account" | "workspace" | "organization"; | ||
| 390 | sessionId?: string; | ||
| 391 | workspaceId: Id<"workspaces">; | ||
| 392 | }, | ||
| 393 | any | ||
| 394 | >; | ||
| 395 | listTasks: FunctionReference< | ||
| 396 | "query", | ||
| 397 | "public", | ||
| 398 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 399 | any | ||
| 400 | >; | ||
| 401 | listToolPolicies: FunctionReference< | ||
| 402 | "query", | ||
| 403 | "public", | ||
| 404 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 405 | any | ||
| 406 | >; | ||
| 407 | listToolPolicyAssignments: FunctionReference< | ||
| 408 | "query", | ||
| 409 | "public", | ||
| 410 | { roleId?: string; sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 411 | any | ||
| 412 | >; | ||
| 413 | listToolPolicyRules: FunctionReference< | ||
| 414 | "query", | ||
| 415 | "public", | ||
| 416 | { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 417 | any | ||
| 418 | >; | ||
| 419 | listToolPolicySets: FunctionReference< | ||
| 420 | "query", | ||
| 421 | "public", | ||
| 422 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 423 | any | ||
| 424 | >; | ||
| 425 | listToolSources: FunctionReference< | ||
| 426 | "query", | ||
| 427 | "public", | ||
| 428 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 429 | any | ||
| 430 | >; | ||
| 431 | openStorageInstance: FunctionReference< | ||
| 432 | "mutation", | ||
| 433 | "public", | ||
| 434 | { | ||
| 435 | durability?: "ephemeral" | "durable"; | ||
| 436 | instanceId?: string; | ||
| 437 | purpose?: string; | ||
| 438 | scopeType?: "scratch" | "account" | "workspace" | "organization"; | ||
| 439 | sessionId?: string; | ||
| 440 | ttlHours?: number; | ||
| 441 | workspaceId: Id<"workspaces">; | ||
| 442 | }, | ||
| 443 | any | ||
| 444 | >; | ||
| 445 | regenerateToolInventory: FunctionReference< | ||
| 446 | "mutation", | ||
| 447 | "public", | ||
| 448 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 449 | any | ||
| 450 | >; | ||
| 451 | resolveCredential: FunctionReference< | ||
| 452 | "query", | ||
| 453 | "public", | ||
| 454 | { | ||
| 455 | accountId?: Id<"accounts">; | ||
| 456 | scopeType: "account" | "organization" | "workspace"; | ||
| 457 | sessionId?: string; | ||
| 458 | sourceKey: string; | ||
| 459 | workspaceId: Id<"workspaces">; | ||
| 460 | }, | ||
| 461 | any | ||
| 462 | >; | ||
| 463 | upsertCredential: FunctionReference< | ||
| 464 | "mutation", | ||
| 465 | "public", | ||
| 466 | { | ||
| 467 | accountId?: Id<"accounts">; | ||
| 468 | id?: string; | ||
| 469 | overridesJson?: Record<string, any>; | ||
| 470 | provider?: "local-convex" | "workos-vault"; | ||
| 471 | scopeType?: "account" | "organization" | "workspace"; | ||
| 472 | secretJson: Record<string, any>; | ||
| 473 | sessionId?: string; | ||
| 474 | sourceKey: string; | ||
| 475 | workspaceId: Id<"workspaces">; | ||
| 476 | }, | ||
| 477 | any | ||
| 478 | >; | ||
| 479 | upsertToolPolicyAssignment: FunctionReference< | ||
| 480 | "mutation", | ||
| 481 | "public", | ||
| 482 | { | ||
| 483 | clientId?: string; | ||
| 484 | expiresAt?: number; | ||
| 485 | id?: string; | ||
| 486 | roleId: string; | ||
| 487 | scopeType?: "account" | "organization" | "workspace"; | ||
| 488 | sessionId?: string; | ||
| 489 | status?: "active" | "disabled"; | ||
| 490 | targetAccountId?: Id<"accounts">; | ||
| 491 | workspaceId: Id<"workspaces">; | ||
| 492 | }, | ||
| 493 | any | ||
| 494 | >; | ||
| 495 | upsertToolPolicyRule: FunctionReference< | ||
| 496 | "mutation", | ||
| 497 | "public", | ||
| 498 | { | ||
| 499 | approvalMode?: "inherit" | "auto" | "required"; | ||
| 500 | argumentConditions?: Array<{ | ||
| 501 | key: string; | ||
| 502 | operator: "equals" | "contains" | "starts_with" | "not_equals"; | ||
| 503 | value: string; | ||
| 504 | }>; | ||
| 505 | effect?: "allow" | "deny"; | ||
| 506 | id?: string; | ||
| 507 | matchType?: "glob" | "exact"; | ||
| 508 | priority?: number; | ||
| 509 | resourcePattern?: string; | ||
| 510 | roleId: string; | ||
| 511 | selectorType: "all" | "source" | "namespace" | "tool_path"; | ||
| 512 | sessionId?: string; | ||
| 513 | sourceKey?: string; | ||
| 514 | workspaceId: Id<"workspaces">; | ||
| 515 | }, | ||
| 516 | any | ||
| 517 | >; | ||
| 518 | upsertToolPolicySet: FunctionReference< | ||
| 519 | "mutation", | ||
| 520 | "public", | ||
| 521 | { | ||
| 522 | description?: string; | ||
| 523 | id?: string; | ||
| 524 | name: string; | ||
| 525 | sessionId?: string; | ||
| 526 | workspaceId: Id<"workspaces">; | ||
| 527 | }, | ||
| 528 | any | ||
| 529 | >; | ||
| 530 | upsertToolSource: FunctionReference< | ||
| 531 | "mutation", | ||
| 532 | "public", | ||
| 533 | { | ||
| 534 | config: Record<string, any>; | ||
| 535 | enabled?: boolean; | ||
| 536 | id?: string; | ||
| 537 | name: string; | ||
| 538 | scopeType?: "organization" | "workspace"; | ||
| 539 | sessionId?: string; | ||
| 540 | type: "mcp" | "openapi" | "graphql"; | ||
| 541 | workspaceId: Id<"workspaces">; | ||
| 542 | }, | ||
| 543 | any | ||
| 544 | >; | ||
| 545 | }; | ||
| 546 | workspaces: { | ||
| 547 | create: FunctionReference< | ||
| 548 | "mutation", | ||
| 549 | "public", | ||
| 550 | { | ||
| 551 | iconStorageId?: Id<"_storage">; | ||
| 552 | name: string; | ||
| 553 | organizationId?: Id<"organizations">; | ||
| 554 | sessionId?: string; | ||
| 555 | }, | ||
| 556 | any | ||
| 557 | >; | ||
| 558 | generateWorkspaceIconUploadUrl: FunctionReference< | ||
| 559 | "mutation", | ||
| 560 | "public", | ||
| 561 | { sessionId?: string }, | ||
| 562 | any | ||
| 563 | >; | ||
| 564 | list: FunctionReference< | ||
| 565 | "query", | ||
| 566 | "public", | ||
| 567 | { organizationId?: Id<"organizations">; sessionId?: string }, | ||
| 568 | any | ||
| 569 | >; | ||
| 570 | }; | ||
| 571 | }; | ||
| 572 | 113 | ||
| 573 | /** | 114 | /** |
| 574 | * A utility for referencing Convex functions in your app's internal API. | 115 | * A utility for referencing Convex functions in your app's internal API. |
| @@ -578,1291 +119,10 @@ export declare const api: { · 3 unmodified lines | |||
| 578 | * const myFunctionReference = internal.myModule.myFunction; | 119 | * const myFunctionReference = internal.myModule.myFunction; |
| 579 | * ``` | 120 | * ``` |
| 580 | */ | 121 | */ |
| 581 | export declare const internal: { | 122 | export declare const internal: FilterApi< |
| 582 | auth: { | 123 | typeof fullApi, |
| 583 | authKitEvent: FunctionReference< | 124 | FunctionReference<any, "internal"> |
| 584 | "mutation", | 125 | >; |
| 585 | "internal", | ||
| 586 | { data: Record<string, any>; event: string }, | ||
| 587 | null | ||
| 588 | >; | ||
| 589 | }; | ||
| 590 | billingInternal: { | ||
| 591 | bumpSeatSyncVersion: FunctionReference< | ||
| 592 | "mutation", | ||
| 593 | "internal", | ||
| 594 | { organizationId: Id<"organizations"> }, | ||
| 595 | any | ||
| 596 | >; | ||
| 597 | getBillingAccessForRequest: FunctionReference< | ||
| 598 | "query", | ||
| 599 | "internal", | ||
| 600 | { organizationId: Id<"organizations">; sessionId?: string }, | ||
| 601 | any | ||
| 602 | >; | ||
| 603 | getSeatSyncSnapshot: FunctionReference< | ||
| 604 | "query", | ||
| 605 | "internal", | ||
| 606 | { organizationId: Id<"organizations"> }, | ||
| 607 | any | ||
| 608 | >; | ||
| 609 | upsertCustomerLink: FunctionReference< | ||
| 610 | "mutation", | ||
| 611 | "internal", | ||
| 612 | { organizationId: Id<"organizations">; stripeCustomerId: string }, | ||
| 613 | any | ||
| 614 | >; | ||
| 615 | upsertSeatState: FunctionReference< | ||
| 616 | "mutation", | ||
| 617 | "internal", | ||
| 618 | { | ||
| 619 | bumpVersion: boolean; | ||
| 620 | desiredSeats: number; | ||
| 621 | lastAppliedSeats: number | null; | ||
| 622 | organizationId: Id<"organizations">; | ||
| 623 | syncError: string | null; | ||
| 624 | }, | ||
| 625 | any | ||
| 626 | >; | ||
| 627 | }; | ||
| 628 | billingSync: { | ||
| 629 | syncSeatQuantity: FunctionReference< | ||
| 630 | "action", | ||
| 631 | "internal", | ||
| 632 | { expectedVersion: number; organizationId: Id<"organizations"> }, | ||
| 633 | any | ||
| 634 | >; | ||
| 635 | }; | ||
| 636 | credentialsNode: { | ||
| 637 | readVaultObject: FunctionReference< | ||
| 638 | "action", | ||
| 639 | "internal", | ||
| 640 | { apiKey?: string; objectId: string }, | ||
| 641 | any | ||
| 642 | >; | ||
| 643 | }; | ||
| 644 | database: { | ||
| 645 | anonymous_session: { | ||
| 646 | bootstrapAnonymousSession: FunctionReference< | ||
| 647 | "mutation", | ||
| 648 | "internal", | ||
| 649 | { accountId?: string; clientId?: string; sessionId?: string }, | ||
| 650 | any | ||
| 651 | >; | ||
| 652 | }; | ||
| 653 | approvals: { | ||
| 654 | createApproval: FunctionReference< | ||
| 655 | "mutation", | ||
| 656 | "internal", | ||
| 657 | { | ||
| 658 | id: string; | ||
| 659 | input?: Record<string, any>; | ||
| 660 | taskId: string; | ||
| 661 | toolPath: string; | ||
| 662 | }, | ||
| 663 | any | ||
| 664 | >; | ||
| 665 | getApproval: FunctionReference< | ||
| 666 | "query", | ||
| 667 | "internal", | ||
| 668 | { approvalId: string }, | ||
| 669 | any | ||
| 670 | >; | ||
| 671 | getApprovalInWorkspace: FunctionReference< | ||
| 672 | "query", | ||
| 673 | "internal", | ||
| 674 | { approvalId: string; workspaceId: Id<"workspaces"> }, | ||
| 675 | any | ||
| 676 | >; | ||
| 677 | listApprovals: FunctionReference< | ||
| 678 | "query", | ||
| 679 | "internal", | ||
| 680 | { | ||
| 681 | status?: "pending" | "approved" | "denied"; | ||
| 682 | workspaceId: Id<"workspaces">; | ||
| 683 | }, | ||
| 684 | any | ||
| 685 | >; | ||
| 686 | listPendingApprovals: FunctionReference< | ||
| 687 | "query", | ||
| 688 | "internal", | ||
| 689 | { workspaceId: Id<"workspaces"> }, | ||
| 690 | any | ||
| 691 | >; | ||
| 692 | resolveApproval: FunctionReference< | ||
| 693 | "mutation", | ||
| 694 | "internal", | ||
| 695 | { | ||
| 696 | approvalId: string; | ||
| 697 | decision: "approved" | "denied"; | ||
| 698 | reason?: string; | ||
| 699 | reviewerId?: string; | ||
| 700 | }, | ||
| 701 | any | ||
| 702 | >; | ||
| 703 | }; | ||
| 704 | bootstrapAnonymousSession: FunctionReference< | ||
| 705 | "mutation", | ||
| 706 | "internal", | ||
| 707 | { accountId?: string; clientId?: string; sessionId?: string }, | ||
| 708 | any | ||
| 709 | >; | ||
| 710 | closeStorageInstance: FunctionReference< | ||
| 711 | "mutation", | ||
| 712 | "internal", | ||
| 713 | { | ||
| 714 | accountId?: Id<"accounts">; | ||
| 715 | instanceId: string; | ||
| 716 | workspaceId: Id<"workspaces">; | ||
| 717 | }, | ||
| 718 | any | ||
| 719 | >; | ||
| 720 | createApproval: FunctionReference< | ||
| 721 | "mutation", | ||
| 722 | "internal", | ||
| 723 | { | ||
| 724 | id: string; | ||
| 725 | input?: Record<string, any>; | ||
| 726 | taskId: string; | ||
| 727 | toolPath: string; | ||
| 728 | }, | ||
| 729 | any | ||
| 730 | >; | ||
| 731 | createTask: FunctionReference< | ||
| 732 | "mutation", | ||
| 733 | "internal", | ||
| 734 | { | ||
| 735 | accountId?: Id<"accounts">; | ||
| 736 | clientId?: string; | ||
| 737 | code: string; | ||
| 738 | id: string; | ||
| 739 | metadata?: Record<string, any>; | ||
| 740 | runtimeId: string; | ||
| 741 | timeoutMs?: number; | ||
| 742 | workspaceId: Id<"workspaces">; | ||
| 743 | }, | ||
| 744 | any | ||
| 745 | >; | ||
| 746 | createTaskEvent: FunctionReference< | ||
| 747 | "mutation", | ||
| 748 | "internal", | ||
| 749 | { | ||
| 750 | eventName: string; | ||
| 751 | payload: Record<string, any>; | ||
| 752 | taskId: string; | ||
| 753 | type: string; | ||
| 754 | }, | ||
| 755 | any | ||
| 756 | >; | ||
| 757 | credentials: { | ||
| 758 | listCredentialProviders: FunctionReference<"query", "internal", {}, any>; | ||
| 759 | listCredentials: FunctionReference< | ||
| 760 | "query", | ||
| 761 | "internal", | ||
| 762 | { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> }, | ||
| 763 | any | ||
| 764 | >; | ||
| 765 | resolveCredential: FunctionReference< | ||
| 766 | "query", | ||
| 767 | "internal", | ||
| 768 | { | ||
| 769 | accountId?: Id<"accounts">; | ||
| 770 | scopeType: "account" | "organization" | "workspace"; | ||
| 771 | sourceKey: string; | ||
| 772 | workspaceId: Id<"workspaces">; | ||
| 773 | }, | ||
| 774 | any | ||
| 775 | >; | ||
| 776 | upsertCredential: FunctionReference< | ||
| 777 | "mutation", | ||
| 778 | "internal", | ||
| 779 | { | ||
| 780 | accountId?: Id<"accounts">; | ||
| 781 | id?: string; | ||
| 782 | overridesJson?: Record<string, any>; | ||
| 783 | provider?: "local-convex" | "workos-vault"; | ||
| 784 | scopeType?: "account" | "organization" | "workspace"; | ||
| 785 | secretJson: Record<string, any>; | ||
| 786 | sourceKey: string; | ||
| 787 | workspaceId: Id<"workspaces">; | ||
| 788 | }, | ||
| 789 | any | ||
| 790 | >; | ||
| 791 | }; | ||
| 792 | deleteStorageInstance: FunctionReference< | ||
| 793 | "mutation", | ||
| 794 | "internal", | ||
| 795 | { | ||
| 796 | accountId?: Id<"accounts">; | ||
| 797 | instanceId: string; | ||
| 798 | workspaceId: Id<"workspaces">; | ||
| 799 | }, | ||
| 800 | any | ||
| 801 | >; | ||
| 802 | deleteToolPolicyAssignment: FunctionReference< | ||
| 803 | "mutation", | ||
| 804 | "internal", | ||
| 805 | { bindingId: string; workspaceId: Id<"workspaces"> }, | ||
| 806 | any | ||
| 807 | >; | ||
| 808 | deleteToolPolicyRule: FunctionReference< | ||
| 809 | "mutation", | ||
| 810 | "internal", | ||
| 811 | { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> }, | ||
| 812 | any | ||
| 813 | >; | ||
| 814 | deleteToolPolicySet: FunctionReference< | ||
| 815 | "mutation", | ||
| 816 | "internal", | ||
| 817 | { roleId: string; workspaceId: Id<"workspaces"> }, | ||
| 818 | any | ||
| 819 | >; | ||
| 820 | deleteToolSource: FunctionReference< | ||
| 821 | "mutation", | ||
| 822 | "internal", | ||
| 823 | { sourceId: string; workspaceId: Id<"workspaces"> }, | ||
| 824 | any | ||
| 825 | >; | ||
| 826 | finishToolCall: FunctionReference< | ||
| 827 | "mutation", | ||
| 828 | "internal", | ||
| 829 | { | ||
| 830 | callId: string; | ||
| 831 | error?: string; | ||
| 832 | status: "completed" | "failed" | "denied"; | ||
| 833 | taskId: string; | ||
| 834 | }, | ||
| 835 | any | ||
| 836 | >; | ||
| 837 | getApproval: FunctionReference< | ||
| 838 | "query", | ||
| 839 | "internal", | ||
| 840 | { approvalId: string }, | ||
| 841 | any | ||
| 842 | >; | ||
| 843 | getApprovalInWorkspace: FunctionReference< | ||
| 844 | "query", | ||
| 845 | "internal", | ||
| 846 | { approvalId: string; workspaceId: Id<"workspaces"> }, | ||
| 847 | any | ||
| 848 | >; | ||
| 849 | getStorageInstance: FunctionReference< | ||
| 850 | "query", | ||
| 851 | "internal", | ||
| 852 | { | ||
| 853 | accountId?: Id<"accounts">; | ||
| 854 | instanceId: string; | ||
| 855 | workspaceId: Id<"workspaces">; | ||
| 856 | }, | ||
| 857 | any | ||
| 858 | >; | ||
| 859 | getTask: FunctionReference<"query", "internal", { taskId: string }, any>; | ||
| 860 | getTaskInWorkspace: FunctionReference< | ||
| 861 | "query", | ||
| 862 | "internal", | ||
| 863 | { taskId: string; workspaceId: Id<"workspaces"> }, | ||
| 864 | any | ||
| 865 | >; | ||
| 866 | getToolCall: FunctionReference< | ||
| 867 | "query", | ||
| 868 | "internal", | ||
| 869 | { callId: string; taskId: string }, | ||
| 870 | any | ||
| 871 | >; | ||
| 872 | listApprovals: FunctionReference< | ||
| 873 | "query", | ||
| 874 | "internal", | ||
| 875 | { | ||
| 876 | status?: "pending" | "approved" | "denied"; | ||
| 877 | workspaceId: Id<"workspaces">; | ||
| 878 | }, | ||
| 879 | any | ||
| 880 | >; | ||
| 881 | listCredentialProviders: FunctionReference<"query", "internal", {}, any>; | ||
| 882 | listCredentials: FunctionReference< | ||
| 883 | "query", | ||
| 884 | "internal", | ||
| 885 | { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> }, | ||
| 886 | any | ||
| 887 | >; | ||
| 888 | listPendingApprovals: FunctionReference< | ||
| 889 | "query", | ||
| 890 | "internal", | ||
| 891 | { workspaceId: Id<"workspaces"> }, | ||
| 892 | any | ||
| 893 | >; | ||
| 894 | listQueuedTaskIds: FunctionReference< | ||
| 895 | "query", | ||
| 896 | "internal", | ||
| 897 | { limit?: number }, | ||
| 898 | any | ||
| 899 | >; | ||
| 900 | listRuntimeTargets: FunctionReference<"query", "internal", {}, any>; | ||
| 901 | listStorageInstances: FunctionReference< | ||
| 902 | "query", | ||
| 903 | "internal", | ||
| 904 | { | ||
| 905 | accountId?: Id<"accounts">; | ||
| 906 | includeDeleted?: boolean; | ||
| 907 | scopeType?: "scratch" | "account" | "workspace" | "organization"; | ||
| 908 | workspaceId: Id<"workspaces">; | ||
| 909 | }, | ||
| 910 | any | ||
| 911 | >; | ||
| 912 | listTaskEvents: FunctionReference< | ||
| 913 | "query", | ||
| 914 | "internal", | ||
| 915 | { taskId: string }, | ||
| 916 | any | ||
| 917 | >; | ||
| 918 | listTasks: FunctionReference< | ||
| 919 | "query", | ||
| 920 | "internal", | ||
| 921 | { workspaceId: Id<"workspaces"> }, | ||
| 922 | any | ||
| 923 | >; | ||
| 924 | listToolCalls: FunctionReference< | ||
| 925 | "query", | ||
| 926 | "internal", | ||
| 927 | { taskId: string }, | ||
| 928 | any | ||
| 929 | >; | ||
| 930 | listToolPolicies: FunctionReference< | ||
| 931 | "query", | ||
| 932 | "internal", | ||
| 933 | { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> }, | ||
| 934 | any | ||
| 935 | >; | ||
| 936 | listToolPolicyAssignments: FunctionReference< | ||
| 937 | "query", | ||
| 938 | "internal", | ||
| 939 | { roleId?: string; workspaceId: Id<"workspaces"> }, | ||
| 940 | any | ||
| 941 | >; | ||
| 942 | listToolPolicyRules: FunctionReference< | ||
| 943 | "query", | ||
| 944 | "internal", | ||
| 945 | { roleId: string; workspaceId: Id<"workspaces"> }, | ||
| 946 | any | ||
| 947 | >; | ||
| 948 | listToolPolicySets: FunctionReference< | ||
| 949 | "query", | ||
| 950 | "internal", | ||
| 951 | { workspaceId: Id<"workspaces"> }, | ||
| 952 | any | ||
| 953 | >; | ||
| 954 | listToolSources: FunctionReference< | ||
| 955 | "query", | ||
| 956 | "internal", | ||
| 957 | { workspaceId: Id<"workspaces"> }, | ||
| 958 | any | ||
| 959 | >; | ||
| 960 | markTaskFinished: FunctionReference< | ||
| 961 | "mutation", | ||
| 962 | "internal", | ||
| 963 | { | ||
| 964 | error?: string; | ||
| 965 | exitCode?: number; | ||
| 966 | status: "completed" | "failed" | "timed_out" | "denied"; | ||
| 967 | taskId: string; | ||
| 968 | }, | ||
| 969 | any | ||
| 970 | >; | ||
| 971 | markTaskRunning: FunctionReference< | ||
| 972 | "mutation", | ||
| 973 | "internal", | ||
| 974 | { taskId: string }, | ||
| 975 | any | ||
| 976 | >; | ||
| 977 | openStorageInstance: FunctionReference< | ||
| 978 | "mutation", | ||
| 979 | "internal", | ||
| 980 | { | ||
| 981 | accountId?: Id<"accounts">; | ||
| 982 | durability?: "ephemeral" | "durable"; | ||
| 983 | instanceId?: string; | ||
| 984 | purpose?: string; | ||
| 985 | scopeType?: "scratch" | "account" | "workspace" | "organization"; | ||
| 986 | ttlHours?: number; | ||
| 987 | workspaceId: Id<"workspaces">; | ||
| 988 | }, | ||
| 989 | any | ||
| 990 | >; | ||
| 991 | policies: { | ||
| 992 | deleteToolPolicyAssignment: FunctionReference< | ||
| 993 | "mutation", | ||
| 994 | "internal", | ||
| 995 | { bindingId: string; workspaceId: Id<"workspaces"> }, | ||
| 996 | any | ||
| 997 | >; | ||
| 998 | deleteToolPolicyRule: FunctionReference< | ||
| 999 | "mutation", | ||
| 1000 | "internal", | ||
| 1001 | { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> }, | ||
| 1002 | any | ||
| 1003 | >; | ||
| 1004 | deleteToolPolicySet: FunctionReference< | ||
| 1005 | "mutation", | ||
| 1006 | "internal", | ||
| 1007 | { roleId: string; workspaceId: Id<"workspaces"> }, | ||
| 1008 | any | ||
| 1009 | >; | ||
| 1010 | listRuntimeTargets: FunctionReference<"query", "internal", {}, any>; | ||
| 1011 | listToolPolicies: FunctionReference< | ||
| 1012 | "query", | ||
| 1013 | "internal", | ||
| 1014 | { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> }, | ||
| 1015 | any | ||
| 1016 | >; | ||
| 1017 | listToolPolicyAssignments: FunctionReference< | ||
| 1018 | "query", | ||
| 1019 | "internal", | ||
| 1020 | { roleId?: string; workspaceId: Id<"workspaces"> }, | ||
| 1021 | any | ||
| 1022 | >; | ||
| 1023 | listToolPolicyRules: FunctionReference< | ||
| 1024 | "query", | ||
| 1025 | "internal", | ||
| 1026 | { roleId: string; workspaceId: Id<"workspaces"> }, | ||
| 1027 | any | ||
| 1028 | >; | ||
| 1029 | listToolPolicySets: FunctionReference< | ||
| 1030 | "query", | ||
| 1031 | "internal", | ||
| 1032 | { workspaceId: Id<"workspaces"> }, | ||
| 1033 | any | ||
| 1034 | >; | ||
| 1035 | upsertToolPolicyAssignment: FunctionReference< | ||
| 1036 | "mutation", | ||
| 1037 | "internal", | ||
| 1038 | { | ||
| 1039 | clientId?: string; | ||
| 1040 | expiresAt?: number; | ||
| 1041 | id?: string; | ||
| 1042 | roleId: string; | ||
| 1043 | scopeType?: "account" | "organization" | "workspace"; | ||
| 1044 | status?: "active" | "disabled"; | ||
| 1045 | targetAccountId?: Id<"accounts">; | ||
| 1046 | workspaceId: Id<"workspaces">; | ||
| 1047 | }, | ||
| 1048 | any | ||
| 1049 | >; | ||
| 1050 | upsertToolPolicyRule: FunctionReference< | ||
| 1051 | "mutation", | ||
| 1052 | "internal", | ||
| 1053 | { | ||
| 1054 | approvalMode?: "inherit" | "auto" | "required"; | ||
| 1055 | argumentConditions?: Array<{ | ||
| 1056 | key: string; | ||
| 1057 | operator: "equals" | "contains" | "starts_with" | "not_equals"; | ||
| 1058 | value: string; | ||
| 1059 | }>; | ||
| 1060 | effect?: "allow" | "deny"; | ||
| 1061 | id?: string; | ||
| 1062 | matchType?: "glob" | "exact"; | ||
| 1063 | priority?: number; | ||
| 1064 | resourcePattern?: string; | ||
| 1065 | roleId: string; | ||
| 1066 | selectorType: "all" | "source" | "namespace" | "tool_path"; | ||
| 1067 | sourceKey?: string; | ||
| 1068 | workspaceId: Id<"workspaces">; | ||
| 1069 | }, | ||
| 1070 | any | ||
| 1071 | >; | ||
| 1072 | upsertToolPolicySet: FunctionReference< | ||
| 1073 | "mutation", | ||
| 1074 | "internal", | ||
| 1075 | { | ||
| 1076 | createdByAccountId?: Id<"accounts">; | ||
| 1077 | description?: string; | ||
| 1078 | id?: string; | ||
| 1079 | name: string; | ||
| 1080 | workspaceId: Id<"workspaces">; | ||
| 1081 | }, | ||
| 1082 | any | ||
| 1083 | >; | ||
| 1084 | }; | ||
| 1085 | resolveApproval: FunctionReference< | ||
| 1086 | "mutation", | ||
| 1087 | "internal", | ||
| 1088 | { | ||
| 1089 | approvalId: string; | ||
| 1090 | decision: "approved" | "denied"; | ||
| 1091 | reason?: string; | ||
| 1092 | reviewerId?: string; | ||
| 1093 | }, | ||
| 1094 | any | ||
| 1095 | >; | ||
| 1096 | resolveCredential: FunctionReference< | ||
| 1097 | "query", | ||
| 1098 | "internal", | ||
| 1099 | { | ||
| 1100 | accountId?: Id<"accounts">; | ||
| 1101 | scopeType: "account" | "organization" | "workspace"; | ||
| 1102 | sourceKey: string; | ||
| 1103 | workspaceId: Id<"workspaces">; | ||
| 1104 | }, | ||
| 1105 | any | ||
| 1106 | >; | ||
| 1107 | setToolCallPendingApproval: FunctionReference< | ||
| 1108 | "mutation", | ||
| 1109 | "internal", | ||
| 1110 | { approvalId: string; callId: string; taskId: string }, | ||
| 1111 | any | ||
| 1112 | >; | ||
| 1113 | storage_instances: { | ||
| 1114 | closeStorageInstance: FunctionReference< | ||
| 1115 | "mutation", | ||
| 1116 | "internal", | ||
| 1117 | { | ||
| 1118 | accountId?: Id<"accounts">; | ||
| 1119 | instanceId: string; | ||
| 1120 | workspaceId: Id<"workspaces">; | ||
| 1121 | }, | ||
| 1122 | any | ||
| 1123 | >; | ||
| 1124 | deleteStorageInstance: FunctionReference< | ||
| 1125 | "mutation", | ||
| 1126 | "internal", | ||
| 1127 | { | ||
| 1128 | accountId?: Id<"accounts">; | ||
| 1129 | instanceId: string; | ||
| 1130 | workspaceId: Id<"workspaces">; | ||
| 1131 | }, | ||
| 1132 | any | ||
| 1133 | >; | ||
| 1134 | getStorageInstance: FunctionReference< | ||
| 1135 | "query", | ||
| 1136 | "internal", | ||
| 1137 | { | ||
| 1138 | accountId?: Id<"accounts">; | ||
| 1139 | instanceId: string; | ||
| 1140 | workspaceId: Id<"workspaces">; | ||
| 1141 | }, | ||
| 1142 | any | ||
| 1143 | >; | ||
| 1144 | listStorageInstances: FunctionReference< | ||
| 1145 | "query", | ||
| 1146 | "internal", | ||
| 1147 | { | ||
| 1148 | accountId?: Id<"accounts">; | ||
| 1149 | includeDeleted?: boolean; | ||
| 1150 | scopeType?: "scratch" | "account" | "workspace" | "organization"; | ||
| 1151 | workspaceId: Id<"workspaces">; | ||
| 1152 | }, | ||
| 1153 | any | ||
| 1154 | >; | ||
| 1155 | openStorageInstance: FunctionReference< | ||
| 1156 | "mutation", | ||
| 1157 | "internal", | ||
| 1158 | { | ||
| 1159 | accountId?: Id<"accounts">; | ||
| 1160 | durability?: "ephemeral" | "durable"; | ||
| 1161 | instanceId?: string; | ||
| 1162 | purpose?: string; | ||
| 1163 | scopeType?: "scratch" | "account" | "workspace" | "organization"; | ||
| 1164 | ttlHours?: number; | ||
| 1165 | workspaceId: Id<"workspaces">; | ||
| 1166 | }, | ||
| 1167 | any | ||
| 1168 | >; | ||
| 1169 | touchStorageInstance: FunctionReference< | ||
| 1170 | "mutation", | ||
| 1171 | "internal", | ||
| 1172 | { | ||
| 1173 | accountId?: Id<"accounts">; | ||
| 1174 | fileCount?: number; | ||
| 1175 | instanceId: string; | ||
| 1176 | provider?: "agentfs-local" | "agentfs-cloudflare"; | ||
| 1177 | sizeBytes?: number; | ||
| 1178 | status?: "active" | "closed" | "deleted"; | ||
| 1179 | workspaceId: Id<"workspaces">; | ||
| 1180 | }, | ||
| 1181 | any | ||
| 1182 | >; | ||
| 1183 | }; | ||
| 1184 | task_events: { | ||
| 1185 | createTaskEvent: FunctionReference< | ||
| 1186 | "mutation", | ||
| 1187 | "internal", | ||
| 1188 | { | ||
| 1189 | eventName: string; | ||
| 1190 | payload: Record<string, any>; | ||
| 1191 | taskId: string; | ||
| 1192 | type: string; | ||
| 1193 | }, | ||
| 1194 | any | ||
| 1195 | >; | ||
| 1196 | listTaskEvents: FunctionReference< | ||
| 1197 | "query", | ||
| 1198 | "internal", | ||
| 1199 | { taskId: string }, | ||
| 1200 | any | ||
| 1201 | >; | ||
| 1202 | }; | ||
| 1203 | tasks: { | ||
| 1204 | createTask: FunctionReference< | ||
| 1205 | "mutation", | ||
| 1206 | "internal", | ||
| 1207 | { | ||
| 1208 | accountId?: Id<"accounts">; | ||
| 1209 | clientId?: string; | ||
| 1210 | code: string; | ||
| 1211 | id: string; | ||
| 1212 | metadata?: Record<string, any>; | ||
| 1213 | runtimeId: string; | ||
| 1214 | timeoutMs?: number; | ||
| 1215 | workspaceId: Id<"workspaces">; | ||
| 1216 | }, | ||
| 1217 | any | ||
| 1218 | >; | ||
| 1219 | getTask: FunctionReference<"query", "internal", { taskId: string }, any>; | ||
| 1220 | getTaskInWorkspace: FunctionReference< | ||
| 1221 | "query", | ||
| 1222 | "internal", | ||
| 1223 | { taskId: string; workspaceId: Id<"workspaces"> }, | ||
| 1224 | any | ||
| 1225 | >; | ||
| 1226 | listQueuedTaskIds: FunctionReference< | ||
| 1227 | "query", | ||
| 1228 | "internal", | ||
| 1229 | { limit?: number }, | ||
| 1230 | any | ||
| 1231 | >; | ||
| 1232 | listTasks: FunctionReference< | ||
| 1233 | "query", | ||
| 1234 | "internal", | ||
| 1235 | { workspaceId: Id<"workspaces"> }, | ||
| 1236 | any | ||
| 1237 | >; | ||
| 1238 | markTaskFinished: FunctionReference< | ||
| 1239 | "mutation", | ||
| 1240 | "internal", | ||
| 1241 | { | ||
| 1242 | error?: string; | ||
| 1243 | exitCode?: number; | ||
| 1244 | status: "completed" | "failed" | "timed_out" | "denied"; | ||
| 1245 | taskId: string; | ||
| 1246 | }, | ||
| 1247 | any | ||
| 1248 | >; | ||
| 1249 | markTaskRunning: FunctionReference< | ||
| 1250 | "mutation", | ||
| 1251 | "internal", | ||
| 1252 | { taskId: string }, | ||
| 1253 | any | ||
| 1254 | >; | ||
| 1255 | }; | ||
| 1256 | tool_calls: { | ||
| 1257 | finishToolCall: FunctionReference< | ||
| 1258 | "mutation", | ||
| 1259 | "internal", | ||
| 1260 | { | ||
| 1261 | callId: string; | ||
| 1262 | error?: string; | ||
| 1263 | status: "completed" | "failed" | "denied"; | ||
| 1264 | taskId: string; | ||
| 1265 | }, | ||
| 1266 | any | ||
| 1267 | >; | ||
| 1268 | getToolCall: FunctionReference< | ||
| 1269 | "query", | ||
| 1270 | "internal", | ||
| 1271 | { callId: string; taskId: string }, | ||
| 1272 | any | ||
| 1273 | >; | ||
| 1274 | listToolCalls: FunctionReference< | ||
| 1275 | "query", | ||
| 1276 | "internal", | ||
| 1277 | { taskId: string }, | ||
| 1278 | any | ||
| 1279 | >; | ||
| 1280 | setToolCallPendingApproval: FunctionReference< | ||
| 1281 | "mutation", | ||
| 1282 | "internal", | ||
| 1283 | { approvalId: string; callId: string; taskId: string }, | ||
| 1284 | any | ||
| 1285 | >; | ||
| 1286 | upsertToolCallRequested: FunctionReference< | ||
| 1287 | "mutation", | ||
| 1288 | "internal", | ||
| 1289 | { | ||
| 1290 | callId: string; | ||
| 1291 | taskId: string; | ||
| 1292 | toolPath: string; | ||
| 1293 | workspaceId: Id<"workspaces">; | ||
| 1294 | }, | ||
| 1295 | any | ||
| 1296 | >; | ||
| 1297 | }; | ||
| 1298 | tool_sources: { | ||
| 1299 | deleteToolSource: FunctionReference< | ||
| 1300 | "mutation", | ||
| 1301 | "internal", | ||
| 1302 | { sourceId: string; workspaceId: Id<"workspaces"> }, | ||
| 1303 | any | ||
| 1304 | >; | ||
| 1305 | listToolSources: FunctionReference< | ||
| 1306 | "query", | ||
| 1307 | "internal", | ||
| 1308 | { workspaceId: Id<"workspaces"> }, | ||
| 1309 | any | ||
| 1310 | >; | ||
| 1311 | upsertToolSource: FunctionReference< | ||
| 1312 | "mutation", | ||
| 1313 | "internal", | ||
| 1314 | { | ||
| 1315 | config: Record<string, any>; | ||
| 1316 | enabled?: boolean; | ||
| 1317 | id?: string; | ||
| 1318 | name: string; | ||
| 1319 | scopeType?: "organization" | "workspace"; | ||
| 1320 | type: "mcp" | "openapi" | "graphql"; | ||
| 1321 | workspaceId: Id<"workspaces">; | ||
| 1322 | }, | ||
| 1323 | any | ||
| 1324 | >; | ||
| 1325 | }; | ||
| 1326 | touchStorageInstance: FunctionReference< | ||
| 1327 | "mutation", | ||
| 1328 | "internal", | ||
| 1329 | { | ||
| 1330 | accountId?: Id<"accounts">; | ||
| 1331 | fileCount?: number; | ||
| 1332 | instanceId: string; | ||
| 1333 | provider?: "agentfs-local" | "agentfs-cloudflare"; | ||
| 1334 | sizeBytes?: number; | ||
| 1335 | status?: "active" | "closed" | "deleted"; | ||
| 1336 | workspaceId: Id<"workspaces">; | ||
| 1337 | }, | ||
| 1338 | any | ||
| 1339 | >; | ||
| 1340 | upsertCredential: FunctionReference< | ||
| 1341 | "mutation", | ||
| 1342 | "internal", | ||
| 1343 | { | ||
| 1344 | accountId?: Id<"accounts">; | ||
| 1345 | id?: string; | ||
| 1346 | overridesJson?: Record<string, any>; | ||
| 1347 | provider?: "local-convex" | "workos-vault"; | ||
| 1348 | scopeType?: "account" | "organization" | "workspace"; | ||
| 1349 | secretJson: Record<string, any>; | ||
| 1350 | sourceKey: string; | ||
| 1351 | workspaceId: Id<"workspaces">; | ||
| 1352 | }, | ||
| 1353 | any | ||
| 1354 | >; | ||
| 1355 | upsertToolCallRequested: FunctionReference< | ||
| 1356 | "mutation", | ||
| 1357 | "internal", | ||
| 1358 | { | ||
| 1359 | callId: string; | ||
| 1360 | taskId: string; | ||
| 1361 | toolPath: string; | ||
| 1362 | workspaceId: Id<"workspaces">; | ||
| 1363 | }, | ||
| 1364 | any | ||
| 1365 | >; | ||
| 1366 | upsertToolPolicyAssignment: FunctionReference< | ||
| 1367 | "mutation", | ||
| 1368 | "internal", | ||
| 1369 | { | ||
| 1370 | clientId?: string; | ||
| 1371 | expiresAt?: number; | ||
| 1372 | id?: string; | ||
| 1373 | roleId: string; | ||
| 1374 | scopeType?: "account" | "organization" | "workspace"; | ||
| 1375 | status?: "active" | "disabled"; | ||
| 1376 | targetAccountId?: Id<"accounts">; | ||
| 1377 | workspaceId: Id<"workspaces">; | ||
| 1378 | }, | ||
| 1379 | any | ||
| 1380 | >; | ||
| 1381 | upsertToolPolicyRule: FunctionReference< | ||
| 1382 | "mutation", | ||
| 1383 | "internal", | ||
| 1384 | { | ||
| 1385 | approvalMode?: "inherit" | "auto" | "required"; | ||
| 1386 | argumentConditions?: Array<{ | ||
| 1387 | key: string; | ||
| 1388 | operator: "equals" | "contains" | "starts_with" | "not_equals"; | ||
| 1389 | value: string; | ||
| 1390 | }>; | ||
| 1391 | effect?: "allow" | "deny"; | ||
| 1392 | id?: string; | ||
| 1393 | matchType?: "glob" | "exact"; | ||
| 1394 | priority?: number; | ||
| 1395 | resourcePattern?: string; | ||
| 1396 | roleId: string; | ||
| 1397 | selectorType: "all" | "source" | "namespace" | "tool_path"; | ||
| 1398 | sourceKey?: string; | ||
| 1399 | workspaceId: Id<"workspaces">; | ||
| 1400 | }, | ||
| 1401 | any | ||
| 1402 | >; | ||
| 1403 | upsertToolPolicySet: FunctionReference< | ||
| 1404 | "mutation", | ||
| 1405 | "internal", | ||
| 1406 | { | ||
| 1407 | createdByAccountId?: Id<"accounts">; | ||
| 1408 | description?: string; | ||
| 1409 | id?: string; | ||
| 1410 | name: string; | ||
| 1411 | workspaceId: Id<"workspaces">; | ||
| 1412 | }, | ||
| 1413 | any | ||
| 1414 | >; | ||
| 1415 | upsertToolSource: FunctionReference< | ||
| 1416 | "mutation", | ||
| 1417 | "internal", | ||
| 1418 | { | ||
| 1419 | config: Record<string, any>; | ||
| 1420 | enabled?: boolean; | ||
| 1421 | id?: string; | ||
| 1422 | name: string; | ||
| 1423 | scopeType?: "organization" | "workspace"; | ||
| 1424 | type: "mcp" | "openapi" | "graphql"; | ||
| 1425 | workspaceId: Id<"workspaces">; | ||
| 1426 | }, | ||
| 1427 | any | ||
| 1428 | >; | ||
| 1429 | }; | ||
| 1430 | executor: { | ||
| 1431 | completeRuntimeRun: FunctionReference< | ||
| 1432 | "mutation", | ||
| 1433 | "internal", | ||
| 1434 | { | ||
| 1435 | durationMs?: number; | ||
| 1436 | error?: string; | ||
| 1437 | exitCode?: number; | ||
| 1438 | runId: string; | ||
| 1439 | status: "completed" | "failed" | "timed_out" | "denied"; | ||
| 1440 | }, | ||
| 1441 | any | ||
| 1442 | >; | ||
| 1443 | createTaskInternal: FunctionReference< | ||
| 1444 | "mutation", | ||
| 1445 | "internal", | ||
| 1446 | { | ||
| 1447 | accountId: Id<"accounts">; | ||
| 1448 | clientId?: string; | ||
| 1449 | code: string; | ||
| 1450 | metadata?: Record<string, any>; | ||
| 1451 | runtimeId?: string; | ||
| 1452 | scheduleAfterCreate?: boolean; | ||
| 1453 | timeoutMs?: number; | ||
| 1454 | workspaceId: Id<"workspaces">; | ||
| 1455 | }, | ||
| 1456 | any | ||
| 1457 | >; | ||
| 1458 | resolveApprovalInternal: FunctionReference< | ||
| 1459 | "mutation", | ||
| 1460 | "internal", | ||
| 1461 | { | ||
| 1462 | approvalId: string; | ||
| 1463 | decision: "approved" | "denied"; | ||
| 1464 | reason?: string; | ||
| 1465 | reviewerId?: string; | ||
| 1466 | workspaceId: Id<"workspaces">; | ||
| 1467 | }, | ||
| 1468 | any | ||
| 1469 | >; | ||
| 1470 | }; | ||
| 1471 | executorNode: { | ||
| 1472 | handleExternalToolCall: FunctionReference< | ||
| 1473 | "action", | ||
| 1474 | "internal", | ||
| 1475 | { | ||
| 1476 | callId: string; | ||
| 1477 | input?: Record<string, any>; | ||
| 1478 | runId: string; | ||
| 1479 | toolPath: string; | ||
| 1480 | }, | ||
| 1481 | any | ||
| 1482 | >; | ||
| 1483 | listToolsInternal: FunctionReference< | ||
| 1484 | "action", | ||
| 1485 | "internal", | ||
| 1486 | { | ||
| 1487 | accountId?: Id<"accounts">; | ||
| 1488 | clientId?: string; | ||
| 1489 | workspaceId: Id<"workspaces">; | ||
| 1490 | }, | ||
| 1491 | any | ||
| 1492 | >; | ||
| 1493 | listToolsWithWarningsInternal: FunctionReference< | ||
| 1494 | "action", | ||
| 1495 | "internal", | ||
| 1496 | { | ||
| 1497 | accountId?: Id<"accounts">; | ||
| 1498 | clientId?: string; | ||
| 1499 | workspaceId: Id<"workspaces">; | ||
| 1500 | }, | ||
| 1501 | any | ||
| 1502 | >; | ||
| 1503 | rebuildToolInventoryInternal: FunctionReference< | ||
| 1504 | "action", | ||
| 1505 | "internal", | ||
| 1506 | { | ||
| 1507 | accountId?: Id<"accounts">; | ||
| 1508 | clientId?: string; | ||
| 1509 | workspaceId: Id<"workspaces">; | ||
| 1510 | }, | ||
| 1511 | any | ||
| 1512 | >; | ||
| 1513 | runTask: FunctionReference<"action", "internal", { taskId: string }, any>; | ||
| 1514 | }; | ||
| 1515 | executorRuntimeNode: { | ||
| 1516 | dispatchCloudflareWorker: FunctionReference< | ||
| 1517 | "action", | ||
| 1518 | "internal", | ||
| 1519 | { code: string; taskId: string; timeoutMs: number }, | ||
| 1520 | any | ||
| 1521 | >; | ||
| 1522 | }; | ||
| 1523 | invites: { | ||
| 1524 | deliverWorkosInvite: FunctionReference< | ||
| 1525 | "action", | ||
| 1526 | "internal", | ||
| 1527 | { | ||
| 1528 | expiresInDays?: number; | ||
| 1529 | inviteId: Id<"invites">; | ||
| 1530 | inviterWorkosUserId: string; | ||
| 1531 | roleSlug?: string; | ||
| 1532 | }, | ||
| 1533 | any | ||
| 1534 | >; | ||
| 1535 | getInviteById: FunctionReference< | ||
| 1536 | "query", | ||
| 1537 | "internal", | ||
| 1538 | { inviteId: Id<"invites"> }, | ||
| 1539 | any | ||
| 1540 | >; | ||
| 1541 | getInviteDeliveryContext: FunctionReference< | ||
| 1542 | "query", | ||
| 1543 | "internal", | ||
| 1544 | { inviteId: Id<"invites"> }, | ||
| 1545 | any | ||
| 1546 | >; | ||
| 1547 | linkOrganizationToWorkos: FunctionReference< | ||
| 1548 | "mutation", | ||
| 1549 | "internal", | ||
| 1550 | { organizationId: Id<"organizations">; workosOrgId: string }, | ||
| 1551 | any | ||
| 1552 | >; | ||
| 1553 | markInviteDelivered: FunctionReference< | ||
| 1554 | "mutation", | ||
| 1555 | "internal", | ||
| 1556 | { inviteId: Id<"invites">; providerInviteId: string }, | ||
| 1557 | any | ||
| 1558 | >; | ||
| 1559 | markInviteDeliveryFailed: FunctionReference< | ||
| 1560 | "mutation", | ||
| 1561 | "internal", | ||
| 1562 | { errorMessage: string; inviteId: Id<"invites"> }, | ||
| 1563 | any | ||
| 1564 | >; | ||
| 1565 | revokeWorkosInvite: FunctionReference< | ||
| 1566 | "action", | ||
| 1567 | "internal", | ||
| 1568 | { inviteId: Id<"invites">; providerInviteId: string }, | ||
| 1569 | any | ||
| 1570 | >; | ||
| 1571 | }; | ||
| 1572 | migrations: { | ||
| 1573 | run: FunctionReference< | ||
| 1574 | "mutation", | ||
| 1575 | "internal", | ||
| 1576 | { | ||
| 1577 | batchSize?: number; | ||
| 1578 | cursor?: string | null; | ||
| 1579 | dryRun?: boolean; | ||
| 1580 | fn?: string; | ||
| 1581 | next?: Array<string>; | ||
| 1582 | }, | ||
| 1583 | any | ||
| 1584 | >; | ||
| 1585 | }; | ||
| 1586 | openApiSpecCache: { | ||
| 1587 | getEntry: FunctionReference< | ||
| 1588 | "query", | ||
| 1589 | "internal", | ||
| 1590 | { maxAgeMs: number; specUrl: string; version: string }, | ||
| 1591 | any | ||
| 1592 | >; | ||
| 1593 | putEntry: FunctionReference< | ||
| 1594 | "mutation", | ||
| 1595 | "internal", | ||
| 1596 | { | ||
| 1597 | sizeBytes: number; | ||
| 1598 | specUrl: string; | ||
| 1599 | storageId: Id<"_storage">; | ||
| 1600 | version: string; | ||
| 1601 | }, | ||
| 1602 | any | ||
| 1603 | >; | ||
| 1604 | }; | ||
| 1605 | runtimeNode: { | ||
| 1606 | compileExternalToolSource: FunctionReference< | ||
| 1607 | "action", | ||
| 1608 | "internal", | ||
| 1609 | { source: Record<string, any> }, | ||
| 1610 | any | ||
| 1611 | >; | ||
| 1612 | executeLocalVm: FunctionReference< | ||
| 1613 | "action", | ||
| 1614 | "internal", | ||
| 1615 | { code: string; taskId: string; timeoutMs: number }, | ||
| 1616 | any | ||
| 1617 | >; | ||
| 1618 | prepareOpenApiSpec: FunctionReference< | ||
| 1619 | "action", | ||
| 1620 | "internal", | ||
| 1621 | { includeDts?: boolean; sourceName: string; specUrl: string }, | ||
| 1622 | any | ||
| 1623 | >; | ||
| 1624 | }; | ||
| 1625 | toolRegistry: { | ||
| 1626 | beginBuild: FunctionReference< | ||
| 1627 | "mutation", | ||
| 1628 | "internal", | ||
| 1629 | { buildId: string; signature: string; workspaceId: Id<"workspaces"> }, | ||
| 1630 | any | ||
| 1631 | >; | ||
| 1632 | deleteToolRegistryNamespacesPage: FunctionReference< | ||
| 1633 | "mutation", | ||
| 1634 | "internal", | ||
| 1635 | { buildId: string; cursor?: string; workspaceId: Id<"workspaces"> }, | ||
| 1636 | any | ||
| 1637 | >; | ||
| 1638 | deleteToolRegistryPayloadsPage: FunctionReference< | ||
| 1639 | "mutation", | ||
| 1640 | "internal", | ||
| 1641 | { buildId: string; cursor?: string; workspaceId: Id<"workspaces"> }, | ||
| 1642 | any | ||
| 1643 | >; | ||
| 1644 | deleteToolRegistryToolsPage: FunctionReference< | ||
| 1645 | "mutation", | ||
| 1646 | "internal", | ||
| 1647 | { buildId: string; cursor?: string; workspaceId: Id<"workspaces"> }, | ||
| 1648 | any | ||
| 1649 | >; | ||
| 1650 | failBuild: FunctionReference< | ||
| 1651 | "mutation", | ||
| 1652 | "internal", | ||
| 1653 | { buildId: string; error?: string; workspaceId: Id<"workspaces"> }, | ||
| 1654 | any | ||
| 1655 | >; | ||
| 1656 | finishBuild: FunctionReference< | ||
| 1657 | "mutation", | ||
| 1658 | "internal", | ||
| 1659 | { buildId: string; signature: string; workspaceId: Id<"workspaces"> }, | ||
| 1660 | any | ||
| 1661 | >; | ||
| 1662 | getSerializedToolsByPaths: FunctionReference< | ||
| 1663 | "query", | ||
| 1664 | "internal", | ||
| 1665 | { buildId: string; paths: Array<string>; workspaceId: Id<"workspaces"> }, | ||
| 1666 | any | ||
| 1667 | >; | ||
| 1668 | getState: FunctionReference< | ||
| 1669 | "query", | ||
| 1670 | "internal", | ||
| 1671 | { workspaceId: Id<"workspaces"> }, | ||
| 1672 | any | ||
| 1673 | >; | ||
| 1674 | getToolByPath: FunctionReference< | ||
| 1675 | "query", | ||
| 1676 | "internal", | ||
| 1677 | { buildId: string; path: string; workspaceId: Id<"workspaces"> }, | ||
| 1678 | any | ||
| 1679 | >; | ||
| 1680 | getToolsByNormalizedPath: FunctionReference< | ||
| 1681 | "query", | ||
| 1682 | "internal", | ||
| 1683 | { | ||
| 1684 | buildId: string; | ||
| 1685 | limit: number; | ||
| 1686 | normalizedPath: string; | ||
| 1687 | workspaceId: Id<"workspaces">; | ||
| 1688 | }, | ||
| 1689 | any | ||
| 1690 | >; | ||
| 1691 | listNamespaces: FunctionReference< | ||
| 1692 | "query", | ||
| 1693 | "internal", | ||
| 1694 | { buildId: string; limit: number; workspaceId: Id<"workspaces"> }, | ||
| 1695 | any | ||
| 1696 | >; | ||
| 1697 | listToolsByNamespace: FunctionReference< | ||
| 1698 | "query", | ||
| 1699 | "internal", | ||
| 1700 | { | ||
| 1701 | buildId: string; | ||
| 1702 | limit: number; | ||
| 1703 | namespace: string; | ||
| 1704 | workspaceId: Id<"workspaces">; | ||
| 1705 | }, | ||
| 1706 | any | ||
| 1707 | >; | ||
| 1708 | listToolsBySourcePage: FunctionReference< | ||
| 1709 | "query", | ||
| 1710 | "internal", | ||
| 1711 | { | ||
| 1712 | buildId: string; | ||
| 1713 | cursor?: string; | ||
| 1714 | limit: number; | ||
| 1715 | source: string; | ||
| 1716 | workspaceId: Id<"workspaces">; | ||
| 1717 | }, | ||
| 1718 | any | ||
| 1719 | >; | ||
| 1720 | listToolsPage: FunctionReference< | ||
| 1721 | "query", | ||
| 1722 | "internal", | ||
| 1723 | { | ||
| 1724 | buildId: string; | ||
| 1725 | cursor?: string; | ||
| 1726 | limit: number; | ||
| 1727 | workspaceId: Id<"workspaces">; | ||
| 1728 | }, | ||
| 1729 | any | ||
| 1730 | >; | ||
| 1731 | pruneBuilds: FunctionReference< | ||
| 1732 | "action", | ||
| 1733 | "internal", | ||
| 1734 | { maxRetainedBuilds?: number; workspaceId: Id<"workspaces"> }, | ||
| 1735 | any | ||
| 1736 | >; | ||
| 1737 | putNamespacesBatch: FunctionReference< | ||
| 1738 | "mutation", | ||
| 1739 | "internal", | ||
| 1740 | { | ||
| 1741 | buildId: string; | ||
| 1742 | namespaces: Array<{ | ||
| 1743 | namespace: string; | ||
| 1744 | samplePaths: Array<string>; | ||
| 1745 | toolCount: number; | ||
| 1746 | }>; | ||
| 1747 | workspaceId: Id<"workspaces">; | ||
| 1748 | }, | ||
| 1749 | any | ||
| 1750 | >; | ||
| 1751 | putToolsBatch: FunctionReference< | ||
| 1752 | "mutation", | ||
| 1753 | "internal", | ||
| 1754 | { | ||
| 1755 | buildId: string; | ||
| 1756 | tools: Array<{ | ||
| 1757 | aliases: Array<string>; | ||
| 1758 | approval: "auto" | "required"; | ||
| 1759 | description: string; | ||
| 1760 | displayInput?: string; | ||
| 1761 | displayOutput?: string; | ||
| 1762 | namespace: string; | ||
| 1763 | normalizedPath: string; | ||
| 1764 | path: string; | ||
| 1765 | preferredPath: string; | ||
| 1766 | previewInputKeys?: Array<string>; | ||
| 1767 | requiredInputKeys?: Array<string>; | ||
| 1768 | searchText: string; | ||
| 1769 | serializedToolJson: string; | ||
| 1770 | source?: string; | ||
| 1771 | typedRef?: { | ||
| 1772 | kind: "openapi_operation"; | ||
| 1773 | operationId: string; | ||
| 1774 | sourceKey: string; | ||
| 1775 | }; | ||
| 1776 | }>; | ||
| 1777 | workspaceId: Id<"workspaces">; | ||
| 1778 | }, | ||
| 1779 | any | ||
| 1780 | >; | ||
| 1781 | scanNamespaceBuildsForPrune: FunctionReference< | ||
| 1782 | "query", | ||
| 1783 | "internal", | ||
| 1784 | { cursor?: string; workspaceId: Id<"workspaces"> }, | ||
| 1785 | any | ||
| 1786 | >; | ||
| 1787 | searchTools: FunctionReference< | ||
| 1788 | "query", | ||
| 1789 | "internal", | ||
| 1790 | { | ||
| 1791 | buildId: string; | ||
| 1792 | limit: number; | ||
| 1793 | query: string; | ||
| 1794 | workspaceId: Id<"workspaces">; | ||
| 1795 | }, | ||
| 1796 | any | ||
| 1797 | >; | ||
| 1798 | updateBuildMetadata: FunctionReference< | ||
| 1799 | "mutation", | ||
| 1800 | "internal", | ||
| 1801 | { | ||
| 1802 | buildId: string; | ||
| 1803 | openApiRefHintTables?: Array<{ | ||
| 1804 | refs: Array<{ hint: string; key: string }>; | ||
| 1805 | sourceKey: string; | ||
| 1806 | }>; | ||
| 1807 | sourceAuthProfiles: Array<{ | ||
| 1808 | header?: string; | ||
| 1809 | inferred: boolean; | ||
| 1810 | mode?: "account" | "organization" | "workspace"; | ||
| 1811 | sourceKey: string; | ||
| 1812 | type: "none" | "bearer" | "apiKey" | "basic" | "mixed"; | ||
| 1813 | }>; | ||
| 1814 | sourceQuality: Array<{ | ||
| 1815 | argsQuality: number; | ||
| 1816 | overallQuality: number; | ||
| 1817 | partialUnknownArgsCount: number; | ||
| 1818 | partialUnknownReturnsCount: number; | ||
| 1819 | returnsQuality: number; | ||
| 1820 | sourceKey: string; | ||
| 1821 | toolCount: number; | ||
| 1822 | unknownArgsCount: number; | ||
| 1823 | unknownReturnsCount: number; | ||
| 1824 | }>; | ||
| 1825 | sourceToolCounts: Array<{ sourceName: string; toolCount: number }>; | ||
| 1826 | sourceVersions: Array<{ | ||
| 1827 | sourceId: string; | ||
| 1828 | sourceName: string; | ||
| 1829 | updatedAt: number; | ||
| 1830 | }>; | ||
| 1831 | toolCount: number; | ||
| 1832 | typesStorageId?: Id<"_storage">; | ||
| 1833 | warnings: Array<string>; | ||
| 1834 | workspaceId: Id<"workspaces">; | ||
| 1835 | }, | ||
| 1836 | any | ||
| 1837 | >; | ||
| 1838 | }; | ||
| 1839 | workspaceAuthInternal: { | ||
| 1840 | getWorkspaceAccessForAccount: FunctionReference< | ||
| 1841 | "query", | ||
| 1842 | "internal", | ||
| 1843 | { accountId: Id<"accounts">; workspaceId: Id<"workspaces"> }, | ||
| 1844 | any | ||
| 1845 | >; | ||
| 1846 | getWorkspaceAccessForAnonymousSubject: FunctionReference< | ||
| 1847 | "query", | ||
| 1848 | "internal", | ||
| 1849 | { accountId: string; workspaceId: Id<"workspaces"> }, | ||
| 1850 | any | ||
| 1851 | >; | ||
| 1852 | getWorkspaceAccessForRequest: FunctionReference< | ||
| 1853 | "query", | ||
| 1854 | "internal", | ||
| 1855 | { sessionId?: string; workspaceId: Id<"workspaces"> }, | ||
| 1856 | any | ||
| 1857 | >; | ||
| 1858 | getWorkspaceAccessForWorkosSubject: FunctionReference< | ||
| 1859 | "query", | ||
| 1860 | "internal", | ||
| 1861 | { subject: string; workspaceId: Id<"workspaces"> }, | ||
| 1862 | any | ||
| 1863 | >; | ||
| 1864 | }; | ||
| 1865 | }; | ||
| 1866 | 126 | ||
| 1867 | export declare const components: { | 127 | export declare const components: { |
| 1868 | workOSAuthKit: { | 128 | workOSAuthKit: { |
| @@ -6,6 +6,8 @@ export { · 5 unmodified lines | |||
| 6 | getTaskInWorkspace, | 6 | getTaskInWorkspace, |
| 7 | markTaskRunning, | 7 | markTaskRunning, |
| 8 | markTaskFinished, | 8 | markTaskFinished, |
| 9 | setTaskStorageDefaultInstance, | ||
| 10 | trackTaskStorageAccess, | ||
| 9 | } from "./database/tasks"; | 11 | } from "./database/tasks"; |
| 10 | export { | 12 | export { |
| 11 | createApproval, | 13 | createApproval, |
| @@ -14,9 +14,29 @@ const MAX_EPHEMERAL_TTL_HOURS = 24 * 30; · 13 unmodified lines | |||
| 14 | 14 | ||
| 15 | function resolveStorageProvider(): "agentfs-local" | "agentfs-cloudflare" { | 15 | function resolveStorageProvider(): "agentfs-local" | "agentfs-cloudflare" { |
| 16 | const raw = (process.env.AGENT_STORAGE_PROVIDER ?? "").trim().toLowerCase(); | 16 | const raw = (process.env.AGENT_STORAGE_PROVIDER ?? "").trim().toLowerCase(); |
| 17 | |||
| 18 | const isHostedConvexDeployment = [process.env.CONVEX_URL, process.env.CONVEX_SITE_URL] | ||
| 19 | .map((value) => value?.trim()) | ||
| 20 | .filter((value): value is string => Boolean(value)) | ||
| 21 | .some((value) => { | ||
| 22 | try { | ||
| 23 | const hostname = new URL(value).hostname.trim().toLowerCase(); | ||
| 24 | return hostname.endsWith(".convex.cloud") || hostname.endsWith(".convex.site"); | ||
| 25 | } catch { | ||
| 26 | return false; | ||
| 27 | } | ||
| 28 | }); | ||
| 29 | |||
| 17 | if (raw === "cloudflare" || raw === "agentfs-cloudflare") { | 30 | if (raw === "cloudflare" || raw === "agentfs-cloudflare") { |
| 18 | return "agentfs-cloudflare"; | 31 | return "agentfs-cloudflare"; |
| 19 | } | 32 | } |
| 33 | |||
| 34 | if (isHostedConvexDeployment) { | ||
| 35 | throw new Error( | ||
| 36 | "agentfs-local is not supported on hosted Convex deployments because filesystem state is not shared across workers. Set AGENT_STORAGE_PROVIDER=agentfs-cloudflare.", | ||
| 37 | ); | ||
| 38 | } | ||
| 39 | |||
| 20 | return "agentfs-local"; | 40 | return "agentfs-local"; |
| 21 | } | 41 | } |
| 22 | 42 | ||
| @@ -2,10 +2,46 @@ import { v } from "convex/values"; · 1 unmodified line | |||
| 2 | import { internalMutation, internalQuery } from "../_generated/server"; | 2 | import { internalMutation, internalQuery } from "../_generated/server"; |
| 3 | import { mapTask } from "../../src/database/mappers"; | 3 | import { mapTask } from "../../src/database/mappers"; |
| 4 | import { getTaskDoc } from "../../src/database/readers"; | 4 | import { getTaskDoc } from "../../src/database/readers"; |
| 5 | import { completedTaskStatusValidator, jsonObjectValidator } from "../../src/database/validators"; | 5 | import { |
| 6 | completedTaskStatusValidator, | ||
| 7 | jsonObjectValidator, | ||
| 8 | storageAccessTypeValidator, | ||
| 9 | storageScopeTypeValidator, | ||
| 10 | } from "../../src/database/validators"; | ||
| 6 | import { DEFAULT_TASK_TIMEOUT_MS } from "../../src/task/constants"; | 11 | import { DEFAULT_TASK_TIMEOUT_MS } from "../../src/task/constants"; |
| 7 | import { isTerminalTaskStatus } from "../../src/task/status"; | 12 | import { isTerminalTaskStatus } from "../../src/task/status"; |
| 8 | 13 | ||
| 14 | function asRecord(value: unknown): Record<string, unknown> { | ||
| 15 | return value && typeof value === "object" && !Array.isArray(value) | ||
| 16 | ? value as Record<string, unknown> | ||
| 17 | : {}; | ||
| 18 | } | ||
| 19 | |||
| 20 | function asStringArray(value: unknown): string[] { | ||
| 21 | if (!Array.isArray(value)) { | ||
| 22 | return []; | ||
| 23 | } | ||
| 24 | |||
| 25 | return value | ||
| 26 | .filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0) | ||
| 27 | .map((entry) => entry.trim()); | ||
| 28 | } | ||
| 29 | |||
| 30 | function pushUniqueString(items: string[], value: string, maxItems = 50): string[] { | ||
| 31 | const normalized = value.trim(); | ||
| 32 | if (!normalized) { | ||
| 33 | return items; | ||
| 34 | } | ||
| 35 | |||
| 36 | const withoutExisting = items.filter((entry) => entry !== normalized); | ||
| 37 | const next = [...withoutExisting, normalized]; | ||
| 38 | if (next.length <= maxItems) { | ||
| 39 | return next; | ||
| 40 | } | ||
| 41 | |||
| 42 | return next.slice(next.length - maxItems); | ||
| 43 | } | ||
| 44 | |||
| 9 | export const createTask = internalMutation({ | 45 | export const createTask = internalMutation({ |
| 10 | args: { | 46 | args: { |
| 11 | id: v.string(), | 47 | id: v.string(), |
| @@ -143,3 +179,106 @@ export const markTaskFinished = internalMutation({ · 131 unmodified lines | |||
| 143 | return updated ? mapTask(updated) : null; | 179 | return updated ? mapTask(updated) : null; |
| 144 | }, | 180 | }, |
| 145 | }); | 181 | }); |
| 182 | |||
| 183 | export const setTaskStorageDefaultInstance = internalMutation({ | ||
| 184 | args: { | ||
| 185 | taskId: v.string(), | ||
| 186 | scopeType: storageScopeTypeValidator, | ||
| 187 | instanceId: v.string(), | ||
| 188 | setCurrent: v.optional(v.boolean()), | ||
| 189 | }, | ||
| 190 | handler: async (ctx, args) => { | ||
| 191 | const doc = await getTaskDoc(ctx, args.taskId); | ||
| 192 | if (!doc) { | ||
| 193 | return null; | ||
| 194 | } | ||
| 195 | |||
| 196 | const now = Date.now(); | ||
| 197 | const metadata = asRecord(doc.metadata); | ||
| 198 | const storage = asRecord(metadata.storage); | ||
| 199 | const defaultsByScope = asRecord(storage.defaultInstanceByScope); | ||
| 200 | |||
| 201 | defaultsByScope[args.scopeType] = args.instanceId; | ||
| 202 | |||
| 203 | const nextStorage: Record<string, unknown> = { | ||
| 204 | ...storage, | ||
| 205 | defaultInstanceByScope: defaultsByScope, | ||
| 206 | }; | ||
| 207 | |||
| 208 | if (args.setCurrent !== false) { | ||
| 209 | nextStorage.currentInstanceId = args.instanceId; | ||
| 210 | nextStorage.currentScopeType = args.scopeType; | ||
| 211 | } | ||
| 212 | |||
| 213 | await ctx.db.patch(doc._id, { | ||
| 214 | metadata: { | ||
| 215 | ...metadata, | ||
| 216 | storage: nextStorage, | ||
| 217 | }, | ||
| 218 | updatedAt: now, | ||
| 219 | }); | ||
| 220 | |||
| 221 | const updated = await getTaskDoc(ctx, args.taskId); | ||
| 222 | return updated ? mapTask(updated) : null; | ||
| 223 | }, | ||
| 224 | }); | ||
| 225 | |||
| 226 | export const trackTaskStorageAccess = internalMutation({ | ||
| 227 | args: { | ||
| 228 | taskId: v.string(), | ||
| 229 | instanceId: v.string(), | ||
| 230 | scopeType: v.optional(storageScopeTypeValidator), | ||
| 231 | accessType: storageAccessTypeValidator, | ||
| 232 | }, | ||
| 233 | handler: async (ctx, args) => { | ||
| 234 | const doc = await getTaskDoc(ctx, args.taskId); | ||
| 235 | if (!doc) { | ||
| 236 | return null; | ||
| 237 | } | ||
| 238 | |||
| 239 | const now = Date.now(); | ||
| 240 | const metadata = asRecord(doc.metadata); | ||
| 241 | const storage = asRecord(metadata.storage); | ||
| 242 | |||
| 243 | const accessedInstanceIds = pushUniqueString( | ||
| 244 | asStringArray(storage.accessedInstanceIds), | ||
| 245 | args.instanceId, | ||
| 246 | ); | ||
| 247 | |||
| 248 | const nextStorage: Record<string, unknown> = { | ||
| 249 | ...storage, | ||
| 250 | accessedInstanceIds, | ||
| 251 | lastAccessedInstanceId: args.instanceId, | ||
| 252 | lastAccessedAt: now, | ||
| 253 | }; | ||
| 254 | |||
| 255 | if (args.accessType === "opened") { | ||
| 256 | nextStorage.openedInstanceIds = pushUniqueString( | ||
| 257 | asStringArray(storage.openedInstanceIds), | ||
| 258 | args.instanceId, | ||
| 259 | ); | ||
| 260 | } | ||
| 261 | |||
| 262 | if (args.accessType === "provided") { | ||
| 263 | nextStorage.providedInstanceIds = pushUniqueString( | ||
| 264 | asStringArray(storage.providedInstanceIds), | ||
| 265 | args.instanceId, | ||
| 266 | ); | ||
| 267 | } | ||
| 268 | |||
| 269 | if (args.scopeType) { | ||
| 270 | nextStorage.lastAccessedScopeType = args.scopeType; | ||
| 271 | } | ||
| 272 | |||
| 273 | await ctx.db.patch(doc._id, { | ||
| 274 | metadata: { | ||
| 275 | ...metadata, | ||
| 276 | storage: nextStorage, | ||
| 277 | }, | ||
| 278 | updatedAt: now, | ||
| 279 | }); | ||
| 280 | |||
| 281 | const updated = await getTaskDoc(ctx, args.taskId); | ||
| 282 | return updated ? mapTask(updated) : null; | ||
| 283 | }, | ||
| 284 | }); | ||
| @@ -7,6 +7,7 @@ import type { · 6 unmodified lines | |||
| 7 | ToolDescriptor, | 7 | ToolDescriptor, |
| 8 | OpenApiSourceQuality, | 8 | OpenApiSourceQuality, |
| 9 | SourceAuthProfile, | 9 | SourceAuthProfile, |
| 10 | StorageInstanceRecord, | ||
| 10 | } from "../../core/src/types"; | 11 | } from "../../core/src/types"; |
| 11 | import { isAdminRole } from "../../core/src/identity"; | 12 | import { isAdminRole } from "../../core/src/identity"; |
| 12 | import { requireCanonicalAccount } from "../src/runtime/account_auth"; | 13 | import { requireCanonicalAccount } from "../src/runtime/account_auth"; |
| @@ -22,6 +23,72 @@ import { jsonObjectValidator } from "../src/database/validators"; · 9 unmodified lines | |||
| 22 | import { customAction } from "../../core/src/function-builders"; | 23 | import { customAction } from "../../core/src/function-builders"; |
| 23 | import { encodeToolCallResultForTransport } from "../../core/src/tool-call-result-transport"; | 24 | import { encodeToolCallResultForTransport } from "../../core/src/tool-call-result-transport"; |
| 24 | import { previewOpenApiSourceUpgradeForContext, type OpenApiUpgradeDiffPreview } from "../src/runtime/tool_upgrade"; | 25 | import { previewOpenApiSourceUpgradeForContext, type OpenApiUpgradeDiffPreview } from "../src/runtime/tool_upgrade"; |
| 26 | import { getStorageProvider, type StorageEncoding, type StorageProvider } from "../src/runtime/storage_provider"; | ||
| 27 | |||
| 28 | function isReadOnlySql(sql: string): boolean { | ||
| 29 | const trimmed = sql.trim().toLowerCase(); | ||
| 30 | return trimmed.startsWith("select") | ||
| 31 | || trimmed.startsWith("pragma") | ||
| 32 | || trimmed.startsWith("explain") | ||
| 33 | || trimmed.startsWith("with"); | ||
| 34 | } | ||
| 35 | |||
| 36 | async function resolveStorageInspectorContext( | ||
| 37 | ctx: Parameters<typeof requireCanonicalAccount>[0], | ||
| 38 | args: { | ||
| 39 | workspaceId: Parameters<typeof requireCanonicalAccount>[1]["workspaceId"]; | ||
| 40 | accountId?: Parameters<typeof requireCanonicalAccount>[1]["accountId"]; | ||
| 41 | sessionId?: string; | ||
| 42 | instanceId: string; | ||
| 43 | }, | ||
| 44 | ): Promise<{ | ||
| 45 | accountId: Awaited<ReturnType<typeof requireCanonicalAccount>>["accountId"]; | ||
| 46 | instance: StorageInstanceRecord; | ||
| 47 | provider: StorageProvider; | ||
| 48 | }> { | ||
| 49 | const access = await requireCanonicalAccount(ctx, { | ||
| 50 | workspaceId: args.workspaceId, | ||
| 51 | sessionId: args.sessionId, | ||
| 52 | accountId: args.accountId, | ||
| 53 | }); | ||
| 54 | |||
| 55 | const instance = await ctx.runQuery(internal.database.getStorageInstance, { | ||
| 56 | workspaceId: args.workspaceId, | ||
| 57 | accountId: access.accountId, | ||
| 58 | instanceId: args.instanceId, | ||
| 59 | }) as StorageInstanceRecord | null; | ||
| 60 | |||
| 61 | if (!instance) { | ||
| 62 | throw new Error(`Storage instance not found or inaccessible: ${args.instanceId}`); | ||
| 63 | } | ||
| 64 | |||
| 65 | return { | ||
| 66 | accountId: access.accountId, | ||
| 67 | instance, | ||
| 68 | provider: getStorageProvider(instance.provider), | ||
| 69 | }; | ||
| 70 | } | ||
| 71 | |||
| 72 | async function touchStorageInstance( | ||
| 73 | ctx: Parameters<typeof requireCanonicalAccount>[0], | ||
| 74 | args: { | ||
| 75 | workspaceId: Parameters<typeof requireCanonicalAccount>[1]["workspaceId"]; | ||
| 76 | accountId: Awaited<ReturnType<typeof requireCanonicalAccount>>["accountId"]; | ||
| 77 | instance: StorageInstanceRecord; | ||
| 78 | provider: StorageProvider; | ||
| 79 | withUsage: boolean; | ||
| 80 | }, | ||
| 81 | ) { | ||
| 82 | const usage = args.withUsage ? await args.provider.usage(args.instance) : undefined; | ||
| 83 | await ctx.runMutation(internal.database.touchStorageInstance, { | ||
| 84 | workspaceId: args.workspaceId, | ||
| 85 | accountId: args.accountId, | ||
| 86 | instanceId: args.instance.id, | ||
| 87 | provider: args.instance.provider, | ||
| 88 | ...(usage?.sizeBytes !== undefined ? { sizeBytes: usage.sizeBytes } : {}), | ||
| 89 | ...(usage?.fileCount !== undefined ? { fileCount: usage.fileCount } : {}), | ||
| 90 | }); | ||
| 91 | } | ||
| 25 | 92 | ||
| 26 | export const listToolsWithWarnings = customAction({ | 93 | export const listToolsWithWarnings = customAction({ |
| 27 | method: "POST", | 94 | method: "POST", |
| @@ -177,6 +244,134 @@ export const previewOpenApiSourceUpgrade = customAction({ · 149 unmodified lines | |||
| 177 | }, | 244 | }, |
| 178 | }); | 245 | }); |
| 179 | 246 | ||
| 247 | export const storageListDirectory = customAction({ | ||
| 248 | method: "POST", | ||
| 249 | args: { | ||
| 250 | workspaceId: v.id("workspaces"), | ||
| 251 | accountId: v.optional(v.id("accounts")), | ||
| 252 | sessionId: v.optional(v.string()), | ||
| 253 | instanceId: v.string(), | ||
| 254 | path: v.optional(v.string()), | ||
| 255 | }, | ||
| 256 | handler: async (ctx, args) => { | ||
| 257 | const { accountId, instance, provider } = await resolveStorageInspectorContext(ctx, args); | ||
| 258 | const path = args.path?.trim() || "/"; | ||
| 259 | const entries = await provider.readdir(instance, path); | ||
| 260 | await touchStorageInstance(ctx, { | ||
| 261 | workspaceId: args.workspaceId, | ||
| 262 | accountId, | ||
| 263 | instance, | ||
| 264 | provider, | ||
| 265 | withUsage: false, | ||
| 266 | }); | ||
| 267 | return { | ||
| 268 | instanceId: instance.id, | ||
| 269 | path, | ||
| 270 | entries, | ||
| 271 | }; | ||
| 272 | }, | ||
| 273 | }); | ||
| 274 | |||
| 275 | export const storageReadFile = customAction({ | ||
| 276 | method: "POST", | ||
| 277 | args: { | ||
| 278 | workspaceId: v.id("workspaces"), | ||
| 279 | accountId: v.optional(v.id("accounts")), | ||
| 280 | sessionId: v.optional(v.string()), | ||
| 281 | instanceId: v.string(), | ||
| 282 | path: v.string(), | ||
| 283 | encoding: v.optional(v.union(v.literal("utf8"), v.literal("base64"))), | ||
| 284 | }, | ||
| 285 | handler: async (ctx, args) => { | ||
| 286 | const { accountId, instance, provider } = await resolveStorageInspectorContext(ctx, args); | ||
| 287 | const encoding = (args.encoding ?? "utf8") as StorageEncoding; | ||
| 288 | const file = await provider.readFile(instance, args.path, encoding); | ||
| 289 | await touchStorageInstance(ctx, { | ||
| 290 | workspaceId: args.workspaceId, | ||
| 291 | accountId, | ||
| 292 | instance, | ||
| 293 | provider, | ||
| 294 | withUsage: false, | ||
| 295 | }); | ||
| 296 | return { | ||
| 297 | instanceId: instance.id, | ||
| 298 | path: args.path, | ||
| 299 | encoding, | ||
| 300 | content: file.content, | ||
| 301 | bytes: file.bytes, | ||
| 302 | }; | ||
| 303 | }, | ||
| 304 | }); | ||
| 305 | |||
| 306 | export const storageListKv = customAction({ | ||
| 307 | method: "POST", | ||
| 308 | args: { | ||
| 309 | workspaceId: v.id("workspaces"), | ||
| 310 | accountId: v.optional(v.id("accounts")), | ||
| 311 | sessionId: v.optional(v.string()), | ||
| 312 | instanceId: v.string(), | ||
| 313 | prefix: v.optional(v.string()), | ||
| 314 | limit: v.optional(v.number()), | ||
| 315 | }, | ||
| 316 | handler: async (ctx, args) => { | ||
| 317 | const { accountId, instance, provider } = await resolveStorageInspectorContext(ctx, args); | ||
| 318 | const limit = Math.max(1, Math.min(500, Math.floor(args.limit ?? 100))); | ||
| 319 | const prefix = args.prefix?.trim() ?? ""; | ||
| 320 | const items = await provider.kvList(instance, prefix, limit); | ||
| 321 | await touchStorageInstance(ctx, { | ||
| 322 | workspaceId: args.workspaceId, | ||
| 323 | accountId, | ||
| 324 | instance, | ||
| 325 | provider, | ||
| 326 | withUsage: false, | ||
| 327 | }); | ||
| 328 | return { | ||
| 329 | instanceId: instance.id, | ||
| 330 | prefix, | ||
| 331 | items, | ||
| 332 | total: items.length, | ||
| 333 | }; | ||
| 334 | }, | ||
| 335 | }); | ||
| 336 | |||
| 337 | export const storageQuerySql = customAction({ | ||
| 338 | method: "POST", | ||
| 339 | args: { | ||
| 340 | workspaceId: v.id("workspaces"), | ||
| 341 | accountId: v.optional(v.id("accounts")), | ||
| 342 | sessionId: v.optional(v.string()), | ||
| 343 | instanceId: v.string(), | ||
| 344 | sql: v.string(), | ||
| 345 | params: v.optional(v.array(v.union(v.string(), v.number(), v.boolean(), v.null()))), | ||
| 346 | maxRows: v.optional(v.number()), | ||
| 347 | }, | ||
| 348 | handler: async (ctx, args) => { | ||
| 349 | if (!isReadOnlySql(args.sql)) { | ||
| 350 | throw new Error("Storage inspector only allows read-only SQL (SELECT/PRAGMA/EXPLAIN/WITH)"); | ||
| 351 | } | ||
| 352 | |||
| 353 | const { accountId, instance, provider } = await resolveStorageInspectorContext(ctx, args); | ||
| 354 | const maxRows = Math.max(1, Math.min(1000, Math.floor(args.maxRows ?? 200))); | ||
| 355 | const result = await provider.sqliteQuery(instance, { | ||
| 356 | sql: args.sql, | ||
| 357 | params: args.params ?? [], | ||
| 358 | mode: "read", | ||
| 359 | maxRows, | ||
| 360 | }); | ||
| 361 | await touchStorageInstance(ctx, { | ||
| 362 | workspaceId: args.workspaceId, | ||
| 363 | accountId, | ||
| 364 | instance, | ||
| 365 | provider, | ||
| 366 | withUsage: false, | ||
| 367 | }); | ||
| 368 | return { | ||
| 369 | instanceId: instance.id, | ||
| 370 | ...result, | ||
| 371 | }; | ||
| 372 | }, | ||
| 373 | }); | ||
| 374 | |||
| 180 | export const handleExternalToolCall = internalAction({ | 375 | export const handleExternalToolCall = internalAction({ |
| 181 | args: { | 376 | args: { |
| 182 | runId: v.string(), | 377 | runId: v.string(), |
| @@ -62,3 +62,9 @@ export const storageDurabilityValidator = v.union(v.literal("ephemeral"), v.lite · 61 unmodified lines | |||
| 62 | export const storageInstanceStatusValidator = v.union(v.literal("active"), v.literal("closed"), v.literal("deleted")); | 62 | export const storageInstanceStatusValidator = v.union(v.literal("active"), v.literal("closed"), v.literal("deleted")); |
| 63 | 63 | ||
| 64 | export const storageProviderValidator = v.union(v.literal("agentfs-local"), v.literal("agentfs-cloudflare")); | 64 | export const storageProviderValidator = v.union(v.literal("agentfs-local"), v.literal("agentfs-cloudflare")); |
| 65 | |||
| 66 | export const storageAccessTypeValidator = v.union( | ||
| 67 | v.literal("opened"), | ||
| 68 | v.literal("provided"), | ||
| 69 | v.literal("accessed"), | ||
| 70 | ); | ||
| @@ -24,12 +24,18 @@ import { · 23 unmodified lines | |||
| 24 | kvDeleteOutputJsonSchema, | 24 | kvDeleteOutputJsonSchema, |
| 25 | kvGetInputJsonSchema, | 25 | kvGetInputJsonSchema, |
| 26 | kvGetOutputJsonSchema, | 26 | kvGetOutputJsonSchema, |
| 27 | kvIncrInputJsonSchema, | ||
| 28 | kvIncrOutputJsonSchema, | ||
| 27 | kvListInputJsonSchema, | 29 | kvListInputJsonSchema, |
| 28 | kvListOutputJsonSchema, | 30 | kvListOutputJsonSchema, |
| 29 | kvSetInputJsonSchema, | 31 | kvSetInputJsonSchema, |
| 30 | kvSetOutputJsonSchema, | 32 | kvSetOutputJsonSchema, |
| 31 | sqliteQueryInputJsonSchema, | 33 | sqliteQueryInputJsonSchema, |
| 32 | sqliteQueryOutputJsonSchema, | 34 | sqliteQueryOutputJsonSchema, |
| 35 | sqliteCapabilitiesInputJsonSchema, | ||
| 36 | sqliteCapabilitiesOutputJsonSchema, | ||
| 37 | sqliteInsertRowsInputJsonSchema, | ||
| 38 | sqliteInsertRowsOutputJsonSchema, | ||
| 33 | storageCloseInputJsonSchema, | 39 | storageCloseInputJsonSchema, |
| 34 | storageCloseOutputJsonSchema, | 40 | storageCloseOutputJsonSchema, |
| 35 | storageDeleteInputJsonSchema, | 41 | storageDeleteInputJsonSchema, |
| @@ -94,7 +100,7 @@ registerSystemTool( · 58 unmodified lines | |||
| 94 | 100 | ||
| 95 | registerSystemTool( | 101 | registerSystemTool( |
| 96 | "storage.open", | 102 | "storage.open", |
| 97 | "Open an existing storage instance or create a new one when instanceId is omitted.", | 103 | "Open an existing storage instance or create a new one when instanceId is omitted. Saves a default instance for this task; pass instanceId explicitly when you need to reuse the same storage across separate tasks/runs.", |
| 98 | { | 104 | { |
| 99 | inputSchema: storageOpenInputJsonSchema, | 105 | inputSchema: storageOpenInputJsonSchema, |
| 100 | outputSchema: storageOpenOutputJsonSchema, | 106 | outputSchema: storageOpenOutputJsonSchema, |
| @@ -130,7 +136,7 @@ registerSystemTool( · 29 unmodified lines | |||
| 130 | 136 | ||
| 131 | registerSystemTool( | 137 | registerSystemTool( |
| 132 | "fs.read", | 138 | "fs.read", |
| 133 | "Read a file from a storage instance.", | 139 | "Read a file from a storage instance. Reuses the current default instance when instanceId is omitted.", |
| 134 | { | 140 | { |
| 135 | inputSchema: fsReadInputJsonSchema, | 141 | inputSchema: fsReadInputJsonSchema, |
| 136 | outputSchema: fsReadOutputJsonSchema, | 142 | outputSchema: fsReadOutputJsonSchema, |
| @@ -139,7 +145,7 @@ registerSystemTool( · 2 unmodified lines | |||
| 139 | 145 | ||
| 140 | registerSystemTool( | 146 | registerSystemTool( |
| 141 | "fs.write", | 147 | "fs.write", |
| 142 | "Write file contents into a storage instance.", | 148 | "Write file contents into a storage instance. Reuses the current default instance when instanceId is omitted.", |
| 143 | { | 149 | { |
| 144 | inputSchema: fsWriteInputJsonSchema, | 150 | inputSchema: fsWriteInputJsonSchema, |
| 145 | outputSchema: fsWriteOutputJsonSchema, | 151 | outputSchema: fsWriteOutputJsonSchema, |
| @@ -200,6 +206,33 @@ registerSystemTool( · 54 unmodified lines | |||
| 200 | }, | 206 | }, |
| 201 | ); | 207 | ); |
| 202 | 208 | ||
| 209 | registerSystemTool( | ||
| 210 | "kv.put", | ||
| 211 | "Alias for kv.set. Write a key-value entry into a storage instance.", | ||
| 212 | { | ||
| 213 | inputSchema: kvSetInputJsonSchema, | ||
| 214 | outputSchema: kvSetOutputJsonSchema, | ||
| 215 | }, | ||
| 216 | ); | ||
| 217 | |||
| 218 | registerSystemTool( | ||
| 219 | "kv.create", | ||
| 220 | "Alias for kv.set. Create a key-value entry in a storage instance.", | ||
| 221 | { | ||
| 222 | inputSchema: kvSetInputJsonSchema, | ||
| 223 | outputSchema: kvSetOutputJsonSchema, | ||
| 224 | }, | ||
| 225 | ); | ||
| 226 | |||
| 227 | registerSystemTool( | ||
| 228 | "kv.update", | ||
| 229 | "Alias for kv.set. Update a key-value entry in a storage instance.", | ||
| 230 | { | ||
| 231 | inputSchema: kvSetInputJsonSchema, | ||
| 232 | outputSchema: kvSetOutputJsonSchema, | ||
| 233 | }, | ||
| 234 | ); | ||
| 235 | |||
| 203 | registerSystemTool( | 236 | registerSystemTool( |
| 204 | "kv.list", | 237 | "kv.list", |
| 205 | "List key-value entries by prefix from a storage instance.", | 238 | "List key-value entries by prefix from a storage instance.", |
| @@ -209,6 +242,15 @@ registerSystemTool( · 3 unmodified lines | |||
| 209 | }, | 242 | }, |
| 210 | ); | 243 | ); |
| 211 | 244 | ||
| 245 | registerSystemTool( | ||
| 246 | "kv.keys", | ||
| 247 | "Alias for kv.list. List keys by prefix from a storage instance.", | ||
| 248 | { | ||
| 249 | inputSchema: kvListInputJsonSchema, | ||
| 250 | outputSchema: kvListOutputJsonSchema, | ||
| 251 | }, | ||
| 252 | ); | ||
| 253 | |||
| 212 | registerSystemTool( | 254 | registerSystemTool( |
| 213 | "kv.delete", | 255 | "kv.delete", |
| 214 | "Delete a key-value entry from a storage instance.", | 256 | "Delete a key-value entry from a storage instance.", |
| @@ -218,11 +260,101 @@ registerSystemTool( · 3 unmodified lines | |||
| 218 | }, | 260 | }, |
| 219 | ); | 261 | ); |
| 220 | 262 | ||
| 263 | registerSystemTool( | ||
| 264 | "kv.del", | ||
| 265 | "Alias for kv.delete. Delete a key-value entry from a storage instance.", | ||
| 266 | { | ||
| 267 | inputSchema: kvDeleteInputJsonSchema, | ||
| 268 | outputSchema: kvDeleteOutputJsonSchema, | ||
| 269 | }, | ||
| 270 | ); | ||
| 271 | |||
| 272 | registerSystemTool( | ||
| 273 | "kv.has", | ||
| 274 | "Alias for kv.get. Check whether a key exists in a storage instance.", | ||
| 275 | { | ||
| 276 | inputSchema: kvGetInputJsonSchema, | ||
| 277 | outputSchema: kvGetOutputJsonSchema, | ||
| 278 | }, | ||
| 279 | ); | ||
| 280 | |||
| 281 | registerSystemTool( | ||
| 282 | "kv.exists", | ||
| 283 | "Alias for kv.get. Check whether a key exists in a storage instance.", | ||
| 284 | { | ||
| 285 | inputSchema: kvGetInputJsonSchema, | ||
| 286 | outputSchema: kvGetOutputJsonSchema, | ||
| 287 | }, | ||
| 288 | ); | ||
| 289 | |||
| 290 | registerSystemTool( | ||
| 291 | "kv.value", | ||
| 292 | "Alias for kv.get. Read a key-value entry from a storage instance.", | ||
| 293 | { | ||
| 294 | inputSchema: kvGetInputJsonSchema, | ||
| 295 | outputSchema: kvGetOutputJsonSchema, | ||
| 296 | }, | ||
| 297 | ); | ||
| 298 | |||
| 299 | registerSystemTool( | ||
| 300 | "kv.incr", | ||
| 301 | "Increment a numeric key-value entry and return the updated value. Creates the key from initial when missing.", | ||
| 302 | { | ||
| 303 | inputSchema: kvIncrInputJsonSchema, | ||
| 304 | outputSchema: kvIncrOutputJsonSchema, | ||
| 305 | }, | ||
| 306 | ); | ||
| 307 | |||
| 308 | registerSystemTool( | ||
| 309 | "kv.decr", | ||
| 310 | "Decrement a numeric key-value entry and return the updated value. Creates the key from initial when missing.", | ||
| 311 | { | ||
| 312 | inputSchema: kvIncrInputJsonSchema, | ||
| 313 | outputSchema: kvIncrOutputJsonSchema, | ||
| 314 | }, | ||
| 315 | ); | ||
| 316 | |||
| 221 | registerSystemTool( | 317 | registerSystemTool( |
| 222 | "sqlite.query", | 318 | "sqlite.query", |
| 223 | "Run SQL queries against the storage instance database.", | 319 | "Run SQL against storage-backed SQLite. Use mode='write' for mutating SQL. For multi-task persistence, pass instanceId explicitly. For bulk inserts, chunk into smaller batches or use json_each(?) with a JSON payload to avoid SQLite bind-variable limits.", |
| 224 | { | 320 | { |
| 225 | inputSchema: sqliteQueryInputJsonSchema, | 321 | inputSchema: sqliteQueryInputJsonSchema, |
| 226 | outputSchema: sqliteQueryOutputJsonSchema, | 322 | outputSchema: sqliteQueryOutputJsonSchema, |
| 227 | }, | 323 | }, |
| 228 | ); | 324 | ); |
| 325 | |||
| 326 | registerSystemTool( | ||
| 327 | "sqlite.exec", | ||
| 328 | "Alias for sqlite.query. Run SQL against storage-backed SQLite with the same guidance on mode, instanceId reuse, and bulk inserts via chunking or json_each(?).", | ||
| 329 | { | ||
| 330 | inputSchema: sqliteQueryInputJsonSchema, | ||
| 331 | outputSchema: sqliteQueryOutputJsonSchema, | ||
| 332 | }, | ||
| 333 | ); | ||
| 334 | |||
| 335 | registerSystemTool( | ||
| 336 | "sqlite.capabilities", | ||
| 337 | "Return SQLite execution limits and import guidance for the active storage provider.", | ||
| 338 | { | ||
| 339 | inputSchema: sqliteCapabilitiesInputJsonSchema, | ||
| 340 | outputSchema: sqliteCapabilitiesOutputJsonSchema, | ||
| 341 | }, | ||
| 342 | ); | ||
| 343 | |||
| 344 | registerSystemTool( | ||
| 345 | "sqlite.insert_rows", | ||
| 346 | "Insert rows into a table with automatic chunking under bind-variable limits. Use for bulk imports instead of huge VALUES clauses.", | ||
| 347 | { | ||
| 348 | inputSchema: sqliteInsertRowsInputJsonSchema, | ||
| 349 | outputSchema: sqliteInsertRowsOutputJsonSchema, | ||
| 350 | }, | ||
| 351 | ); | ||
| 352 | |||
| 353 | registerSystemTool( | ||
| 354 | "sqlite.bulk_insert", | ||
| 355 | "Alias for sqlite.insert_rows. Bulk insert rows with automatic chunking.", | ||
| 356 | { | ||
| 357 | inputSchema: sqliteInsertRowsInputJsonSchema, | ||
| 358 | outputSchema: sqliteInsertRowsOutputJsonSchema, | ||
| 359 | }, | ||
| 360 | ); | ||
| @@ -351,15 +351,19 @@ class AgentFsLocalStorageProvider implements StorageProvider { · 350 unmodified lines | |||
| 351 | async kvList(instance: StorageInstanceRecord, prefix: string, limit: number): Promise<Array<{ key: string; value: unknown }>> { | 351 | async kvList(instance: StorageInstanceRecord, prefix: string, limit: number): Promise<Array<{ key: string; value: unknown }>> { |
| 352 | return await this.withDatabase(instance, false, async (db) => { | 352 | return await this.withDatabase(instance, false, async (db) => { |
| 353 | await this.ensureKvTable(db); | 353 | await this.ensureKvTable(db); |
| 354 | const escapedPrefix = prefix | ||
| 355 | .replaceAll("^", "^^") | ||
| 356 | .replaceAll("%", "^%") | ||
| 357 | .replaceAll("_", "^_"); | ||
| 354 | const rows = rowsFromSqlResult( | 358 | const rows = rowsFromSqlResult( |
| 355 | db.exec( | 359 | db.exec( |
| 356 | "SELECT key, value FROM kv_store WHERE key LIKE ? ESCAPE '\\\\' ORDER BY key LIMIT ?", | 360 | "SELECT key, value FROM kv_store WHERE key LIKE ? ESCAPE '^' ORDER BY key LIMIT ?", |
| 357 | [`${prefix.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`, limit], | 361 | [`${escapedPrefix}%`, limit], |
| 358 | ), | 362 | ), |
| 359 | ); | 363 | ); |
| 360 | return rows.map((row) => ({ | 364 | return rows.map((row) => ({ |
| 361 | key: String(row.key ?? ""), | 365 | key: String(row.key ?? ""), |
| 362 | value: typeof row.value === "string" ? JSON.parse(row.value) : undefined, | 366 | value: typeof row.value === "string" ? JSON.parse(row.value) : null, |
| 363 | })); | 367 | })); |
| 364 | }); | 368 | }); |
| 365 | } | 369 | } |
| @@ -553,6 +557,40 @@ let localProviderSingleton: AgentFsLocalStorageProvider | null = null; · 187 unmodified lines | |||
| 553 | let cloudflareProviderSingleton: CloudflareRemoteStorageProvider | null = null; | 557 | let cloudflareProviderSingleton: CloudflareRemoteStorageProvider | null = null; |
| 554 | let cloudflareStorageProbeStarted = false; | 558 | let cloudflareStorageProbeStarted = false; |
| 555 | 559 | ||
| 560 | function isHostedConvexHostname(hostname: string): boolean { | ||
| 561 | const normalized = hostname.trim().toLowerCase(); | ||
| 562 | return normalized.endsWith(".convex.cloud") || normalized.endsWith(".convex.site"); | ||
| 563 | } | ||
| 564 | |||
| 565 | function isHostedConvexDeployment(): boolean { | ||
| 566 | const candidates = [process.env.CONVEX_URL, process.env.CONVEX_SITE_URL] | ||
| 567 | .map((value) => value?.trim()) | ||
| 568 | .filter((value): value is string => Boolean(value)); | ||
| 569 | |||
| 570 | for (const candidate of candidates) { | ||
| 571 | try { | ||
| 572 | const url = new URL(candidate); | ||
| 573 | if (isHostedConvexHostname(url.hostname)) { | ||
| 574 | return true; | ||
| 575 | } | ||
| 576 | } catch { | ||
| 577 | continue; | ||
| 578 | } | ||
| 579 | } | ||
| 580 | |||
| 581 | return false; | ||
| 582 | } | ||
| 583 | |||
| 584 | function assertLocalProviderSupported() { | ||
| 585 | if (!isHostedConvexDeployment()) { | ||
| 586 | return; | ||
| 587 | } | ||
| 588 | |||
| 589 | throw new Error( | ||
| 590 | "agentfs-local is not supported on hosted Convex deployments because filesystem state is not shared across workers. Use AGENT_STORAGE_PROVIDER=agentfs-cloudflare, or run self-hosted Convex for agentfs-local.", | ||
| 591 | ); | ||
| 592 | } | ||
| 593 | |||
| 556 | function startCloudflareStorageProbe(endpoint: string, token: string) { | 594 | function startCloudflareStorageProbe(endpoint: string, token: string) { |
| 557 | if (cloudflareStorageProbeStarted) { | 595 | if (cloudflareStorageProbeStarted) { |
| 558 | return; | 596 | return; |
| @@ -594,6 +632,8 @@ function startCloudflareStorageProbe(endpoint: string, token: string) { · 35 unmodified lines | |||
| 594 | } | 632 | } |
| 595 | 633 | ||
| 596 | function getLocalProvider(): AgentFsLocalStorageProvider { | 634 | function getLocalProvider(): AgentFsLocalStorageProvider { |
| 635 | assertLocalProviderSupported(); | ||
| 636 | |||
| 597 | if (!localProviderSingleton) { | 637 | if (!localProviderSingleton) { |
| 598 | const baseDir = (process.env.AGENT_STORAGE_ROOT ?? "").trim() || "/tmp/executor-agentfs"; | 638 | const baseDir = (process.env.AGENT_STORAGE_ROOT ?? "").trim() || "/tmp/executor-agentfs"; |
| 599 | localProviderSingleton = new AgentFsLocalStorageProvider(baseDir); | 639 | localProviderSingleton = new AgentFsLocalStorageProvider(baseDir); |
| @@ -1,11 +1,64 @@ | |||
| 1 | import { z } from "zod"; | 1 | import { z } from "zod"; |
| 2 | 2 | ||
| 3 | type JsonSchema = Record<string, unknown>; | 3 | type JsonSchema = Record<string, unknown>; |
| 4 | type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }; | ||
| 5 | |||
| 6 | function isRecord(value: unknown): value is Record<string, unknown> { | ||
| 7 | return typeof value === "object" && value !== null && !Array.isArray(value); | ||
| 8 | } | ||
| 9 | |||
| 10 | function mergeSchemaWithFallback(schema: unknown, fallback: JsonSchema): JsonSchema { | ||
| 11 | if (!isRecord(schema)) { | ||
| 12 | return fallback; | ||
| 13 | } | ||
| 14 | |||
| 15 | const merged: JsonSchema = { ...schema }; | ||
| 16 | |||
| 17 | if (typeof fallback.description === "string" && typeof merged.description !== "string") { | ||
| 18 | merged.description = fallback.description; | ||
| 19 | } | ||
| 20 | |||
| 21 | const schemaProps = isRecord(merged.properties) ? { ...merged.properties } : {}; | ||
| 22 | const fallbackProps = isRecord(fallback.properties) ? fallback.properties : {}; | ||
| 23 | for (const [key, fallbackProp] of Object.entries(fallbackProps)) { | ||
| 24 | const currentProp = schemaProps[key]; | ||
| 25 | if (!isRecord(currentProp)) { | ||
| 26 | schemaProps[key] = fallbackProp; | ||
| 27 | continue; | ||
| 28 | } | ||
| 29 | |||
| 30 | if (isRecord(fallbackProp)) { | ||
| 31 | schemaProps[key] = { | ||
| 32 | ...fallbackProp, | ||
| 33 | ...currentProp, | ||
| 34 | ...(typeof fallbackProp.description === "string" && typeof currentProp.description !== "string" | ||
| 35 | ? { description: fallbackProp.description } | ||
| 36 | : {}), | ||
| 37 | }; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | if (Object.keys(schemaProps).length > 0) { | ||
| 41 | merged.properties = schemaProps; | ||
| 42 | } | ||
| 43 | |||
| 44 | if (!Array.isArray(merged.required) && Array.isArray(fallback.required)) { | ||
| 45 | merged.required = fallback.required; | ||
| 46 | } | ||
| 47 | if (!("additionalProperties" in merged) && "additionalProperties" in fallback) { | ||
| 48 | merged.additionalProperties = fallback.additionalProperties; | ||
| 49 | } | ||
| 50 | |||
| 51 | return merged; | ||
| 52 | } | ||
| 4 | 53 | ||
| 5 | function toJsonSchema(schema: z.ZodTypeAny, fallback: JsonSchema): JsonSchema { | 54 | function toJsonSchema(schema: z.ZodTypeAny, fallback: JsonSchema): JsonSchema { |
| 6 | const maybeToJsonSchema = (z as unknown as { toJSONSchema?: (value: z.ZodTypeAny) => unknown }).toJSONSchema; | 55 | const maybeToJsonSchema = (z as unknown as { toJSONSchema?: (value: z.ZodTypeAny) => unknown }).toJSONSchema; |
| 7 | if (typeof maybeToJsonSchema === "function") { | 56 | if (typeof maybeToJsonSchema === "function") { |
| 8 | return maybeToJsonSchema(schema) as JsonSchema; | 57 | try { |
| 58 | return mergeSchemaWithFallback(maybeToJsonSchema(schema), fallback); | ||
| 59 | } catch { | ||
| 60 | return fallback; | ||
| 61 | } | ||
| 9 | } | 62 | } |
| 10 | 63 | ||
| 11 | return fallback; | 64 | return fallback; |
| @@ -16,6 +69,15 @@ export const storageDurabilitySchema = z.enum(["ephemeral", "durable"]); · 4 unmodified lines | |||
| 16 | export const storageStatusSchema = z.enum(["active", "closed", "deleted"]); | 69 | export const storageStatusSchema = z.enum(["active", "closed", "deleted"]); |
| 17 | export const storageProviderSchema = z.enum(["agentfs-local", "agentfs-cloudflare"]); | 70 | export const storageProviderSchema = z.enum(["agentfs-local", "agentfs-cloudflare"]); |
| 18 | 71 | ||
| 72 | const jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() => z.union([ | ||
| 73 | z.string(), | ||
| 74 | z.number(), | ||
| 75 | z.boolean(), | ||
| 76 | z.null(), | ||
| 77 | z.array(jsonValueSchema), | ||
| 78 | z.record(jsonValueSchema), | ||
| 79 | ])); | ||
| 80 | |||
| 19 | export const storageInstanceSchema = z.object({ | 81 | export const storageInstanceSchema = z.object({ |
| 20 | id: z.string(), | 82 | id: z.string(), |
| 21 | scopeType: storageScopeSchema, | 83 | scopeType: storageScopeSchema, |
| @@ -165,12 +227,12 @@ export const kvGetOutputSchema = z.object({ · 143 unmodified lines | |||
| 165 | instanceId: z.string(), | 227 | instanceId: z.string(), |
| 166 | key: z.string(), | 228 | key: z.string(), |
| 167 | found: z.boolean(), | 229 | found: z.boolean(), |
| 168 | value: z.unknown().optional(), | 230 | value: jsonValueSchema.optional(), |
| 169 | }); | 231 | }); |
| 170 | 232 | ||
| 171 | export const kvSetInputSchema = fsAccessSchema.extend({ | 233 | export const kvSetInputSchema = fsAccessSchema.extend({ |
| 172 | key: z.string(), | 234 | key: z.string(), |
| 173 | value: z.unknown(), | 235 | value: jsonValueSchema, |
| 174 | }); | 236 | }); |
| 175 | 237 | ||
| 176 | export const kvSetOutputSchema = z.object({ | 238 | export const kvSetOutputSchema = z.object({ |
| @@ -186,7 +248,7 @@ export const kvListInputSchema = fsAccessSchema.extend({ · 9 unmodified lines | |||
| 186 | 248 | ||
| 187 | export const kvListOutputSchema = z.object({ | 249 | export const kvListOutputSchema = z.object({ |
| 188 | instanceId: z.string(), | 250 | instanceId: z.string(), |
| 189 | items: z.array(z.object({ key: z.string(), value: z.unknown() })), | 251 | items: z.array(z.object({ key: z.string(), value: jsonValueSchema })), |
| 190 | total: z.number(), | 252 | total: z.number(), |
| 191 | }); | 253 | }); |
| 192 | 254 | ||
| @@ -200,6 +262,20 @@ export const kvDeleteOutputSchema = z.object({ · 7 unmodified lines | |||
| 200 | ok: z.boolean(), | 262 | ok: z.boolean(), |
| 201 | }); | 263 | }); |
| 202 | 264 | ||
| 265 | export const kvIncrInputSchema = fsAccessSchema.extend({ | ||
| 266 | key: z.string(), | ||
| 267 | by: z.coerce.number().optional(), | ||
| 268 | initial: z.coerce.number().optional(), | ||
| 269 | }); | ||
| 270 | |||
| 271 | export const kvIncrOutputSchema = z.object({ | ||
| 272 | instanceId: z.string(), | ||
| 273 | key: z.string(), | ||
| 274 | by: z.number(), | ||
| 275 | previous: z.number(), | ||
| 276 | value: z.number(), | ||
| 277 | }); | ||
| 278 | |||
| 203 | export const sqliteQueryInputSchema = fsAccessSchema.extend({ | 279 | export const sqliteQueryInputSchema = fsAccessSchema.extend({ |
| 204 | sql: z.string(), | 280 | sql: z.string(), |
| 205 | params: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(), | 281 | params: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(), |
| @@ -210,11 +286,55 @@ export const sqliteQueryInputSchema = fsAccessSchema.extend({ · 4 unmodified lines | |||
| 210 | export const sqliteQueryOutputSchema = z.object({ | 286 | export const sqliteQueryOutputSchema = z.object({ |
| 211 | instanceId: z.string(), | 287 | instanceId: z.string(), |
| 212 | mode: z.enum(["read", "write"]), | 288 | mode: z.enum(["read", "write"]), |
| 213 | rows: z.array(z.record(z.unknown())).optional(), | 289 | rows: z.array(z.record(jsonValueSchema)).optional(), |
| 214 | rowCount: z.number(), | 290 | rowCount: z.number(), |
| 215 | changes: z.number().optional(), | 291 | changes: z.number().optional(), |
| 216 | }); | 292 | }); |
| 217 | 293 | ||
| 294 | const sqliteScalarSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); | ||
| 295 | |||
| 296 | export const sqliteCapabilitiesInputSchema = fsAccessSchema.extend({}); | ||
| 297 | |||
| 298 | export const sqliteCapabilitiesOutputSchema = z.object({ | ||
| 299 | instanceId: z.string(), | ||
| 300 | provider: storageProviderSchema, | ||
| 301 | maxBindVariables: z.number(), | ||
| 302 | supportsJsonEach: z.boolean(), | ||
| 303 | supportsInsertRowsTool: z.boolean(), | ||
| 304 | guidance: z.array(z.string()), | ||
| 305 | }); | ||
| 306 | |||
| 307 | export const sqliteInsertRowsInputSchema = fsAccessSchema.extend({ | ||
| 308 | table: z.string(), | ||
| 309 | columns: z.array(z.string()).min(1), | ||
| 310 | rows: z.array(z.array(sqliteScalarSchema)).min(1), | ||
| 311 | onConflict: z.enum(["none", "ignore", "replace"]).optional(), | ||
| 312 | chunkSize: z.coerce.number().optional(), | ||
| 313 | }); | ||
| 314 | |||
| 315 | export const sqliteInsertRowsOutputSchema = z.object({ | ||
| 316 | instanceId: z.string(), | ||
| 317 | table: z.string(), | ||
| 318 | columns: z.array(z.string()), | ||
| 319 | rowsReceived: z.number(), | ||
| 320 | rowsProcessed: z.number(), | ||
| 321 | chunkCount: z.number(), | ||
| 322 | rowsPerChunk: z.number(), | ||
| 323 | maxBindVariables: z.number(), | ||
| 324 | changes: z.number(), | ||
| 325 | }); | ||
| 326 | |||
| 327 | const jsonValueJsonSchema = { | ||
| 328 | oneOf: [ | ||
| 329 | { type: "string" }, | ||
| 330 | { type: "number" }, | ||
| 331 | { type: "boolean" }, | ||
| 332 | { type: "null" }, | ||
| 333 | { type: "array", items: {} }, | ||
| 334 | { type: "object", additionalProperties: {} }, | ||
| 335 | ], | ||
| 336 | }; | ||
| 337 | |||
| 218 | export const storageOpenInputJsonSchema = toJsonSchema(storageOpenInputSchema, { | 338 | export const storageOpenInputJsonSchema = toJsonSchema(storageOpenInputSchema, { |
| 219 | type: "object", | 339 | type: "object", |
| 220 | properties: { | 340 | properties: { |
| @@ -280,8 +400,15 @@ export const storageDeleteOutputJsonSchema = storageCloseOutputJsonSchema; · 59 unmodified lines | |||
| 280 | 400 | ||
| 281 | function fsAccessJsonProperties() { | 401 | function fsAccessJsonProperties() { |
| 282 | return { | 402 | return { |
| 283 | instanceId: { type: "string" }, | 403 | instanceId: { |
| 284 | scopeType: { type: "string", enum: ["scratch", "account", "workspace", "organization"] }, | 404 | type: "string", |
| 405 | description: "Storage instance identifier. Recommended for cross-task/cross-run persistence.", | ||
| 406 | }, | ||
| 407 | scopeType: { | ||
| 408 | type: "string", | ||
| 409 | enum: ["scratch", "account", "workspace", "organization"], | ||
| 410 | description: "Scope used to open/select a default instance when instanceId is omitted.", | ||
| 411 | }, | ||
| 285 | }; | 412 | }; |
| 286 | } | 413 | } |
| 287 | 414 | ||
| @@ -438,7 +565,7 @@ export const kvGetOutputJsonSchema = toJsonSchema(kvGetOutputSchema, { · 150 unmodified lines | |||
| 438 | instanceId: { type: "string" }, | 565 | instanceId: { type: "string" }, |
| 439 | key: { type: "string" }, | 566 | key: { type: "string" }, |
| 440 | found: { type: "boolean" }, | 567 | found: { type: "boolean" }, |
| 441 | value: {}, | 568 | value: jsonValueJsonSchema, |
| 442 | }, | 569 | }, |
| 443 | required: ["instanceId", "key", "found"], | 570 | required: ["instanceId", "key", "found"], |
| 444 | additionalProperties: false, | 571 | additionalProperties: false, |
| @@ -449,7 +576,7 @@ export const kvSetInputJsonSchema = toJsonSchema(kvSetInputSchema, { · 4 unmodified lines | |||
| 449 | properties: { | 576 | properties: { |
| 450 | ...fsAccessJsonProperties(), | 577 | ...fsAccessJsonProperties(), |
| 451 | key: { type: "string" }, | 578 | key: { type: "string" }, |
| 452 | value: {}, | 579 | value: jsonValueJsonSchema, |
| 453 | }, | 580 | }, |
| 454 | required: ["key", "value"], | 581 | required: ["key", "value"], |
| 455 | additionalProperties: false, | 582 | additionalProperties: false, |
| @@ -480,7 +607,18 @@ export const kvListOutputJsonSchema = toJsonSchema(kvListOutputSchema, { · 24 unmodified lines | |||
| 480 | type: "object", | 607 | type: "object", |
| 481 | properties: { | 608 | properties: { |
| 482 | instanceId: { type: "string" }, | 609 | instanceId: { type: "string" }, |
| 483 | items: { type: "array", items: { type: "object" } }, | 610 | items: { |
| 611 | type: "array", | ||
| 612 | items: { | ||
| 613 | type: "object", | ||
| 614 | properties: { | ||
| 615 | key: { type: "string" }, | ||
| 616 | value: jsonValueJsonSchema, | ||
| 617 | }, | ||
| 618 | required: ["key", "value"], | ||
| 619 | additionalProperties: false, | ||
| 620 | }, | ||
| 621 | }, | ||
| 484 | total: { type: "number" }, | 622 | total: { type: "number" }, |
| 485 | }, | 623 | }, |
| 486 | required: ["instanceId", "items", "total"], | 624 | required: ["instanceId", "items", "total"], |
| @@ -508,19 +646,52 @@ export const kvDeleteOutputJsonSchema = toJsonSchema(kvDeleteOutputSchema, { · 21 unmodified lines | |||
| 508 | additionalProperties: false, | 646 | additionalProperties: false, |
| 509 | }); | 647 | }); |
| 510 | 648 | ||
| 649 | export const kvIncrInputJsonSchema = toJsonSchema(kvIncrInputSchema, { | ||
| 650 | type: "object", | ||
| 651 | properties: { | ||
| 652 | ...fsAccessJsonProperties(), | ||
| 653 | key: { type: "string" }, | ||
| 654 | by: { type: "number" }, | ||
| 655 | initial: { type: "number" }, | ||
| 656 | }, | ||
| 657 | required: ["key"], | ||
| 658 | additionalProperties: false, | ||
| 659 | }); | ||
| 660 | |||
| 661 | export const kvIncrOutputJsonSchema = toJsonSchema(kvIncrOutputSchema, { | ||
| 662 | type: "object", | ||
| 663 | properties: { | ||
| 664 | instanceId: { type: "string" }, | ||
| 665 | key: { type: "string" }, | ||
| 666 | by: { type: "number" }, | ||
| 667 | previous: { type: "number" }, | ||
| 668 | value: { type: "number" }, | ||
| 669 | }, | ||
| 670 | required: ["instanceId", "key", "by", "previous", "value"], | ||
| 671 | additionalProperties: false, | ||
| 672 | }); | ||
| 673 | |||
| 511 | export const sqliteQueryInputJsonSchema = toJsonSchema(sqliteQueryInputSchema, { | 674 | export const sqliteQueryInputJsonSchema = toJsonSchema(sqliteQueryInputSchema, { |
| 512 | type: "object", | 675 | type: "object", |
| 676 | description: | ||
| 677 | "Execute SQL on a storage-backed SQLite database. Use instanceId to target the same database across separate tasks or sessions.", | ||
| 513 | properties: { | 678 | properties: { |
| 514 | ...fsAccessJsonProperties(), | 679 | ...fsAccessJsonProperties(), |
| 515 | sql: { type: "string" }, | 680 | sql: { type: "string", description: "SQL statement text to execute." }, |
| 516 | params: { | 681 | params: { |
| 517 | type: "array", | 682 | type: "array", |
| 683 | description: | ||
| 684 | "Positional SQL parameters. Keep per-call bind count modest (for large inserts, batch rows into smaller chunks or pass one JSON payload and expand with json_each(?)).", | ||
| 518 | items: { | 685 | items: { |
| 519 | oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }, { type: "null" }], | 686 | oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }, { type: "null" }], |
| 520 | }, | 687 | }, |
| 521 | }, | 688 | }, |
| 522 | mode: { type: "string", enum: ["read", "write"] }, | 689 | mode: { |
| 523 | maxRows: { type: "number" }, | 690 | type: "string", |
| 691 | enum: ["read", "write"], | ||
| 692 | description: "Use 'write' for CREATE/INSERT/UPDATE/DELETE statements.", | ||
| 693 | }, | ||
| 694 | maxRows: { type: "number", description: "Read mode row cap for result payload size." }, | ||
| 524 | }, | 695 | }, |
| 525 | required: ["sql"], | 696 | required: ["sql"], |
| 526 | additionalProperties: false, | 697 | additionalProperties: false, |
| @@ -531,10 +702,90 @@ export const sqliteQueryOutputJsonSchema = toJsonSchema(sqliteQueryOutputSchema, · 4 unmodified lines | |||
| 531 | properties: { | 702 | properties: { |
| 532 | instanceId: { type: "string" }, | 703 | instanceId: { type: "string" }, |
| 533 | mode: { type: "string", enum: ["read", "write"] }, | 704 | mode: { type: "string", enum: ["read", "write"] }, |
| 534 | rows: { type: "array", items: { type: "object" } }, | 705 | rows: { type: "array", items: { type: "object", additionalProperties: jsonValueJsonSchema } }, |
| 535 | rowCount: { type: "number" }, | 706 | rowCount: { type: "number" }, |
| 536 | changes: { type: "number" }, | 707 | changes: { type: "number" }, |
| 537 | }, | 708 | }, |
| 538 | required: ["instanceId", "mode", "rowCount"], | 709 | required: ["instanceId", "mode", "rowCount"], |
| 539 | additionalProperties: false, | 710 | additionalProperties: false, |
| 540 | }); | 711 | }); |
| 712 | |||
| 713 | export const sqliteCapabilitiesInputJsonSchema = toJsonSchema(sqliteCapabilitiesInputSchema, { | ||
| 714 | type: "object", | ||
| 715 | description: "Get provider-specific SQLite execution capabilities and batching guidance.", | ||
| 716 | properties: { | ||
| 717 | ...fsAccessJsonProperties(), | ||
| 718 | }, | ||
| 719 | additionalProperties: false, | ||
| 720 | }); | ||
| 721 | |||
| 722 | export const sqliteCapabilitiesOutputJsonSchema = toJsonSchema(sqliteCapabilitiesOutputSchema, { | ||
| 723 | type: "object", | ||
| 724 | properties: { | ||
| 725 | instanceId: { type: "string" }, | ||
| 726 | provider: { type: "string", enum: ["agentfs-local", "agentfs-cloudflare"] }, | ||
| 727 | maxBindVariables: { type: "number" }, | ||
| 728 | supportsJsonEach: { type: "boolean" }, | ||
| 729 | supportsInsertRowsTool: { type: "boolean" }, | ||
| 730 | guidance: { type: "array", items: { type: "string" } }, | ||
| 731 | }, | ||
| 732 | required: [ | ||
| 733 | "instanceId", | ||
| 734 | "provider", | ||
| 735 | "maxBindVariables", | ||
| 736 | "supportsJsonEach", | ||
| 737 | "supportsInsertRowsTool", | ||
| 738 | "guidance", | ||
| 739 | ], | ||
| 740 | additionalProperties: false, | ||
| 741 | }); | ||
| 742 | |||
| 743 | export const sqliteInsertRowsInputJsonSchema = toJsonSchema(sqliteInsertRowsInputSchema, { | ||
| 744 | type: "object", | ||
| 745 | description: | ||
| 746 | "Insert tabular rows with automatic chunking to stay under SQLite bind-variable limits. Prefer this over huge VALUES(...) statements.", | ||
| 747 | properties: { | ||
| 748 | ...fsAccessJsonProperties(), | ||
| 749 | table: { type: "string", description: "Target table name." }, | ||
| 750 | columns: { type: "array", items: { type: "string" }, minItems: 1 }, | ||
| 751 | rows: { | ||
| 752 | type: "array", | ||
| 753 | minItems: 1, | ||
| 754 | items: { | ||
| 755 | type: "array", | ||
| 756 | items: { oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }, { type: "null" }] }, | ||
| 757 | }, | ||
| 758 | }, | ||
| 759 | onConflict: { type: "string", enum: ["none", "ignore", "replace"] }, | ||
| 760 | chunkSize: { type: "number", description: "Optional max rows per insert chunk." }, | ||
| 761 | }, | ||
| 762 | required: ["table", "columns", "rows"], | ||
| 763 | additionalProperties: false, | ||
| 764 | }); | ||
| 765 | |||
| 766 | export const sqliteInsertRowsOutputJsonSchema = toJsonSchema(sqliteInsertRowsOutputSchema, { | ||
| 767 | type: "object", | ||
| 768 | properties: { | ||
| 769 | instanceId: { type: "string" }, | ||
| 770 | table: { type: "string" }, | ||
| 771 | columns: { type: "array", items: { type: "string" } }, | ||
| 772 | rowsReceived: { type: "number" }, | ||
| 773 | rowsProcessed: { type: "number" }, | ||
| 774 | chunkCount: { type: "number" }, | ||
| 775 | rowsPerChunk: { type: "number" }, | ||
| 776 | maxBindVariables: { type: "number" }, | ||
| 777 | changes: { type: "number" }, | ||
| 778 | }, | ||
| 779 | required: [ | ||
| 780 | "instanceId", | ||
| 781 | "table", | ||
| 782 | "columns", | ||
| 783 | "rowsReceived", | ||
| 784 | "rowsProcessed", | ||
| 785 | "chunkCount", | ||
| 786 | "rowsPerChunk", | ||
| 787 | "maxBindVariables", | ||
| 788 | "changes", | ||
| 789 | ], | ||
| 790 | additionalProperties: false, | ||
| 791 | }); | ||
| @@ -19,10 +19,16 @@ import { · 18 unmodified lines | |||
| 19 | kvDeleteOutputSchema, | 19 | kvDeleteOutputSchema, |
| 20 | kvGetInputSchema, | 20 | kvGetInputSchema, |
| 21 | kvGetOutputSchema, | 21 | kvGetOutputSchema, |
| 22 | kvIncrInputSchema, | ||
| 23 | kvIncrOutputSchema, | ||
| 22 | kvListInputSchema, | 24 | kvListInputSchema, |
| 23 | kvListOutputSchema, | 25 | kvListOutputSchema, |
| 24 | kvSetInputSchema, | 26 | kvSetInputSchema, |
| 25 | kvSetOutputSchema, | 27 | kvSetOutputSchema, |
| 28 | sqliteCapabilitiesInputSchema, | ||
| 29 | sqliteCapabilitiesOutputSchema, | ||
| 30 | sqliteInsertRowsInputSchema, | ||
| 31 | sqliteInsertRowsOutputSchema, | ||
| 26 | sqliteQueryInputSchema, | 32 | sqliteQueryInputSchema, |
| 27 | sqliteQueryOutputSchema, | 33 | sqliteQueryOutputSchema, |
| 28 | storageCloseInputSchema, | 34 | storageCloseInputSchema, |
| @@ -35,6 +41,14 @@ import { · 6 unmodified lines | |||
| 35 | storageOpenOutputSchema, | 41 | storageOpenOutputSchema, |
| 36 | } from "./storage_tool_contracts"; | 42 | } from "./storage_tool_contracts"; |
| 37 | 43 | ||
| 44 | type StorageScopeType = "scratch" | "account" | "workspace" | "organization"; | ||
| 45 | |||
| 46 | type TaskStorageDefaults = { | ||
| 47 | currentInstanceId?: string; | ||
| 48 | currentScopeType?: StorageScopeType; | ||
| 49 | byScope: Partial<Record<StorageScopeType, string>>; | ||
| 50 | }; | ||
| 51 | |||
| 38 | const STORAGE_SYSTEM_TOOLS = new Set([ | 52 | const STORAGE_SYSTEM_TOOLS = new Set([ |
| 39 | "storage.open", | 53 | "storage.open", |
| 40 | "storage.list", | 54 | "storage.list", |
| @@ -48,11 +62,27 @@ const STORAGE_SYSTEM_TOOLS = new Set([ · 7 unmodified lines | |||
| 48 | "fs.remove", | 62 | "fs.remove", |
| 49 | "kv.get", | 63 | "kv.get", |
| 50 | "kv.set", | 64 | "kv.set", |
| 65 | "kv.put", | ||
| 66 | "kv.create", | ||
| 67 | "kv.update", | ||
| 51 | "kv.list", | 68 | "kv.list", |
| 69 | "kv.keys", | ||
| 52 | "kv.delete", | 70 | "kv.delete", |
| 71 | "kv.del", | ||
| 72 | "kv.has", | ||
| 73 | "kv.exists", | ||
| 74 | "kv.value", | ||
| 75 | "kv.incr", | ||
| 76 | "kv.decr", | ||
| 53 | "sqlite.query", | 77 | "sqlite.query", |
| 78 | "sqlite.exec", | ||
| 79 | "sqlite.capabilities", | ||
| 80 | "sqlite.insert_rows", | ||
| 81 | "sqlite.bulk_insert", | ||
| 54 | ]); | 82 | ]); |
| 55 | 83 | ||
| 84 | const SQLITE_MAX_BIND_VARIABLES = 100; | ||
| 85 | |||
| 56 | function toInputRecord(input: unknown): Record<string, unknown> { | 86 | function toInputRecord(input: unknown): Record<string, unknown> { |
| 57 | if (!input || typeof input !== "object" || Array.isArray(input)) { | 87 | if (!input || typeof input !== "object" || Array.isArray(input)) { |
| 58 | return {}; | 88 | return {}; |
| @@ -70,6 +100,107 @@ function normalizeScopeType(value: unknown): undefined | "scratch" | "account" | · 11 unmodified lines | |||
| 70 | return undefined; | 100 | return undefined; |
| 71 | } | 101 | } |
| 72 | 102 | ||
| 103 | function isReadOnlySql(sql: string): boolean { | ||
| 104 | const trimmed = sql.trim().toLowerCase(); | ||
| 105 | return trimmed.startsWith("select") | ||
| 106 | || trimmed.startsWith("pragma") | ||
| 107 | || trimmed.startsWith("explain") | ||
| 108 | || trimmed.startsWith("with"); | ||
| 109 | } | ||
| 110 | |||
| 111 | function toErrorMessage(error: unknown): string { | ||
| 112 | if (error instanceof Error && typeof error.message === "string") { | ||
| 113 | return error.message; | ||
| 114 | } | ||
| 115 | return String(error); | ||
| 116 | } | ||
| 117 | |||
| 118 | function estimateSqlBindCount(sql: string): number { | ||
| 119 | let inSingleQuote = false; | ||
| 120 | let inDoubleQuote = false; | ||
| 121 | let count = 0; | ||
| 122 | |||
| 123 | for (let index = 0; index < sql.length; index += 1) { | ||
| 124 | const char = sql[index]; | ||
| 125 | if (char === "'" && !inDoubleQuote) { | ||
| 126 | inSingleQuote = !inSingleQuote; | ||
| 127 | continue; | ||
| 128 | } | ||
| 129 | if (char === '"' && !inSingleQuote) { | ||
| 130 | inDoubleQuote = !inDoubleQuote; | ||
| 131 | continue; | ||
| 132 | } | ||
| 133 | if (!inSingleQuote && !inDoubleQuote && char === "?") { | ||
| 134 | count += 1; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | return count; | ||
| 139 | } | ||
| 140 | |||
| 141 | function decorateSqliteError(error: unknown, args: { | ||
| 142 | sql: string; | ||
| 143 | params: Array<string | number | boolean | null>; | ||
| 144 | instanceId: string; | ||
| 145 | }): Error { | ||
| 146 | const original = toErrorMessage(error); | ||
| 147 | const normalized = original.toLowerCase(); | ||
| 148 | |||
| 149 | if (normalized.includes("too many sql variables")) { | ||
| 150 | const bindCount = estimateSqlBindCount(args.sql); | ||
| 151 | const paramsCount = args.params.length; | ||
| 152 | return new Error( | ||
| 153 | [ | ||
| 154 | original, | ||
| 155 | `sqlite.query guidance: this statement used ${paramsCount} params and ~${bindCount} '?' placeholders.`, | ||
| 156 | "Use smaller batches, or prefer JSON batching: INSERT ... SELECT ... FROM json_each(?) with one JSON payload param.", | ||
| 157 | "Keep using instanceId to write/read the same database across task runs.", | ||
| 158 | ].join(" "), | ||
| 159 | ); | ||
| 160 | } | ||
| 161 | |||
| 162 | if (normalized.includes("no such table")) { | ||
| 163 | return new Error( | ||
| 164 | [ | ||
| 165 | original, | ||
| 166 | `sqlite.query guidance: table lookup happened in instanceId=${args.instanceId}.`, | ||
| 167 | "If the table was created in another run, pass that exact instanceId explicitly.", | ||
| 168 | ].join(" "), | ||
| 169 | ); | ||
| 170 | } | ||
| 171 | |||
| 172 | if (normalized.includes("timed out") || normalized.includes("timeout")) { | ||
| 173 | return new Error( | ||
| 174 | [ | ||
| 175 | original, | ||
| 176 | "sqlite.query guidance: reduce batch size and split long imports into multiple calls.", | ||
| 177 | "For bulk inserts, use json_each(?) payload batches instead of very large VALUES(...) statements.", | ||
| 178 | ].join(" "), | ||
| 179 | ); | ||
| 180 | } | ||
| 181 | |||
| 182 | return error instanceof Error ? error : new Error(original); | ||
| 183 | } | ||
| 184 | |||
| 185 | function assertSqlIdentifier(identifier: string, label: string): string { | ||
| 186 | const trimmed = identifier.trim(); | ||
| 187 | if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(trimmed)) { | ||
| 188 | throw new Error(`${label} must be a valid SQLite identifier`); | ||
| 189 | } | ||
| 190 | return trimmed; | ||
| 191 | } | ||
| 192 | |||
| 193 | function quoteSqlIdentifier(identifier: string): string { | ||
| 194 | return `"${identifier.replaceAll('"', '""')}"`; | ||
| 195 | } | ||
| 196 | |||
| 197 | function assertFiniteNumber(value: unknown, label: string): number { | ||
| 198 | if (typeof value !== "number" || !Number.isFinite(value)) { | ||
| 199 | throw new Error(`${label} must be a finite number`); | ||
| 200 | } | ||
| 201 | return value; | ||
| 202 | } | ||
| 203 | |||
| 73 | function normalizeInstanceId(value: unknown): string | undefined { | 204 | function normalizeInstanceId(value: unknown): string | undefined { |
| 74 | if (typeof value !== "string") return undefined; | 205 | if (typeof value !== "string") return undefined; |
| 75 | const normalized = value.trim(); | 206 | const normalized = value.trim(); |
| @@ -85,6 +216,84 @@ function normalizeInputPayload(input: unknown): Record<string, unknown> { · 9 unmodified lines | |||
| 85 | }; | 216 | }; |
| 86 | } | 217 | } |
| 87 | 218 | ||
| 219 | function asRecord(value: unknown): Record<string, unknown> { | ||
| 220 | return value && typeof value === "object" && !Array.isArray(value) | ||
| 221 | ? value as Record<string, unknown> | ||
| 222 | : {}; | ||
| 223 | } | ||
| 224 | |||
| 225 | function parseTaskStorageDefaults(metadata: unknown): TaskStorageDefaults { | ||
| 226 | const root = asRecord(metadata); | ||
| 227 | const storage = asRecord(root.storage); | ||
| 228 | const byScopeRaw = asRecord(storage.defaultInstanceByScope); | ||
| 229 | const byScope: Partial<Record<StorageScopeType, string>> = {}; | ||
| 230 | |||
| 231 | for (const scopeType of ["scratch", "account", "workspace", "organization"] as const) { | ||
| 232 | const value = normalizeInstanceId(byScopeRaw[scopeType]); | ||
| 233 | if (value) { | ||
| 234 | byScope[scopeType] = value; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | return { | ||
| 239 | currentInstanceId: normalizeInstanceId(storage.currentInstanceId), | ||
| 240 | currentScopeType: normalizeScopeType(storage.currentScopeType), | ||
| 241 | byScope, | ||
| 242 | }; | ||
| 243 | } | ||
| 244 | |||
| 245 | async function getTaskStorageDefaults( | ||
| 246 | ctx: ActionCtx, | ||
| 247 | task: TaskRecord, | ||
| 248 | ): Promise<TaskStorageDefaults> { | ||
| 249 | const latest = await ctx.runQuery(internal.database.getTask, { taskId: task.id }); | ||
| 250 | if (!latest) { | ||
| 251 | return { byScope: {} }; | ||
| 252 | } | ||
| 253 | |||
| 254 | return parseTaskStorageDefaults((latest as TaskRecord).metadata); | ||
| 255 | } | ||
| 256 | |||
| 257 | async function saveTaskStorageDefault( | ||
| 258 | ctx: ActionCtx, | ||
| 259 | task: TaskRecord, | ||
| 260 | scopeType: StorageScopeType, | ||
| 261 | instanceId: string, | ||
| 262 | setCurrent = true, | ||
| 263 | accessType: "opened" | "provided" | "accessed" = "accessed", | ||
| 264 | ) { | ||
| 265 | await ctx.runMutation(internal.database.setTaskStorageDefaultInstance, { | ||
| 266 | taskId: task.id, | ||
| 267 | scopeType, | ||
| 268 | instanceId, | ||
| 269 | setCurrent, | ||
| 270 | }); | ||
| 271 | |||
| 272 | await ctx.runMutation(internal.database.trackTaskStorageAccess, { | ||
| 273 | taskId: task.id, | ||
| 274 | instanceId, | ||
| 275 | scopeType, | ||
| 276 | accessType, | ||
| 277 | }); | ||
| 278 | } | ||
| 279 | |||
| 280 | async function trackTaskStorageAccess( | ||
| 281 | ctx: ActionCtx, | ||
| 282 | task: TaskRecord, | ||
| 283 | args: { | ||
| 284 | instanceId: string; | ||
| 285 | scopeType?: StorageScopeType; | ||
| 286 | accessType: "opened" | "provided" | "accessed"; | ||
| 287 | }, | ||
| 288 | ) { | ||
| 289 | await ctx.runMutation(internal.database.trackTaskStorageAccess, { | ||
| 290 | taskId: task.id, | ||
| 291 | instanceId: args.instanceId, | ||
| 292 | scopeType: args.scopeType, | ||
| 293 | accessType: args.accessType, | ||
| 294 | }); | ||
| 295 | } | ||
| 296 | |||
| 88 | async function openStorageInstanceForTask( | 297 | async function openStorageInstanceForTask( |
| 89 | ctx: ActionCtx, | 298 | ctx: ActionCtx, |
| 90 | task: TaskRecord, | 299 | task: TaskRecord, |
| @@ -114,7 +323,7 @@ async function resolveStorageInstance( · 23 unmodified lines | |||
| 114 | payload: Record<string, unknown>, | 323 | payload: Record<string, unknown>, |
| 115 | ): Promise<StorageInstanceRecord> { | 324 | ): Promise<StorageInstanceRecord> { |
| 116 | const requestedInstanceId = normalizeInstanceId(payload.instanceId); | 325 | const requestedInstanceId = normalizeInstanceId(payload.instanceId); |
| 117 | const scopeType = normalizeScopeType(payload.scopeType); | 326 | const requestedScopeType = normalizeScopeType(payload.scopeType); |
| 118 | 327 | ||
| 119 | if (requestedInstanceId) { | 328 | if (requestedInstanceId) { |
| 120 | const existing = await ctx.runQuery(internal.database.getStorageInstance, { | 329 | const existing = await ctx.runQuery(internal.database.getStorageInstance, { |
| @@ -129,13 +338,55 @@ async function resolveStorageInstance( · 8 unmodified lines | |||
| 129 | const reopened = await openStorageInstanceForTask(ctx, task, { | 338 | const reopened = await openStorageInstanceForTask(ctx, task, { |
| 130 | instanceId: requestedInstanceId, | 339 | instanceId: requestedInstanceId, |
| 131 | }); | 340 | }); |
| 341 | await saveTaskStorageDefault(ctx, task, reopened.scopeType, reopened.id, true, "provided"); | ||
| 132 | return reopened; | 342 | return reopened; |
| 133 | } | 343 | } |
| 134 | 344 | ||
| 135 | return await openStorageInstanceForTask(ctx, task, { | 345 | const defaults = await getTaskStorageDefaults(ctx, task); |
| 136 | scopeType, | 346 | |
| 137 | purpose: "auto", | 347 | const candidateIds: string[] = []; |
| 348 | if (requestedScopeType) { | ||
| 349 | const forScope = defaults.byScope[requestedScopeType]; | ||
| 350 | if (forScope) { | ||
| 351 | candidateIds.push(forScope); | ||
| 352 | } | ||
| 353 | if (defaults.currentScopeType === requestedScopeType && defaults.currentInstanceId) { | ||
| 354 | candidateIds.push(defaults.currentInstanceId); | ||
| 355 | } | ||
| 356 | } else { | ||
| 357 | if (defaults.currentInstanceId) { | ||
| 358 | candidateIds.push(defaults.currentInstanceId); | ||
| 359 | } | ||
| 360 | if (defaults.byScope.scratch) { | ||
| 361 | candidateIds.push(defaults.byScope.scratch); | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | const uniqueCandidateIds = [...new Set(candidateIds)]; | ||
| 366 | for (const candidateId of uniqueCandidateIds) { | ||
| 367 | const existing = await ctx.runQuery(internal.database.getStorageInstance, { | ||
| 368 | workspaceId: task.workspaceId, | ||
| 369 | accountId: task.accountId, | ||
| 370 | instanceId: candidateId, | ||
| 371 | }) as StorageInstanceRecord | null; | ||
| 372 | if (!existing) { | ||
| 373 | continue; | ||
| 374 | } | ||
| 375 | |||
| 376 | const reopened = await openStorageInstanceForTask(ctx, task, { | ||
| 377 | instanceId: candidateId, | ||
| 378 | }); | ||
| 379 | await saveTaskStorageDefault(ctx, task, reopened.scopeType, reopened.id, true, "accessed"); | ||
| 380 | return reopened; | ||
| 381 | } | ||
| 382 | |||
| 383 | const fallbackScopeType = requestedScopeType ?? defaults.currentScopeType ?? "scratch"; | ||
| 384 | |||
| 385 | const created = await openStorageInstanceForTask(ctx, task, { | ||
| 386 | scopeType: fallbackScopeType, | ||
| 138 | }); | 387 | }); |
| 388 | await saveTaskStorageDefault(ctx, task, created.scopeType, created.id, true, "opened"); | ||
| 389 | return created; | ||
| 139 | } | 390 | } |
| 140 | 391 | ||
| 141 | async function touchInstance( | 392 | async function touchInstance( |
| @@ -167,14 +418,43 @@ export async function runStorageSystemTool( · 25 unmodified lines | |||
| 167 | input: unknown, | 418 | input: unknown, |
| 168 | ): Promise<unknown> { | 419 | ): Promise<unknown> { |
| 169 | const payload = normalizeInputPayload(input); | 420 | const payload = normalizeInputPayload(input); |
| 170 | 421 | const normalizedToolPath = toolPath === "kv.put" | |
| 171 | if (toolPath === "storage.open") { | 422 | ? "kv.set" |
| 423 | : toolPath === "kv.create" | ||
| 424 | ? "kv.set" | ||
| 425 | : toolPath === "kv.update" | ||
| 426 | ? "kv.set" | ||
| 427 | : toolPath === "kv.del" | ||
| 428 | ? "kv.delete" | ||
| 429 | : toolPath === "kv.has" | ||
| 430 | ? "kv.get" | ||
| 431 | : toolPath === "kv.exists" | ||
| 432 | ? "kv.get" | ||
| 433 | : toolPath === "kv.value" | ||
| 434 | ? "kv.get" | ||
| 435 | : toolPath === "kv.keys" | ||
| 436 | ? "kv.list" | ||
| 437 | : toolPath === "sqlite.exec" | ||
| 438 | ? "sqlite.query" | ||
| 439 | : toolPath === "sqlite.bulk_insert" | ||
| 440 | ? "sqlite.insert_rows" | ||
| 441 | : toolPath; | ||
| 442 | |||
| 443 | if (normalizedToolPath === "storage.open") { | ||
| 172 | const parsed = storageOpenInputSchema.parse(payload); | 444 | const parsed = storageOpenInputSchema.parse(payload); |
| 173 | const instance = await openStorageInstanceForTask(ctx, task, parsed); | 445 | const instance = await openStorageInstanceForTask(ctx, task, parsed); |
| 446 | await saveTaskStorageDefault( | ||
| 447 | ctx, | ||
| 448 | task, | ||
| 449 | instance.scopeType, | ||
| 450 | instance.id, | ||
| 451 | true, | ||
| 452 | parsed.instanceId ? "provided" : "opened", | ||
| 453 | ); | ||
| 174 | return storageOpenOutputSchema.parse({ instance }); | 454 | return storageOpenOutputSchema.parse({ instance }); |
| 175 | } | 455 | } |
| 176 | 456 | ||
| 177 | if (toolPath === "storage.list") { | 457 | if (normalizedToolPath === "storage.list") { |
| 178 | const parsed = storageListInputSchema.parse(payload); | 458 | const parsed = storageListInputSchema.parse(payload); |
| 179 | const instances = await ctx.runQuery(internal.database.listStorageInstances, { | 459 | const instances = await ctx.runQuery(internal.database.listStorageInstances, { |
| 180 | workspaceId: task.workspaceId, | 460 | workspaceId: task.workspaceId, |
| @@ -189,17 +469,24 @@ export async function runStorageSystemTool( · 8 unmodified lines | |||
| 189 | }); | 469 | }); |
| 190 | } | 470 | } |
| 191 | 471 | ||
| 192 | if (toolPath === "storage.close") { | 472 | if (normalizedToolPath === "storage.close") { |
| 193 | const parsed = storageCloseInputSchema.parse(payload); | 473 | const parsed = storageCloseInputSchema.parse(payload); |
| 194 | const instance = await ctx.runMutation(internal.database.closeStorageInstance, { | 474 | const instance = await ctx.runMutation(internal.database.closeStorageInstance, { |
| 195 | workspaceId: task.workspaceId, | 475 | workspaceId: task.workspaceId, |
| 196 | accountId: task.accountId, | 476 | accountId: task.accountId, |
| 197 | instanceId: parsed.instanceId, | 477 | instanceId: parsed.instanceId, |
| 198 | }); | 478 | }); |
| 479 | |||
| 480 | await trackTaskStorageAccess(ctx, task, { | ||
| 481 | instanceId: parsed.instanceId, | ||
| 482 | scopeType: instance?.scopeType, | ||
| 483 | accessType: "provided", | ||
| 484 | }); | ||
| 485 | |||
| 199 | return storageCloseOutputSchema.parse({ instance }); | 486 | return storageCloseOutputSchema.parse({ instance }); |
| 200 | } | 487 | } |
| 201 | 488 | ||
| 202 | if (toolPath === "storage.delete") { | 489 | if (normalizedToolPath === "storage.delete") { |
| 203 | const parsed = storageDeleteInputSchema.parse(payload); | 490 | const parsed = storageDeleteInputSchema.parse(payload); |
| 204 | const existing = await ctx.runQuery(internal.database.getStorageInstance, { | 491 | const existing = await ctx.runQuery(internal.database.getStorageInstance, { |
| 205 | workspaceId: task.workspaceId, | 492 | workspaceId: task.workspaceId, |
| @@ -221,10 +508,17 @@ export async function runStorageSystemTool( · 15 unmodified lines | |||
| 221 | accountId: task.accountId, | 508 | accountId: task.accountId, |
| 222 | instanceId: parsed.instanceId, | 509 | instanceId: parsed.instanceId, |
| 223 | }); | 510 | }); |
| 511 | |||
| 512 | await trackTaskStorageAccess(ctx, task, { | ||
| 513 | instanceId: parsed.instanceId, | ||
| 514 | scopeType: existing?.scopeType, | ||
| 515 | accessType: "provided", | ||
| 516 | }); | ||
| 517 | |||
| 224 | return storageDeleteOutputSchema.parse({ instance }); | 518 | return storageDeleteOutputSchema.parse({ instance }); |
| 225 | } | 519 | } |
| 226 | 520 | ||
| 227 | if (toolPath === "fs.read") { | 521 | if (normalizedToolPath === "fs.read") { |
| 228 | const parsed = fsReadInputSchema.parse(payload); | 522 | const parsed = fsReadInputSchema.parse(payload); |
| 229 | const instance = await resolveStorageInstance(ctx, task, payload); | 523 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 230 | const provider = getStorageProvider(instance.provider); | 524 | const provider = getStorageProvider(instance.provider); |
| @@ -240,7 +534,7 @@ export async function runStorageSystemTool( · 9 unmodified lines | |||
| 240 | }); | 534 | }); |
| 241 | } | 535 | } |
| 242 | 536 | ||
| 243 | if (toolPath === "fs.write") { | 537 | if (normalizedToolPath === "fs.write") { |
| 244 | const parsed = fsWriteInputSchema.parse(payload); | 538 | const parsed = fsWriteInputSchema.parse(payload); |
| 245 | const instance = await resolveStorageInstance(ctx, task, payload); | 539 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 246 | const provider = getStorageProvider(instance.provider); | 540 | const provider = getStorageProvider(instance.provider); |
| @@ -254,7 +548,7 @@ export async function runStorageSystemTool( · 7 unmodified lines | |||
| 254 | }); | 548 | }); |
| 255 | } | 549 | } |
| 256 | 550 | ||
| 257 | if (toolPath === "fs.readdir") { | 551 | if (normalizedToolPath === "fs.readdir") { |
| 258 | const parsed = fsReaddirInputSchema.parse(payload); | 552 | const parsed = fsReaddirInputSchema.parse(payload); |
| 259 | const instance = await resolveStorageInstance(ctx, task, payload); | 553 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 260 | const provider = getStorageProvider(instance.provider); | 554 | const provider = getStorageProvider(instance.provider); |
| @@ -268,7 +562,7 @@ export async function runStorageSystemTool( · 7 unmodified lines | |||
| 268 | }); | 562 | }); |
| 269 | } | 563 | } |
| 270 | 564 | ||
| 271 | if (toolPath === "fs.stat") { | 565 | if (normalizedToolPath === "fs.stat") { |
| 272 | const parsed = fsStatInputSchema.parse(payload); | 566 | const parsed = fsStatInputSchema.parse(payload); |
| 273 | const instance = await resolveStorageInstance(ctx, task, payload); | 567 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 274 | const provider = getStorageProvider(instance.provider); | 568 | const provider = getStorageProvider(instance.provider); |
| @@ -281,7 +575,7 @@ export async function runStorageSystemTool( · 6 unmodified lines | |||
| 281 | }); | 575 | }); |
| 282 | } | 576 | } |
| 283 | 577 | ||
| 284 | if (toolPath === "fs.mkdir") { | 578 | if (normalizedToolPath === "fs.mkdir") { |
| 285 | const parsed = fsMkdirInputSchema.parse(payload); | 579 | const parsed = fsMkdirInputSchema.parse(payload); |
| 286 | const instance = await resolveStorageInstance(ctx, task, payload); | 580 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 287 | const provider = getStorageProvider(instance.provider); | 581 | const provider = getStorageProvider(instance.provider); |
| @@ -294,7 +588,7 @@ export async function runStorageSystemTool( · 6 unmodified lines | |||
| 294 | }); | 588 | }); |
| 295 | } | 589 | } |
| 296 | 590 | ||
| 297 | if (toolPath === "fs.remove") { | 591 | if (normalizedToolPath === "fs.remove") { |
| 298 | const parsed = fsRemoveInputSchema.parse(payload); | 592 | const parsed = fsRemoveInputSchema.parse(payload); |
| 299 | const instance = await resolveStorageInstance(ctx, task, payload); | 593 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 300 | const provider = getStorageProvider(instance.provider); | 594 | const provider = getStorageProvider(instance.provider); |
| @@ -310,7 +604,7 @@ export async function runStorageSystemTool( · 9 unmodified lines | |||
| 310 | }); | 604 | }); |
| 311 | } | 605 | } |
| 312 | 606 | ||
| 313 | if (toolPath === "kv.get") { | 607 | if (normalizedToolPath === "kv.get") { |
| 314 | const parsed = kvGetInputSchema.parse(payload); | 608 | const parsed = kvGetInputSchema.parse(payload); |
| 315 | const instance = await resolveStorageInstance(ctx, task, payload); | 609 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 316 | const provider = getStorageProvider(instance.provider); | 610 | const provider = getStorageProvider(instance.provider); |
| @@ -324,7 +618,7 @@ export async function runStorageSystemTool( · 7 unmodified lines | |||
| 324 | }); | 618 | }); |
| 325 | } | 619 | } |
| 326 | 620 | ||
| 327 | if (toolPath === "kv.set") { | 621 | if (normalizedToolPath === "kv.set") { |
| 328 | const parsed = kvSetInputSchema.parse(payload); | 622 | const parsed = kvSetInputSchema.parse(payload); |
| 329 | const instance = await resolveStorageInstance(ctx, task, payload); | 623 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 330 | const provider = getStorageProvider(instance.provider); | 624 | const provider = getStorageProvider(instance.provider); |
| @@ -337,7 +631,7 @@ export async function runStorageSystemTool( · 6 unmodified lines | |||
| 337 | }); | 631 | }); |
| 338 | } | 632 | } |
| 339 | 633 | ||
| 340 | if (toolPath === "kv.list") { | 634 | if (normalizedToolPath === "kv.list") { |
| 341 | const parsed = kvListInputSchema.parse(payload); | 635 | const parsed = kvListInputSchema.parse(payload); |
| 342 | const instance = await resolveStorageInstance(ctx, task, payload); | 636 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 343 | const provider = getStorageProvider(instance.provider); | 637 | const provider = getStorageProvider(instance.provider); |
| @@ -351,7 +645,7 @@ export async function runStorageSystemTool( · 7 unmodified lines | |||
| 351 | }); | 645 | }); |
| 352 | } | 646 | } |
| 353 | 647 | ||
| 354 | if (toolPath === "kv.delete") { | 648 | if (normalizedToolPath === "kv.delete") { |
| 355 | const parsed = kvDeleteInputSchema.parse(payload); | 649 | const parsed = kvDeleteInputSchema.parse(payload); |
| 356 | const instance = await resolveStorageInstance(ctx, task, payload); | 650 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 357 | const provider = getStorageProvider(instance.provider); | 651 | const provider = getStorageProvider(instance.provider); |
| @@ -364,18 +658,151 @@ export async function runStorageSystemTool( · 6 unmodified lines | |||
| 364 | }); | 658 | }); |
| 365 | } | 659 | } |
| 366 | 660 | ||
| 367 | if (toolPath === "sqlite.query") { | 661 | if (normalizedToolPath === "kv.incr" || normalizedToolPath === "kv.decr") { |
| 662 | const parsed = kvIncrInputSchema.parse(payload); | ||
| 663 | const instance = await resolveStorageInstance(ctx, task, payload); | ||
| 664 | const provider = getStorageProvider(instance.provider); | ||
| 665 | |||
| 666 | const existing = await provider.kvGet(instance, parsed.key); | ||
| 667 | const initial = assertFiniteNumber(parsed.initial ?? 0, "initial"); | ||
| 668 | const previous = existing === undefined | ||
| 669 | ? initial | ||
| 670 | : assertFiniteNumber(existing, `kv value at '${parsed.key}'`); | ||
| 671 | |||
| 672 | const rawBy = assertFiniteNumber(parsed.by ?? 1, "by"); | ||
| 673 | const by = normalizedToolPath === "kv.decr" ? -Math.abs(rawBy) : rawBy; | ||
| 674 | const value = previous + by; | ||
| 675 | |||
| 676 | await provider.kvSet(instance, parsed.key, value); | ||
| 677 | await touchInstance(ctx, task, instance, provider, true); | ||
| 678 | |||
| 679 | return kvIncrOutputSchema.parse({ | ||
| 680 | instanceId: instance.id, | ||
| 681 | key: parsed.key, | ||
| 682 | by, | ||
| 683 | previous, | ||
| 684 | value, | ||
| 685 | }); | ||
| 686 | } | ||
| 687 | |||
| 688 | if (normalizedToolPath === "sqlite.capabilities") { | ||
| 689 | sqliteCapabilitiesInputSchema.parse(payload); | ||
| 690 | const instance = await resolveStorageInstance(ctx, task, payload); | ||
| 691 | const provider = getStorageProvider(instance.provider); | ||
| 692 | await touchInstance(ctx, task, instance, provider, false); | ||
| 693 | return sqliteCapabilitiesOutputSchema.parse({ | ||
| 694 | instanceId: instance.id, | ||
| 695 | provider: instance.provider, | ||
| 696 | maxBindVariables: SQLITE_MAX_BIND_VARIABLES, | ||
| 697 | supportsJsonEach: true, | ||
| 698 | supportsInsertRowsTool: true, | ||
| 699 | guidance: [ | ||
| 700 | "Prefer sqlite.insert_rows for bulk tabular inserts.", | ||
| 701 | "Keep bind params per statement under maxBindVariables.", | ||
| 702 | "For very large payloads, use one JSON payload param and expand with json_each(?).", | ||
| 703 | "Pass instanceId explicitly to reuse the same database across task runs.", | ||
| 704 | ], | ||
| 705 | }); | ||
| 706 | } | ||
| 707 | |||
| 708 | if (normalizedToolPath === "sqlite.insert_rows") { | ||
| 709 | const parsed = sqliteInsertRowsInputSchema.parse(payload); | ||
| 710 | const instance = await resolveStorageInstance(ctx, task, payload); | ||
| 711 | const provider = getStorageProvider(instance.provider); | ||
| 712 | |||
| 713 | const table = assertSqlIdentifier(parsed.table, "table"); | ||
| 714 | const columns = parsed.columns.map((column, index) => assertSqlIdentifier(column, `columns[${index}]`)); | ||
| 715 | if (new Set(columns).size !== columns.length) { | ||
| 716 | throw new Error("columns must be unique"); | ||
| 717 | } | ||
| 718 | |||
| 719 | const rows = parsed.rows; | ||
| 720 | for (let index = 0; index < rows.length; index += 1) { | ||
| 721 | if (rows[index].length !== columns.length) { | ||
| 722 | throw new Error(`rows[${index}] has ${rows[index].length} values but expected ${columns.length}`); | ||
| 723 | } | ||
| 724 | } | ||
| 725 | |||
| 726 | const maxRowsPerChunk = Math.max(1, Math.floor(SQLITE_MAX_BIND_VARIABLES / Math.max(1, columns.length))); | ||
| 727 | const requestedChunkSize = typeof parsed.chunkSize === "number" && Number.isFinite(parsed.chunkSize) | ||
| 728 | ? Math.max(1, Math.floor(parsed.chunkSize)) | ||
| 729 | : maxRowsPerChunk; | ||
| 730 | const rowsPerChunk = Math.max(1, Math.min(maxRowsPerChunk, requestedChunkSize)); | ||
| 731 | |||
| 732 | const conflictClause = parsed.onConflict === "ignore" | ||
| 733 | ? " OR IGNORE" | ||
| 734 | : parsed.onConflict === "replace" | ||
| 735 | ? " OR REPLACE" | ||
| 736 | : ""; | ||
| 737 | |||
| 738 | const quotedTable = quoteSqlIdentifier(table); | ||
| 739 | const quotedColumns = columns.map(quoteSqlIdentifier).join(", "); | ||
| 740 | const placeholderRow = `(${columns.map(() => "?").join(", ")})`; | ||
| 741 | |||
| 742 | let totalChanges = 0; | ||
| 743 | let chunkCount = 0; | ||
| 744 | for (let start = 0; start < rows.length; start += rowsPerChunk) { | ||
| 745 | const chunk = rows.slice(start, start + rowsPerChunk); | ||
| 746 | const values = chunk.map(() => placeholderRow).join(", "); | ||
| 747 | const params = chunk.flat(); | ||
| 748 | const sql = `INSERT${conflictClause} INTO ${quotedTable} (${quotedColumns}) VALUES ${values}`; | ||
| 749 | |||
| 750 | let writeResult; | ||
| 751 | try { | ||
| 752 | writeResult = await provider.sqliteQuery(instance, { | ||
| 753 | sql, | ||
| 754 | params, | ||
| 755 | mode: "write", | ||
| 756 | maxRows: 1, | ||
| 757 | }); | ||
| 758 | } catch (error) { | ||
| 759 | throw decorateSqliteError(error, { | ||
| 760 | sql, | ||
| 761 | params, | ||
| 762 | instanceId: instance.id, | ||
| 763 | }); | ||
| 764 | } | ||
| 765 | |||
| 766 | totalChanges += Number(writeResult.changes ?? 0); | ||
| 767 | chunkCount += 1; | ||
| 768 | } | ||
| 769 | |||
| 770 | await touchInstance(ctx, task, instance, provider, true); | ||
| 771 | return sqliteInsertRowsOutputSchema.parse({ | ||
| 772 | instanceId: instance.id, | ||
| 773 | table, | ||
| 774 | columns, | ||
| 775 | rowsReceived: rows.length, | ||
| 776 | rowsProcessed: rows.length, | ||
| 777 | chunkCount, | ||
| 778 | rowsPerChunk, | ||
| 779 | maxBindVariables: SQLITE_MAX_BIND_VARIABLES, | ||
| 780 | changes: totalChanges, | ||
| 781 | }); | ||
| 782 | } | ||
| 783 | |||
| 784 | if (normalizedToolPath === "sqlite.query") { | ||
| 368 | const parsed = sqliteQueryInputSchema.parse(payload); | 785 | const parsed = sqliteQueryInputSchema.parse(payload); |
| 369 | const instance = await resolveStorageInstance(ctx, task, payload); | 786 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 370 | const provider = getStorageProvider(instance.provider); | 787 | const provider = getStorageProvider(instance.provider); |
| 371 | const mode = parsed.mode ?? "read"; | 788 | const mode = parsed.mode ?? (isReadOnlySql(parsed.sql) ? "read" : "write"); |
| 372 | const maxRows = Math.max(1, Math.min(1_000, Math.floor(parsed.maxRows ?? 200))); | 789 | const maxRows = Math.max(1, Math.min(1_000, Math.floor(parsed.maxRows ?? 200))); |
| 373 | const result = await provider.sqliteQuery(instance, { | 790 | const params = parsed.params ?? []; |
| 374 | sql: parsed.sql, | 791 | let result; |
| 375 | params: parsed.params ?? [], | 792 | try { |
| 376 | mode, | 793 | result = await provider.sqliteQuery(instance, { |
| 377 | maxRows, | 794 | sql: parsed.sql, |
| 378 | }); | 795 | params, |
| 796 | mode, | ||
| 797 | maxRows, | ||
| 798 | }); | ||
| 799 | } catch (error) { | ||
| 800 | throw decorateSqliteError(error, { | ||
| 801 | sql: parsed.sql, | ||
| 802 | params, | ||
| 803 | instanceId: instance.id, | ||
| 804 | }); | ||
| 805 | } | ||
| 379 | await touchInstance(ctx, task, instance, provider, mode === "write"); | 806 | await touchInstance(ctx, task, instance, provider, mode === "write"); |
| 380 | return sqliteQueryOutputSchema.parse({ | 807 | return sqliteQueryOutputSchema.parse({ |
| 381 | instanceId: instance.id, | 808 | instanceId: instance.id, |
| @@ -159,7 +159,9 @@ function unknownToolErrorMessage(toolPath: string, suggestions: string[]): strin · 158 unmodified lines | |||
| 159 | : ""; | 159 | : ""; |
| 160 | const queryHint = toolPath.split(".").filter(Boolean).join(" "); | 160 | const queryHint = toolPath.split(".").filter(Boolean).join(" "); |
| 161 | const discoverHint = `\nTry: const found = await tools.discover({ query: "${queryHint}", depth: 1, limit: 12 });`; | 161 | const discoverHint = `\nTry: const found = await tools.discover({ query: "${queryHint}", depth: 1, limit: 12 });`; |
| 162 | return `Unknown tool: ${toolPath}${suggestionText}${discoverHint}`; | 162 | const catalogHint = `\nOr: const list = await tools.catalog.tools({ query: "${queryHint}", limit: 20 });`; |
| 163 | const proxyHint = "\nNote: tools.* uses a lazy proxy, so property introspection (Object.keys/typeof on arbitrary paths) is not a reliable availability check."; | ||
| 164 | return `Unknown tool: ${toolPath}${suggestionText}${discoverHint}${catalogHint}${proxyHint}`; | ||
| 163 | } | 165 | } |
| 164 | 166 | ||
| 165 | export async function resolveToolForCall( | 167 | export async function resolveToolForCall( |
| @@ -12,6 +12,10 @@ import type { · 11 unmodified lines | |||
| 12 | ToolRunContext, | 12 | ToolRunContext, |
| 13 | } from "../../../core/src/types"; | 13 | } from "../../../core/src/types"; |
| 14 | import { executeSerializedTool, parseSerializedTool } from "../../../core/src/tool/source-serialization"; | 14 | import { executeSerializedTool, parseSerializedTool } from "../../../core/src/tool/source-serialization"; |
| 15 | import { | ||
| 16 | compactArgTypeHintFromSchema, | ||
| 17 | compactReturnTypeHintFromSchema, | ||
| 18 | } from "../../../core/src/type-hints"; | ||
| 15 | import { describeError } from "../../../core/src/utils"; | 19 | import { describeError } from "../../../core/src/utils"; |
| 16 | import { | 20 | import { |
| 17 | decodeToolCallControlSignal, | 21 | decodeToolCallControlSignal, |
| @@ -239,6 +243,53 @@ async function listRegistryNamespaces( · 221 unmodified lines | |||
| 239 | return parsed.success ? parsed.data : []; | 243 | return parsed.success ? parsed.data : []; |
| 240 | } | 244 | } |
| 241 | 245 | ||
| 246 | function toolNamespace(path: string): string { | ||
| 247 | const normalized = path.trim().toLowerCase(); | ||
| 248 | if (!normalized) return "default"; | ||
| 249 | const [head] = normalized.split("."); | ||
| 250 | return head && head.length > 0 ? head : "default"; | ||
| 251 | } | ||
| 252 | |||
| 253 | function matchesToolQuery( | ||
| 254 | query: string, | ||
| 255 | args: { path: string; description?: string; source?: string }, | ||
| 256 | ): boolean { | ||
| 257 | const normalizedQuery = query.trim().toLowerCase(); | ||
| 258 | if (!normalizedQuery) return true; | ||
| 259 | |||
| 260 | const haystack = `${args.path} ${args.description ?? ""} ${args.source ?? ""}`.toLowerCase(); | ||
| 261 | return normalizedQuery | ||
| 262 | .split(/\s+/) | ||
| 263 | .filter((token) => token.length > 0) | ||
| 264 | .every((token) => haystack.includes(token)); | ||
| 265 | } | ||
| 266 | |||
| 267 | function getToolHints(tool: ToolDefinition): { inputHint: string; outputHint: string } { | ||
| 268 | const hintedInput = typeof tool.typing?.inputHint === "string" ? tool.typing.inputHint.trim() : ""; | ||
| 269 | const hintedOutput = typeof tool.typing?.outputHint === "string" ? tool.typing.outputHint.trim() : ""; | ||
| 270 | |||
| 271 | const inputHint = hintedInput.length > 0 | ||
| 272 | ? hintedInput | ||
| 273 | : compactArgTypeHintFromSchema(tool.typing?.inputSchema ?? {}); | ||
| 274 | const outputHint = hintedOutput.length > 0 | ||
| 275 | ? hintedOutput | ||
| 276 | : compactReturnTypeHintFromSchema(tool.typing?.outputSchema ?? {}); | ||
| 277 | |||
| 278 | return { | ||
| 279 | inputHint: inputHint.trim().length > 0 ? inputHint : "{}", | ||
| 280 | outputHint: outputHint.trim().length > 0 ? outputHint : "unknown", | ||
| 281 | }; | ||
| 282 | } | ||
| 283 | |||
| 284 | function toBaseDiscoveryTyping(tool: ToolDefinition): DiscoveryTypingPayload { | ||
| 285 | const inputSchemaJson = toSchemaJson(tool.typing?.inputSchema); | ||
| 286 | const outputSchemaJson = toSchemaJson(tool.typing?.outputSchema); | ||
| 287 | return { | ||
| 288 | ...(inputSchemaJson ? { inputSchemaJson } : {}), | ||
| 289 | ...(outputSchemaJson ? { outputSchemaJson } : {}), | ||
| 290 | }; | ||
| 291 | } | ||
| 292 | |||
| 242 | async function searchRegistryTools( | 293 | async function searchRegistryTools( |
| 243 | ctx: ActionCtx, | 294 | ctx: ActionCtx, |
| 244 | args: { | 295 | args: { |
| @@ -403,13 +454,12 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 158 unmodified lines | |||
| 403 | accountId: task.accountId, | 454 | accountId: task.accountId, |
| 404 | clientId: task.clientId, | 455 | clientId: task.clientId, |
| 405 | }); | 456 | }); |
| 406 | if (buildIdResult.isErr()) { | 457 | const buildId = buildIdResult.isErr() ? undefined : buildIdResult.value; |
| 407 | throw buildIdResult.error; | 458 | const state = buildId |
| 408 | } | 459 | ? await ctx.runQuery(internal.toolRegistry.getState, { |
| 409 | const buildId = buildIdResult.value; | 460 | workspaceId: task.workspaceId, |
| 410 | const state = await ctx.runQuery(internal.toolRegistry.getState, { | 461 | }) as unknown as { openApiRefHintTables?: unknown } | null |
| 411 | workspaceId: task.workspaceId, | 462 | : null; |
| 412 | }) as unknown as { openApiRefHintTables?: unknown } | null; | ||
| 413 | const refHintLookup = buildOpenApiRefHintLookup(state?.openApiRefHintTables); | 463 | const refHintLookup = buildOpenApiRefHintLookup(state?.openApiRefHintTables); |
| 414 | 464 | ||
| 415 | const payload = typeof input === "string" | 465 | const payload = typeof input === "string" |
| @@ -439,11 +489,45 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 23 unmodified lines | |||
| 439 | const parsedInput = catalogNamespacesInputSchema.safeParse(payload); | 489 | const parsedInput = catalogNamespacesInputSchema.safeParse(payload); |
| 440 | const limitInput = parsedInput.success ? parsedInput.data.limit : undefined; | 490 | const limitInput = parsedInput.success ? parsedInput.data.limit : undefined; |
| 441 | const limit = Math.max(1, Math.min(200, Number(limitInput ?? 200))); | 491 | const limit = Math.max(1, Math.min(200, Number(limitInput ?? 200))); |
| 442 | const namespaces = await listRegistryNamespaces(ctx, { | 492 | const registryNamespaces = buildId |
| 443 | workspaceId: task.workspaceId, | 493 | ? await listRegistryNamespaces(ctx, { |
| 444 | buildId, | 494 | workspaceId: task.workspaceId, |
| 445 | limit, | 495 | buildId, |
| 446 | }); | 496 | limit: 200, |
| 497 | }) | ||
| 498 | : []; | ||
| 499 | |||
| 500 | const namespaceMap = new Map<string, { toolCount: number; samplePaths: string[] }>(); | ||
| 501 | for (const entry of registryNamespaces) { | ||
| 502 | namespaceMap.set(entry.namespace, { | ||
| 503 | toolCount: entry.toolCount, | ||
| 504 | samplePaths: [...entry.samplePaths], | ||
| 505 | }); | ||
| 506 | } | ||
| 507 | |||
| 508 | for (const baseTool of baseTools.values()) { | ||
| 509 | if (!isAllowed(baseTool.path, baseTool.approval, baseTool.source)) { | ||
| 510 | continue; | ||
| 511 | } | ||
| 512 | |||
| 513 | const namespace = toolNamespace(baseTool.path); | ||
| 514 | const current = namespaceMap.get(namespace) ?? { toolCount: 0, samplePaths: [] }; | ||
| 515 | current.toolCount += 1; | ||
| 516 | if (!current.samplePaths.includes(baseTool.path)) { | ||
| 517 | current.samplePaths.push(baseTool.path); | ||
| 518 | } | ||
| 519 | namespaceMap.set(namespace, current); | ||
| 520 | } | ||
| 521 | |||
| 522 | const namespaces = [...namespaceMap.entries()] | ||
| 523 | .map(([namespace, meta]) => ({ | ||
| 524 | namespace, | ||
| 525 | toolCount: meta.toolCount, | ||
| 526 | samplePaths: [...meta.samplePaths].sort((a, b) => a.localeCompare(b)).slice(0, 3), | ||
| 527 | })) | ||
| 528 | .sort((a, b) => a.namespace.localeCompare(b.namespace)) | ||
| 529 | .slice(0, limit); | ||
| 530 | |||
| 447 | const output = catalogNamespacesOutputSchema.parse({ | 531 | const output = catalogNamespacesOutputSchema.parse({ |
| 448 | namespaces, | 532 | namespaces, |
| 449 | total: namespaces.length, | 533 | total: namespaces.length, |
| @@ -459,29 +543,47 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 9 unmodified lines | |||
| 459 | const limit = Math.max(1, Math.min(200, Number(limitInput ?? 50))); | 543 | const limit = Math.max(1, Math.min(200, Number(limitInput ?? 50))); |
| 460 | const includeSchemas = parsedInput.success ? (parsedInput.data.includeSchemas ?? false) : false; | 544 | const includeSchemas = parsedInput.success ? (parsedInput.data.includeSchemas ?? false) : false; |
| 461 | 545 | ||
| 462 | const raw = query | 546 | const raw = !buildId |
| 463 | ? await searchRegistryTools(ctx, { | 547 | ? [] |
| 464 | workspaceId: task.workspaceId, | 548 | : query |
| 465 | buildId, | 549 | ? await searchRegistryTools(ctx, { |
| 466 | query, | ||
| 467 | limit, | ||
| 468 | includeSchemas, | ||
| 469 | }) | ||
| 470 | : namespace | ||
| 471 | ? await listRegistryToolsByNamespace(ctx, { | ||
| 472 | workspaceId: task.workspaceId, | 550 | workspaceId: task.workspaceId, |
| 473 | buildId, | 551 | buildId, |
| 474 | namespace, | 552 | query, |
| 475 | limit, | 553 | limit, |
| 476 | includeSchemas, | 554 | includeSchemas, |
| 477 | }) | 555 | }) |
| 478 | : []; | 556 | : namespace |
| 557 | ? await listRegistryToolsByNamespace(ctx, { | ||
| 558 | workspaceId: task.workspaceId, | ||
| 559 | buildId, | ||
| 560 | namespace, | ||
| 561 | limit, | ||
| 562 | includeSchemas, | ||
| 563 | }) | ||
| 564 | : []; | ||
| 565 | |||
| 566 | const baseMatches = [...baseTools.values()] | ||
| 567 | .filter((baseTool) => { | ||
| 568 | if (namespace && toolNamespace(baseTool.path) !== namespace) { | ||
| 569 | return false; | ||
| 570 | } | ||
| 571 | if (query && !matchesToolQuery(query, { | ||
| 572 | path: baseTool.path, | ||
| 573 | description: baseTool.description, | ||
| 574 | source: baseTool.source, | ||
| 575 | })) { | ||
| 576 | return false; | ||
| 577 | } | ||
| 578 | |||
| 579 | return isAllowed(baseTool.path, baseTool.approval, baseTool.source); | ||
| 580 | }) | ||
| 581 | .sort((left, right) => left.path.localeCompare(right.path)); | ||
| 479 | 582 | ||
| 480 | const refHintTable: Record<string, string> = {}; | 583 | const refHintTable: Record<string, string> = {}; |
| 481 | const results = raw | 584 | const registryResults = raw |
| 482 | .filter((entry) => !namespace || String(entry.preferredPath ?? entry.path ?? "").toLowerCase().startsWith(`${namespace}.`)) | 585 | .filter((entry) => !namespace || String(entry.preferredPath ?? entry.path ?? "").toLowerCase().startsWith(`${namespace}.`)) |
| 483 | .filter((entry) => isAllowed(entry.path, entry.approval, entry.source)) | 586 | .filter((entry) => isAllowed(entry.path, entry.approval, entry.source)) |
| 484 | .slice(0, limit) | ||
| 485 | .map((entry) => { | 587 | .map((entry) => { |
| 486 | const preferredPath = entry.preferredPath ?? entry.path; | 588 | const preferredPath = entry.preferredPath ?? entry.path; |
| 487 | const discoveryTyping = resolveEntryDiscoveryTyping(entry, refHintLookup); | 589 | const discoveryTyping = resolveEntryDiscoveryTyping(entry, refHintLookup); |
| @@ -504,6 +606,34 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 16 unmodified lines | |||
| 504 | }; | 606 | }; |
| 505 | }); | 607 | }); |
| 506 | 608 | ||
| 609 | const baseResults = baseMatches.map((entry) => { | ||
| 610 | const hints = getToolHints(entry); | ||
| 611 | const typing = toBaseDiscoveryTyping(entry); | ||
| 612 | return { | ||
| 613 | path: entry.path, | ||
| 614 | source: entry.source, | ||
| 615 | approval: entry.approval, | ||
| 616 | description: entry.description, | ||
| 617 | ...(includeSchemas | ||
| 618 | ? (Object.keys(typing).length > 0 ? { typing } : {}) | ||
| 619 | : { | ||
| 620 | inputHint: hints.inputHint, | ||
| 621 | outputHint: hints.outputHint, | ||
| 622 | }), | ||
| 623 | }; | ||
| 624 | }); | ||
| 625 | |||
| 626 | const mergedByPath = new Map<string, (typeof registryResults)[number]>(); | ||
| 627 | for (const entry of baseResults) { | ||
| 628 | mergedByPath.set(entry.path, entry); | ||
| 629 | } | ||
| 630 | for (const entry of registryResults) { | ||
| 631 | if (!mergedByPath.has(entry.path)) { | ||
| 632 | mergedByPath.set(entry.path, entry); | ||
| 633 | } | ||
| 634 | } | ||
| 635 | const results = [...mergedByPath.values()].slice(0, limit); | ||
| 636 | |||
| 507 | const output = catalogToolsOutputSchema.parse({ | 637 | const output = catalogToolsOutputSchema.parse({ |
| 508 | ...(Object.keys(refHintTable).length > 0 ? { refHintTable } : {}), | 638 | ...(Object.keys(refHintTable).length > 0 ? { refHintTable } : {}), |
| 509 | results, | 639 | results, |
| @@ -519,20 +649,31 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 9 unmodified lines | |||
| 519 | const limit = Math.max(1, Math.min(50, Number(limitInput ?? 8))); | 649 | const limit = Math.max(1, Math.min(50, Number(limitInput ?? 8))); |
| 520 | const compact = parsedInput.success ? (parsedInput.data.compact ?? true) : true; | 650 | const compact = parsedInput.success ? (parsedInput.data.compact ?? true) : true; |
| 521 | const includeSchemas = parsedInput.success ? (parsedInput.data.includeSchemas ?? false) : false; | 651 | const includeSchemas = parsedInput.success ? (parsedInput.data.includeSchemas ?? false) : false; |
| 522 | const hits = await searchRegistryTools(ctx, { | 652 | const hits = buildId |
| 523 | workspaceId: task.workspaceId, | 653 | ? await searchRegistryTools(ctx, { |
| 524 | buildId, | 654 | workspaceId: task.workspaceId, |
| 525 | query, | 655 | buildId, |
| 526 | limit: Math.max(limit * 2, limit), | 656 | query, |
| 527 | includeSchemas, | 657 | limit: Math.max(limit * 2, limit), |
| 528 | }); | 658 | includeSchemas, |
| 659 | }) | ||
| 660 | : []; | ||
| 661 | |||
| 662 | const baseHits = [...baseTools.values()] | ||
| 663 | .filter((baseTool) => matchesToolQuery(query, { | ||
| 664 | path: baseTool.path, | ||
| 665 | description: baseTool.description, | ||
| 666 | source: baseTool.source, | ||
| 667 | })) | ||
| 668 | .filter((baseTool) => isAllowed(baseTool.path, baseTool.approval, baseTool.source)) | ||
| 669 | .sort((left, right) => left.path.localeCompare(right.path)); | ||
| 529 | 670 | ||
| 530 | const filtered = hits | 671 | const filtered = hits |
| 531 | .filter((entry) => isAllowed(entry.path, entry.approval, entry.source)) | 672 | .filter((entry) => isAllowed(entry.path, entry.approval, entry.source)) |
| 532 | .slice(0, limit); | 673 | .slice(0, Math.max(limit * 2, limit)); |
| 533 | 674 | ||
| 534 | const refHintTable: Record<string, string> = {}; | 675 | const refHintTable: Record<string, string> = {}; |
| 535 | const results = filtered.map((entry) => { | 676 | const registryResults = filtered.map((entry) => { |
| 536 | const preferredPath = entry.preferredPath ?? entry.path; | 677 | const preferredPath = entry.preferredPath ?? entry.path; |
| 537 | const description = compact ? String(entry.description ?? "").split("\n")[0] : entry.description; | 678 | const description = compact ? String(entry.description ?? "").split("\n")[0] : entry.description; |
| 538 | const discoveryTyping = resolveEntryDiscoveryTyping(entry, refHintLookup); | 679 | const discoveryTyping = resolveEntryDiscoveryTyping(entry, refHintLookup); |
| @@ -555,6 +696,34 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 16 unmodified lines | |||
| 555 | }; | 696 | }; |
| 556 | }); | 697 | }); |
| 557 | 698 | ||
| 699 | const baseResults = baseHits.map((entry) => { | ||
| 700 | const hints = getToolHints(entry); | ||
| 701 | const typing = toBaseDiscoveryTyping(entry); | ||
| 702 | return { | ||
| 703 | path: entry.path, | ||
| 704 | source: entry.source, | ||
| 705 | approval: entry.approval, | ||
| 706 | description: compact ? String(entry.description ?? "").split("\n")[0] : entry.description, | ||
| 707 | ...(includeSchemas | ||
| 708 | ? (Object.keys(typing).length > 0 ? { typing } : {}) | ||
| 709 | : { | ||
| 710 | inputHint: hints.inputHint, | ||
| 711 | outputHint: hints.outputHint, | ||
| 712 | }), | ||
| 713 | }; | ||
| 714 | }); | ||
| 715 | |||
| 716 | const mergedByPath = new Map<string, (typeof registryResults)[number]>(); | ||
| 717 | for (const entry of baseResults) { | ||
| 718 | mergedByPath.set(entry.path, entry); | ||
| 719 | } | ||
| 720 | for (const entry of registryResults) { | ||
| 721 | if (!mergedByPath.has(entry.path)) { | ||
| 722 | mergedByPath.set(entry.path, entry); | ||
| 723 | } | ||
| 724 | } | ||
| 725 | const results = [...mergedByPath.values()].slice(0, limit); | ||
| 726 | |||
| 558 | const bestPath = results[0]?.path ?? null; | 727 | const bestPath = results[0]?.path ?? null; |
| 559 | const output = discoverOutputSchema.parse({ | 728 | const output = discoverOutputSchema.parse({ |
| 560 | bestPath, | 729 | bestPath, |
| @@ -320,10 +320,13 @@ export class AgentFSStorageObject extends DurableObject<Env> { · 319 unmodified lines | |||
| 320 | const limit = typeof payload.limit === "number" && Number.isFinite(payload.limit) | 320 | const limit = typeof payload.limit === "number" && Number.isFinite(payload.limit) |
| 321 | ? Math.max(1, Math.min(500, Math.floor(payload.limit))) | 321 | ? Math.max(1, Math.min(500, Math.floor(payload.limit))) |
| 322 | : 100; | 322 | : 100; |
| 323 | const escaped = prefix.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_"); | 323 | const escaped = prefix |
| 324 | .replaceAll("^", "^^") | ||
| 325 | .replaceAll("%", "^%") | ||
| 326 | .replaceAll("_", "^_"); | ||
| 324 | const rows = this.ctx.storage.sql | 327 | const rows = this.ctx.storage.sql |
| 325 | .exec<{ key: string; value: string }>( | 328 | .exec<{ key: string; value: string }>( |
| 326 | "SELECT key, value FROM kv_store WHERE key LIKE ? ESCAPE '\\\\' ORDER BY key LIMIT ?", | 329 | "SELECT key, value FROM kv_store WHERE key LIKE ? ESCAPE '^' ORDER BY key LIMIT ?", |
| 327 | `${escaped}%`, | 330 | `${escaped}%`, |
| 328 | limit, | 331 | limit, |
| 329 | ) | 332 | ) |
| @@ -81,6 +81,15 @@ function parseWorkersDevUrl(output: string): string | null { · 80 unmodified lines | |||
| 81 | return match ? match[0] : null; | 81 | return match ? match[0] : null; |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | function isHostedConvexUrl(url: string): boolean { | ||
| 85 | try { | ||
| 86 | const hostname = new URL(url).hostname.trim().toLowerCase(); | ||
| 87 | return hostname.endsWith(".convex.cloud") || hostname.endsWith(".convex.site"); | ||
| 88 | } catch { | ||
| 89 | return false; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 84 | async function deployCloudflareWorkerAndGetUrl(): Promise<string> { | 93 | async function deployCloudflareWorkerAndGetUrl(): Promise<string> { |
| 85 | console.log("[storage:smoke:execute-flow] deploying runner-sandbox-host..."); | 94 | console.log("[storage:smoke:execute-flow] deploying runner-sandbox-host..."); |
| 86 | const output = await $`bun run --cwd packages/runner-sandbox-host deploy` | 95 | const output = await $`bun run --cwd packages/runner-sandbox-host deploy` |
| @@ -172,6 +181,12 @@ async function run(): Promise<void> { · 85 unmodified lines | |||
| 172 | const requestedRuntime: RuntimeId = options.runtimeId | 181 | const requestedRuntime: RuntimeId = options.runtimeId |
| 173 | ?? (process.env.CLOUDFLARE_SANDBOX_RUN_URL?.trim() ? "cloudflare-worker-loader" : "local-bun"); | 182 | ?? (process.env.CLOUDFLARE_SANDBOX_RUN_URL?.trim() ? "cloudflare-worker-loader" : "local-bun"); |
| 174 | 183 | ||
| 184 | if (requestedRuntime === "local-bun" && isHostedConvexUrl(convexUrl)) { | ||
| 185 | throw new Error( | ||
| 186 | "local-bun storage smoke is self-host only. Hosted Convex deployments must use AGENT_STORAGE_PROVIDER=agentfs-cloudflare and the cloudflare-worker-loader runtime.", | ||
| 187 | ); | ||
| 188 | } | ||
| 189 | |||
| 175 | await maybeDeployConvex(options.deployConvex); | 190 | await maybeDeployConvex(options.deployConvex); |
| 176 | 191 | ||
| 177 | if (requestedRuntime === "cloudflare-worker-loader") { | 192 | if (requestedRuntime === "cloudflare-worker-loader") { |