- 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 | 16 | # ── Agent storage backend (optional) ── |
| 17 | 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 | 20 | # AGENT_STORAGE_PROVIDER=agentfs-local |
| 19 | 21 | # AGENT_STORAGE_ROOT=/tmp/executor-agentfs |
| 20 | 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 | 148 | - `CONVEX_URL` |
| 149 | 149 | - `CONVEX_SITE_URL` |
| 150 | 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 | 155 | - WorkOS (optional auth/org features): |
| 152 | 156 | - `WORKOS_CLIENT_ID` |
| 153 | 157 | - `WORKOS_API_KEY` |
| @@ -225,4 +229,5 @@ executor/ · 71 unmodified lines | ||
| 225 | 229 | - `401` on `/mcp`: verify your bearer token issuer matches `MCP_AUTHORIZATION_SERVER`. |
| 226 | 230 | - `401` on `/mcp/anonymous`: pass `API_KEY` from the setup card as `Authorization: Bearer <API_KEY>` (or `x-api-key`). |
| 227 | 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 | 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 | 1 | "use client"; |
| 2 | 2 | |
| 3 | −import { useMemo, useState } from "react"; | |
| 4 | −import { Database, FolderOpen, Plus, Trash2, Power } from "lucide-react"; | |
| 3 | +import { useEffect, useMemo, useState } from "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 | 16 | import { Badge } from "@/components/ui/badge"; |
| 6 | 17 | import { Button } from "@/components/ui/button"; |
| 7 | 18 | import { Input } from "@/components/ui/input"; |
| 8 | 19 | import { Skeleton } from "@/components/ui/skeleton"; |
| 9 | 20 | import type { StorageDurability, StorageInstanceRecord, StorageScopeType } from "@/lib/types"; |
| 21 | +import { convexApi } from "@/lib/convex-api"; | |
| 10 | 22 | import { cn } from "@/lib/utils"; |
| 11 | 23 | |
| 12 | 24 | type CreateStorageArgs = { |
| @@ -16,6 +28,49 @@ type CreateStorageArgs = { · 3 unmodified lines | ||
| 16 | 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 | 74 | function prettyBytes(value?: number): string { |
| 20 | 75 | if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) { |
| 21 | 76 | return "-"; |
| @@ -39,7 +94,74 @@ function asLocalDate(value?: number): string { · 17 unmodified lines | ||
| 39 | 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 | 162 | export function StoragePanel({ |
| 163 | + workspaceId, | |
| 164 | + sessionId, | |
| 43 | 165 | instances, |
| 44 | 166 | loading, |
| 45 | 167 | creating, |
| @@ -48,6 +170,8 @@ export function StoragePanel({ · 2 unmodified lines | ||
| 48 | 170 | onClose, |
| 49 | 171 | onDelete, |
| 50 | 172 | }: { |
| 173 | + workspaceId?: string; | |
| 174 | + sessionId?: string; | |
| 51 | 175 | instances: StorageInstanceRecord[]; |
| 52 | 176 | loading: boolean; |
| 53 | 177 | creating: boolean; |
| @@ -60,12 +184,252 @@ export function StoragePanel({ · 6 unmodified lines | ||
| 60 | 184 | const [durability, setDurability] = useState<StorageDurability>("ephemeral"); |
| 61 | 185 | const [purpose, setPurpose] = useState(""); |
| 62 | 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 | 222 | const visibleInstances = useMemo( |
| 65 | 223 | () => [...instances].sort((a, b) => b.updatedAt - a.updatedAt), |
| 66 | 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 | 433 | const submitCreate = async () => { |
| 70 | 434 | const parsedTtl = Number.parseInt(ttlHours, 10); |
| 71 | 435 | await onCreate({ |
| @@ -78,159 +442,486 @@ export function StoragePanel({ · 6 unmodified lines | ||
| 78 | 442 | }; |
| 79 | 443 | |
| 80 | 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"> | |
| 82 | − <div className="flex items-center justify-between border-b border-border/40 px-4 py-3 sm:px-5"> | |
| 83 | − <div className="flex items-center gap-2"> | |
| 84 | − <div className="flex h-7 w-7 items-center justify-center border border-border/60 bg-background/80"> | |
| 85 | − <Database className="h-4 w-4 text-muted-foreground" /> | |
| 445 | + <section className="flex h-full min-h-0 w-full overflow-hidden rounded-none border border-border/50 bg-card/30"> | |
| 446 | + <aside className="flex h-full w-72 shrink-0 flex-col border-r border-border/40 lg:w-80 xl:w-[22rem]"> | |
| 447 | + <div className="flex items-center justify-between border-b border-border/40 px-3 py-2.5"> | |
| 448 | + <div className="flex items-center gap-2"> | |
| 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 | 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 | 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"> | |
| 95 | − <div className="grid gap-2 md:grid-cols-5"> | |
| 96 | − <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> | |
| 97 | − Scope | |
| 98 | − <select | |
| 99 | − className="h-8 rounded-none border border-border/60 bg-background px-2 text-xs text-foreground" | |
| 100 | − value={scopeType} | |
| 101 | − onChange={(event) => { | |
| 102 | − const next = event.target.value as StorageScopeType; | |
| 103 | − setScopeType(next); | |
| 104 | − if (next !== "scratch") { | |
| 105 | − setDurability("durable"); | |
| 106 | − } | |
| 107 | − }} | |
| 108 | − > | |
| 109 | − <option value="scratch">scratch</option> | |
| 110 | − <option value="account">account</option> | |
| 111 | − <option value="workspace">workspace</option> | |
| 112 | − <option value="organization">organization</option> | |
| 113 | − </select> | |
| 114 | − </label> | |
| 115 | − | |
| 116 | − <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> | |
| 117 | − Durability | |
| 118 | − <select | |
| 119 | − className="h-8 rounded-none border border-border/60 bg-background px-2 text-xs text-foreground" | |
| 120 | − value={durability} | |
| 121 | − onChange={(event) => setDurability(event.target.value as StorageDurability)} | |
| 122 | − > | |
| 123 | − <option value="ephemeral">ephemeral</option> | |
| 124 | − <option value="durable">durable</option> | |
| 125 | − </select> | |
| 126 | − </label> | |
| 127 | − | |
| 128 | − <label className="flex flex-col gap-1 text-[11px] text-muted-foreground md:col-span-2"> | |
| 129 | − 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) | |
| 459 | + <div className="shrink-0 border-b border-border/40 bg-background/40 px-3 py-3"> | |
| 460 | + <div className="space-y-2"> | |
| 461 | + <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> | |
| 462 | + Scope | |
| 463 | + <select | |
| 464 | + className="h-8 rounded-none border border-border/60 bg-background px-2 text-xs text-foreground" | |
| 465 | + value={scopeType} | |
| 466 | + onChange={(event) => { | |
| 467 | + const next = event.target.value as StorageScopeType; | |
| 468 | + setScopeType(next); | |
| 469 | + if (next !== "scratch") { | |
| 470 | + setDurability("durable"); | |
| 471 | + } | |
| 472 | + }} | |
| 473 | + > | |
| 474 | + <option value="scratch">scratch</option> | |
| 475 | + <option value="account">account</option> | |
| 476 | + <option value="workspace">workspace</option> | |
| 477 | + <option value="organization">organization</option> | |
| 478 | + </select> | |
| 479 | + </label> | |
| 480 | + | |
| 481 | + <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> | |
| 482 | + Durability | |
| 483 | + <select | |
| 484 | + className="h-8 rounded-none border border-border/60 bg-background px-2 text-xs text-foreground" | |
| 485 | + value={durability} | |
| 486 | + onChange={(event) => setDurability(event.target.value as StorageDurability)} | |
| 487 | + > | |
| 488 | + <option value="ephemeral">ephemeral</option> | |
| 489 | + <option value="durable">durable</option> | |
| 490 | + </select> | |
| 491 | + </label> | |
| 492 | + | |
| 493 | + <label className="flex flex-col gap-1 text-[11px] text-muted-foreground"> | |
| 494 | + Purpose | |
| 141 | 495 | <Input |
| 142 | − value={ttlHours} | |
| 143 | − onChange={(event) => setTtlHours(event.target.value)} | |
| 144 | − disabled={durability !== "ephemeral"} | |
| 496 | + value={purpose} | |
| 497 | + onChange={(event) => setPurpose(event.target.value)} | |
| 498 | + placeholder="scratch for repo indexing" | |
| 145 | 499 | className="h-8 rounded-none text-xs" |
| 146 | 500 | /> |
| 147 | 501 | </label> |
| 148 | − <Button size="sm" className="h-8 rounded-none text-xs" disabled={creating} onClick={() => void submitCreate()}> | |
| 149 | − <Plus className="mr-1.5 h-3.5 w-3.5" /> | |
| 150 | − Create | |
| 151 | − </Button> | |
| 502 | + | |
| 503 | + <div className="flex items-end gap-2"> | |
| 504 | + <label className="flex flex-1 flex-col gap-1 text-[11px] text-muted-foreground"> | |
| 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 | 518 | </div> |
| 153 | 519 | </div> |
| 154 | − </div> | |
| 155 | 520 | |
| 156 | − <div className="min-h-0 flex-1 overflow-y-auto p-4 sm:p-5"> | |
| 157 | − {loading ? ( | |
| 158 | − <div className="space-y-2"> | |
| 159 | − {Array.from({ length: 4 }).map((_, i) => ( | |
| 160 | − <Skeleton key={i} className="h-16 rounded-none" /> | |
| 161 | − ))} | |
| 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" /> | |
| 521 | + <div className="min-h-0 flex-1 overflow-y-auto p-2"> | |
| 522 | + {loading ? ( | |
| 523 | + <div className="space-y-2"> | |
| 524 | + {Array.from({ length: 4 }).map((_, i) => ( | |
| 525 | + <Skeleton key={i} className="h-16 rounded-none" /> | |
| 526 | + ))} | |
| 167 | 527 | </div> |
| 168 | − <p className="text-sm text-muted-foreground">No storage instances yet</p> | |
| 169 | − <p className="text-[11px] text-muted-foreground/70 text-center max-w-md"> | |
| 170 | − Create a scratch instance for short-lived runs or durable instances for shared state. | |
| 171 | − </p> | |
| 172 | − </div> | |
| 173 | − ) : ( | |
| 174 | − <div className="space-y-2"> | |
| 175 | − {visibleInstances.map((instance) => { | |
| 176 | − const busy = busyInstanceId === instance.id; | |
| 177 | − return ( | |
| 178 | − <div | |
| 179 | − key={instance.id} | |
| 180 | − className={cn( | |
| 181 | − "group flex items-center gap-3 border border-border/50 bg-background/70 px-3 py-2.5 transition-colors", | |
| 182 | − busy ? "opacity-60" : "hover:border-border hover:bg-accent/20", | |
| 183 | − )} | |
| 184 | − style={{ boxShadow: "inset 2px 0 0 0 color-mix(in oklch, var(--border) 70%, transparent)" }} | |
| 528 | + ) : visibleInstances.length === 0 ? ( | |
| 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"> | |
| 530 | + <div className="flex h-10 w-10 items-center justify-center border border-border/60 bg-muted/40"> | |
| 531 | + <FolderOpen className="h-5 w-5 text-muted-foreground/50" /> | |
| 532 | + </div> | |
| 533 | + <p className="text-sm text-muted-foreground">No storage instances yet</p> | |
| 534 | + </div> | |
| 535 | + ) : ( | |
| 536 | + <div className="space-y-2"> | |
| 537 | + {visibleInstances.map((instance) => { | |
| 538 | + const busy = busyInstanceId === instance.id; | |
| 539 | + const active = selectedInstance?.id === instance.id; | |
| 540 | + return ( | |
| 541 | + <div | |
| 542 | + key={instance.id} | |
| 543 | + className={cn( | |
| 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"> | |
| 187 | − <Database className="h-4 w-4 text-muted-foreground" /> | |
| 618 | + <KeyRound className="mr-1 h-3 w-3" /> KV | |
| 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 | 693 | </div> |
| 694 | + ) : null} | |
| 695 | + </div> | |
| 696 | + ) : null} | |
| 189 | 697 | |
| 190 | − <div className="flex-1 min-w-0"> | |
| 191 | − <div className="mb-1 flex items-center gap-1.5 flex-wrap"> | |
| 192 | − <span className="text-sm font-medium truncate">{instance.purpose || instance.id}</span> | |
| 193 | − <Badge variant="outline" className="text-[9px] font-mono uppercase tracking-wider">{instance.scopeType}</Badge> | |
| 194 | − <Badge variant="outline" className="text-[9px] font-mono uppercase tracking-wider">{instance.durability}</Badge> | |
| 195 | − <Badge variant="outline" className="text-[9px] font-mono uppercase tracking-wider">{instance.provider}</Badge> | |
| 196 | − <Badge variant="outline" className="text-[9px] font-mono uppercase tracking-wider">{instance.status}</Badge> | |
| 698 | + {activeInspectorTab === "kv" ? ( | |
| 699 | + <div className="space-y-2 p-4"> | |
| 700 | + <div className="flex items-center gap-2"> | |
| 701 | + <Input | |
| 702 | + value={kvPrefix} | |
| 703 | + onChange={(event) => setKvPrefix(event.target.value)} | |
| 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 | 737 | </div> |
| 738 | + )} | |
| 739 | + </div> | |
| 740 | + </div> | |
| 741 | + ) : null} | |
| 198 | 742 | |
| 199 | − <p className="text-[11px] text-muted-foreground"> | |
| 200 | − {prettyBytes(instance.sizeBytes)} - {instance.fileCount ?? "-"} inode{instance.fileCount === 1 ? "" : "s"} | |
| 201 | − </p> | |
| 202 | − <p className="mt-0.5 text-[11px] text-muted-foreground"> | |
| 203 | − Last used {asLocalDate(instance.lastSeenAt)} | |
| 204 | − {instance.expiresAt ? ` - expires ${asLocalDate(instance.expiresAt)}` : ""} | |
| 205 | − </p> | |
| 743 | + {activeInspectorTab === "sql" ? ( | |
| 744 | + <div className="grid h-full min-h-0 gap-3 p-4 lg:grid-cols-[15rem_minmax(0,1fr)]"> | |
| 745 | + <div className="flex min-h-0 flex-col border border-border/40"> | |
| 746 | + <div className="flex items-center justify-between border-b border-border/30 px-2 py-1 text-[11px] text-muted-foreground"> | |
| 747 | + <span>Tables</span> | |
| 748 | + <div className="flex items-center gap-1"> | |
| 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 | 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 | 811 | <Button |
| 210 | − variant="outline" | |
| 211 | 812 | size="sm" |
| 212 | − className="h-7 rounded-none text-[11px]" | |
| 213 | − disabled={busy} | |
| 214 | − onClick={() => void onClose(instance.id)} | |
| 813 | + className="h-8 rounded-none text-xs" | |
| 814 | + disabled={sqlLoading || !canInspect} | |
| 815 | + onClick={() => { | |
| 816 | + setSelectedSqlObjectName(null); | |
| 817 | + void runSql(); | |
| 818 | + }} | |
| 215 | 819 | > |
| 216 | − <Power className="mr-1 h-3 w-3" /> | |
| 217 | − Close | |
| 820 | + <Play className="mr-1 h-3 w-3" /> Run | |
| 218 | 821 | </Button> |
| 219 | − <Button | |
| 220 | − variant="outline" | |
| 221 | − size="sm" | |
| 222 | − className="h-7 rounded-none text-[11px] border-destructive/40 text-destructive hover:bg-destructive/10" | |
| 223 | − disabled={busy} | |
| 224 | − onClick={() => void onDelete(instance.id)} | |
| 225 | − > | |
| 226 | − <Trash2 className="mr-1 h-3 w-3" /> | |
| 227 | − Delete | |
| 822 | + <Button variant="outline" size="sm" className="h-8 rounded-none px-2 text-[11px]" onClick={() => { setSelectedSqlObjectName(null); void runSql(USER_TABLES_QUERY); }}> | |
| 823 | + User tables | |
| 824 | + </Button> | |
| 825 | + <Button variant="outline" size="sm" className="h-8 rounded-none px-2 text-[11px]" onClick={() => { setSelectedSqlObjectName(null); void runSql(ALL_OBJECTS_QUERY); }}> | |
| 826 | + All objects | |
| 827 | + </Button> | |
| 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')"); }}> | |
| 829 | + KV schema | |
| 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 | 836 | </Button> |
| 229 | 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 | 919 | </div> |
| 231 | − ); | |
| 232 | − })} | |
| 233 | − </div> | |
| 920 | + </div> | |
| 921 | + ) : null} | |
| 922 | + </> | |
| 923 | + ) : ( | |
| 924 | + <div className="p-4 text-[11px] text-muted-foreground">Select a storage instance to inspect.</div> | |
| 234 | 925 | )} |
| 235 | 926 | </div> |
| 236 | 927 | </section> |
| @@ -558,8 +558,10 @@ export function ToolsView() { · 557 unmodified lines | ||
| 558 | 558 | ) : null} |
| 559 | 559 | |
| 560 | 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 | 562 | <StoragePanel |
| 563 | + workspaceId={context?.workspaceId} | |
| 564 | + sessionId={context?.sessionId} | |
| 563 | 565 | instances={storageItems} |
| 564 | 566 | loading={storageLoading} |
| 565 | 567 | creating={storageCreateBusy} |
| @@ -8,8 +8,95 @@ · 7 unmodified lines | ||
| 8 | 8 | * @module |
| 9 | 9 | */ |
| 10 | 10 | |
| 11 | −import type { FunctionReference } from "convex/server"; | |
| 12 | −import type { GenericId as Id } from "convex/values"; | |
| 11 | +import type * as accounts from "../accounts.js"; | |
| 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 | 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 | 106 | * const myFunctionReference = api.myModule.myFunction; |
| 20 | 107 | * ``` |
| 21 | 108 | */ |
| 22 | −export declare const api: { | |
| 23 | − accounts: { | |
| 24 | − deleteCurrentAccount: FunctionReference< | |
| 25 | − "mutation", | |
| 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 | −}; | |
| 109 | +export declare const api: FilterApi< | |
| 110 | + typeof fullApi, | |
| 111 | + FunctionReference<any, "public"> | |
| 112 | +>; | |
| 572 | 113 | |
| 573 | 114 | /** |
| 574 | 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 | 119 | * const myFunctionReference = internal.myModule.myFunction; |
| 579 | 120 | * ``` |
| 580 | 121 | */ |
| 581 | −export declare const internal: { | |
| 582 | − auth: { | |
| 583 | − authKitEvent: FunctionReference< | |
| 584 | − "mutation", | |
| 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 | −}; | |
| 122 | +export declare const internal: FilterApi< | |
| 123 | + typeof fullApi, | |
| 124 | + FunctionReference<any, "internal"> | |
| 125 | +>; | |
| 1866 | 126 | |
| 1867 | 127 | export declare const components: { |
| 1868 | 128 | workOSAuthKit: { |
| @@ -6,6 +6,8 @@ export { · 5 unmodified lines | ||
| 6 | 6 | getTaskInWorkspace, |
| 7 | 7 | markTaskRunning, |
| 8 | 8 | markTaskFinished, |
| 9 | + setTaskStorageDefaultInstance, | |
| 10 | + trackTaskStorageAccess, | |
| 9 | 11 | } from "./database/tasks"; |
| 10 | 12 | export { |
| 11 | 13 | createApproval, |
| @@ -14,9 +14,29 @@ const MAX_EPHEMERAL_TTL_HOURS = 24 * 30; · 13 unmodified lines | ||
| 14 | 14 | |
| 15 | 15 | function resolveStorageProvider(): "agentfs-local" | "agentfs-cloudflare" { |
| 16 | 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 | 30 | if (raw === "cloudflare" || raw === "agentfs-cloudflare") { |
| 18 | 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 | 40 | return "agentfs-local"; |
| 21 | 41 | } |
| 22 | 42 | |
| @@ -2,10 +2,46 @@ import { v } from "convex/values"; · 1 unmodified line | ||
| 2 | 2 | import { internalMutation, internalQuery } from "../_generated/server"; |
| 3 | 3 | import { mapTask } from "../../src/database/mappers"; |
| 4 | 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 | 11 | import { DEFAULT_TASK_TIMEOUT_MS } from "../../src/task/constants"; |
| 7 | 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 | 45 | export const createTask = internalMutation({ |
| 10 | 46 | args: { |
| 11 | 47 | id: v.string(), |
| @@ -143,3 +179,106 @@ export const markTaskFinished = internalMutation({ · 131 unmodified lines | ||
| 143 | 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 | 7 | ToolDescriptor, |
| 8 | 8 | OpenApiSourceQuality, |
| 9 | 9 | SourceAuthProfile, |
| 10 | + StorageInstanceRecord, | |
| 10 | 11 | } from "../../core/src/types"; |
| 11 | 12 | import { isAdminRole } from "../../core/src/identity"; |
| 12 | 13 | import { requireCanonicalAccount } from "../src/runtime/account_auth"; |
| @@ -22,6 +23,72 @@ import { jsonObjectValidator } from "../src/database/validators"; · 9 unmodified lines | ||
| 22 | 23 | import { customAction } from "../../core/src/function-builders"; |
| 23 | 24 | import { encodeToolCallResultForTransport } from "../../core/src/tool-call-result-transport"; |
| 24 | 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 | 93 | export const listToolsWithWarnings = customAction({ |
| 27 | 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 | 375 | export const handleExternalToolCall = internalAction({ |
| 181 | 376 | args: { |
| 182 | 377 | runId: v.string(), |
| @@ -62,3 +62,9 @@ export const storageDurabilityValidator = v.union(v.literal("ephemeral"), v.lite · 61 unmodified lines | ||
| 62 | 62 | export const storageInstanceStatusValidator = v.union(v.literal("active"), v.literal("closed"), v.literal("deleted")); |
| 63 | 63 | |
| 64 | 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 | 24 | kvDeleteOutputJsonSchema, |
| 25 | 25 | kvGetInputJsonSchema, |
| 26 | 26 | kvGetOutputJsonSchema, |
| 27 | + kvIncrInputJsonSchema, | |
| 28 | + kvIncrOutputJsonSchema, | |
| 27 | 29 | kvListInputJsonSchema, |
| 28 | 30 | kvListOutputJsonSchema, |
| 29 | 31 | kvSetInputJsonSchema, |
| 30 | 32 | kvSetOutputJsonSchema, |
| 31 | 33 | sqliteQueryInputJsonSchema, |
| 32 | 34 | sqliteQueryOutputJsonSchema, |
| 35 | + sqliteCapabilitiesInputJsonSchema, | |
| 36 | + sqliteCapabilitiesOutputJsonSchema, | |
| 37 | + sqliteInsertRowsInputJsonSchema, | |
| 38 | + sqliteInsertRowsOutputJsonSchema, | |
| 33 | 39 | storageCloseInputJsonSchema, |
| 34 | 40 | storageCloseOutputJsonSchema, |
| 35 | 41 | storageDeleteInputJsonSchema, |
| @@ -94,7 +100,7 @@ registerSystemTool( · 58 unmodified lines | ||
| 94 | 100 | |
| 95 | 101 | registerSystemTool( |
| 96 | 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 | 105 | inputSchema: storageOpenInputJsonSchema, |
| 100 | 106 | outputSchema: storageOpenOutputJsonSchema, |
| @@ -130,7 +136,7 @@ registerSystemTool( · 29 unmodified lines | ||
| 130 | 136 | |
| 131 | 137 | registerSystemTool( |
| 132 | 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 | 141 | inputSchema: fsReadInputJsonSchema, |
| 136 | 142 | outputSchema: fsReadOutputJsonSchema, |
| @@ -139,7 +145,7 @@ registerSystemTool( · 2 unmodified lines | ||
| 139 | 145 | |
| 140 | 146 | registerSystemTool( |
| 141 | 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 | 150 | inputSchema: fsWriteInputJsonSchema, |
| 145 | 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 | 236 | registerSystemTool( |
| 204 | 237 | "kv.list", |
| 205 | 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 | 254 | registerSystemTool( |
| 213 | 255 | "kv.delete", |
| 214 | 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 | 317 | registerSystemTool( |
| 222 | 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 | 321 | inputSchema: sqliteQueryInputJsonSchema, |
| 226 | 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 | 351 | async kvList(instance: StorageInstanceRecord, prefix: string, limit: number): Promise<Array<{ key: string; value: unknown }>> { |
| 352 | 352 | return await this.withDatabase(instance, false, async (db) => { |
| 353 | 353 | await this.ensureKvTable(db); |
| 354 | + const escapedPrefix = prefix | |
| 355 | + .replaceAll("^", "^^") | |
| 356 | + .replaceAll("%", "^%") | |
| 357 | + .replaceAll("_", "^_"); | |
| 354 | 358 | const rows = rowsFromSqlResult( |
| 355 | 359 | db.exec( |
| 356 | − "SELECT key, value FROM kv_store WHERE key LIKE ? ESCAPE '\\\\' ORDER BY key LIMIT ?", | |
| 357 | − [`${prefix.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`, limit], | |
| 360 | + "SELECT key, value FROM kv_store WHERE key LIKE ? ESCAPE '^' ORDER BY key LIMIT ?", | |
| 361 | + [`${escapedPrefix}%`, limit], | |
| 358 | 362 | ), |
| 359 | 363 | ); |
| 360 | 364 | return rows.map((row) => ({ |
| 361 | 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 | 557 | let cloudflareProviderSingleton: CloudflareRemoteStorageProvider | null = null; |
| 554 | 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 | 594 | function startCloudflareStorageProbe(endpoint: string, token: string) { |
| 557 | 595 | if (cloudflareStorageProbeStarted) { |
| 558 | 596 | return; |
| @@ -594,6 +632,8 @@ function startCloudflareStorageProbe(endpoint: string, token: string) { · 35 unmodified lines | ||
| 594 | 632 | } |
| 595 | 633 | |
| 596 | 634 | function getLocalProvider(): AgentFsLocalStorageProvider { |
| 635 | + assertLocalProviderSupported(); | |
| 636 | + | |
| 597 | 637 | if (!localProviderSingleton) { |
| 598 | 638 | const baseDir = (process.env.AGENT_STORAGE_ROOT ?? "").trim() || "/tmp/executor-agentfs"; |
| 599 | 639 | localProviderSingleton = new AgentFsLocalStorageProvider(baseDir); |
| @@ -1,11 +1,64 @@ | ||
| 1 | 1 | import { z } from "zod"; |
| 2 | 2 | |
| 3 | 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 | 54 | function toJsonSchema(schema: z.ZodTypeAny, fallback: JsonSchema): JsonSchema { |
| 6 | 55 | const maybeToJsonSchema = (z as unknown as { toJSONSchema?: (value: z.ZodTypeAny) => unknown }).toJSONSchema; |
| 7 | 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 | 64 | return fallback; |
| @@ -16,6 +69,15 @@ export const storageDurabilitySchema = z.enum(["ephemeral", "durable"]); · 4 unmodified lines | ||
| 16 | 69 | export const storageStatusSchema = z.enum(["active", "closed", "deleted"]); |
| 17 | 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 | 81 | export const storageInstanceSchema = z.object({ |
| 20 | 82 | id: z.string(), |
| 21 | 83 | scopeType: storageScopeSchema, |
| @@ -165,12 +227,12 @@ export const kvGetOutputSchema = z.object({ · 143 unmodified lines | ||
| 165 | 227 | instanceId: z.string(), |
| 166 | 228 | key: z.string(), |
| 167 | 229 | found: z.boolean(), |
| 168 | − value: z.unknown().optional(), | |
| 230 | + value: jsonValueSchema.optional(), | |
| 169 | 231 | }); |
| 170 | 232 | |
| 171 | 233 | export const kvSetInputSchema = fsAccessSchema.extend({ |
| 172 | 234 | key: z.string(), |
| 173 | − value: z.unknown(), | |
| 235 | + value: jsonValueSchema, | |
| 174 | 236 | }); |
| 175 | 237 | |
| 176 | 238 | export const kvSetOutputSchema = z.object({ |
| @@ -186,7 +248,7 @@ export const kvListInputSchema = fsAccessSchema.extend({ · 9 unmodified lines | ||
| 186 | 248 | |
| 187 | 249 | export const kvListOutputSchema = z.object({ |
| 188 | 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 | 252 | total: z.number(), |
| 191 | 253 | }); |
| 192 | 254 | |
| @@ -200,6 +262,20 @@ export const kvDeleteOutputSchema = z.object({ · 7 unmodified lines | ||
| 200 | 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 | 279 | export const sqliteQueryInputSchema = fsAccessSchema.extend({ |
| 204 | 280 | sql: z.string(), |
| 205 | 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 | 286 | export const sqliteQueryOutputSchema = z.object({ |
| 211 | 287 | instanceId: z.string(), |
| 212 | 288 | mode: z.enum(["read", "write"]), |
| 213 | − rows: z.array(z.record(z.unknown())).optional(), | |
| 289 | + rows: z.array(z.record(jsonValueSchema)).optional(), | |
| 214 | 290 | rowCount: z.number(), |
| 215 | 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 | 338 | export const storageOpenInputJsonSchema = toJsonSchema(storageOpenInputSchema, { |
| 219 | 339 | type: "object", |
| 220 | 340 | properties: { |
| @@ -280,8 +400,15 @@ export const storageDeleteOutputJsonSchema = storageCloseOutputJsonSchema; · 59 unmodified lines | ||
| 280 | 400 | |
| 281 | 401 | function fsAccessJsonProperties() { |
| 282 | 402 | return { |
| 283 | − instanceId: { type: "string" }, | |
| 284 | − scopeType: { type: "string", enum: ["scratch", "account", "workspace", "organization"] }, | |
| 403 | + instanceId: { | |
| 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 | 565 | instanceId: { type: "string" }, |
| 439 | 566 | key: { type: "string" }, |
| 440 | 567 | found: { type: "boolean" }, |
| 441 | − value: {}, | |
| 568 | + value: jsonValueJsonSchema, | |
| 442 | 569 | }, |
| 443 | 570 | required: ["instanceId", "key", "found"], |
| 444 | 571 | additionalProperties: false, |
| @@ -449,7 +576,7 @@ export const kvSetInputJsonSchema = toJsonSchema(kvSetInputSchema, { · 4 unmodified lines | ||
| 449 | 576 | properties: { |
| 450 | 577 | ...fsAccessJsonProperties(), |
| 451 | 578 | key: { type: "string" }, |
| 452 | − value: {}, | |
| 579 | + value: jsonValueJsonSchema, | |
| 453 | 580 | }, |
| 454 | 581 | required: ["key", "value"], |
| 455 | 582 | additionalProperties: false, |
| @@ -480,7 +607,18 @@ export const kvListOutputJsonSchema = toJsonSchema(kvListOutputSchema, { · 24 unmodified lines | ||
| 480 | 607 | type: "object", |
| 481 | 608 | properties: { |
| 482 | 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 | 622 | total: { type: "number" }, |
| 485 | 623 | }, |
| 486 | 624 | required: ["instanceId", "items", "total"], |
| @@ -508,19 +646,52 @@ export const kvDeleteOutputJsonSchema = toJsonSchema(kvDeleteOutputSchema, { · 21 unmodified lines | ||
| 508 | 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 | 674 | export const sqliteQueryInputJsonSchema = toJsonSchema(sqliteQueryInputSchema, { |
| 512 | 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 | 678 | properties: { |
| 514 | 679 | ...fsAccessJsonProperties(), |
| 515 | − sql: { type: "string" }, | |
| 680 | + sql: { type: "string", description: "SQL statement text to execute." }, | |
| 516 | 681 | params: { |
| 517 | 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 | 685 | items: { |
| 519 | 686 | oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }, { type: "null" }], |
| 520 | 687 | }, |
| 521 | 688 | }, |
| 522 | − mode: { type: "string", enum: ["read", "write"] }, | |
| 523 | − maxRows: { type: "number" }, | |
| 689 | + mode: { | |
| 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 | 696 | required: ["sql"], |
| 526 | 697 | additionalProperties: false, |
| @@ -531,10 +702,90 @@ export const sqliteQueryOutputJsonSchema = toJsonSchema(sqliteQueryOutputSchema, · 4 unmodified lines | ||
| 531 | 702 | properties: { |
| 532 | 703 | instanceId: { type: "string" }, |
| 533 | 704 | mode: { type: "string", enum: ["read", "write"] }, |
| 534 | − rows: { type: "array", items: { type: "object" } }, | |
| 705 | + rows: { type: "array", items: { type: "object", additionalProperties: jsonValueJsonSchema } }, | |
| 535 | 706 | rowCount: { type: "number" }, |
| 536 | 707 | changes: { type: "number" }, |
| 537 | 708 | }, |
| 538 | 709 | required: ["instanceId", "mode", "rowCount"], |
| 539 | 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 | 19 | kvDeleteOutputSchema, |
| 20 | 20 | kvGetInputSchema, |
| 21 | 21 | kvGetOutputSchema, |
| 22 | + kvIncrInputSchema, | |
| 23 | + kvIncrOutputSchema, | |
| 22 | 24 | kvListInputSchema, |
| 23 | 25 | kvListOutputSchema, |
| 24 | 26 | kvSetInputSchema, |
| 25 | 27 | kvSetOutputSchema, |
| 28 | + sqliteCapabilitiesInputSchema, | |
| 29 | + sqliteCapabilitiesOutputSchema, | |
| 30 | + sqliteInsertRowsInputSchema, | |
| 31 | + sqliteInsertRowsOutputSchema, | |
| 26 | 32 | sqliteQueryInputSchema, |
| 27 | 33 | sqliteQueryOutputSchema, |
| 28 | 34 | storageCloseInputSchema, |
| @@ -35,6 +41,14 @@ import { · 6 unmodified lines | ||
| 35 | 41 | storageOpenOutputSchema, |
| 36 | 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 | 52 | const STORAGE_SYSTEM_TOOLS = new Set([ |
| 39 | 53 | "storage.open", |
| 40 | 54 | "storage.list", |
| @@ -48,11 +62,27 @@ const STORAGE_SYSTEM_TOOLS = new Set([ · 7 unmodified lines | ||
| 48 | 62 | "fs.remove", |
| 49 | 63 | "kv.get", |
| 50 | 64 | "kv.set", |
| 65 | + "kv.put", | |
| 66 | + "kv.create", | |
| 67 | + "kv.update", | |
| 51 | 68 | "kv.list", |
| 69 | + "kv.keys", | |
| 52 | 70 | "kv.delete", |
| 71 | + "kv.del", | |
| 72 | + "kv.has", | |
| 73 | + "kv.exists", | |
| 74 | + "kv.value", | |
| 75 | + "kv.incr", | |
| 76 | + "kv.decr", | |
| 53 | 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 | 86 | function toInputRecord(input: unknown): Record<string, unknown> { |
| 57 | 87 | if (!input || typeof input !== "object" || Array.isArray(input)) { |
| 58 | 88 | return {}; |
| @@ -70,6 +100,107 @@ function normalizeScopeType(value: unknown): undefined | "scratch" | "account" | · 11 unmodified lines | ||
| 70 | 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 | 204 | function normalizeInstanceId(value: unknown): string | undefined { |
| 74 | 205 | if (typeof value !== "string") return undefined; |
| 75 | 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 | 297 | async function openStorageInstanceForTask( |
| 89 | 298 | ctx: ActionCtx, |
| 90 | 299 | task: TaskRecord, |
| @@ -114,7 +323,7 @@ async function resolveStorageInstance( · 23 unmodified lines | ||
| 114 | 323 | payload: Record<string, unknown>, |
| 115 | 324 | ): Promise<StorageInstanceRecord> { |
| 116 | 325 | const requestedInstanceId = normalizeInstanceId(payload.instanceId); |
| 117 | − const scopeType = normalizeScopeType(payload.scopeType); | |
| 326 | + const requestedScopeType = normalizeScopeType(payload.scopeType); | |
| 118 | 327 | |
| 119 | 328 | if (requestedInstanceId) { |
| 120 | 329 | const existing = await ctx.runQuery(internal.database.getStorageInstance, { |
| @@ -129,13 +338,55 @@ async function resolveStorageInstance( · 8 unmodified lines | ||
| 129 | 338 | const reopened = await openStorageInstanceForTask(ctx, task, { |
| 130 | 339 | instanceId: requestedInstanceId, |
| 131 | 340 | }); |
| 341 | + await saveTaskStorageDefault(ctx, task, reopened.scopeType, reopened.id, true, "provided"); | |
| 132 | 342 | return reopened; |
| 133 | 343 | } |
| 134 | 344 | |
| 135 | − return await openStorageInstanceForTask(ctx, task, { | |
| 136 | − scopeType, | |
| 137 | − purpose: "auto", | |
| 345 | + const defaults = await getTaskStorageDefaults(ctx, task); | |
| 346 | + | |
| 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 | 392 | async function touchInstance( |
| @@ -167,14 +418,43 @@ export async function runStorageSystemTool( · 25 unmodified lines | ||
| 167 | 418 | input: unknown, |
| 168 | 419 | ): Promise<unknown> { |
| 169 | 420 | const payload = normalizeInputPayload(input); |
| 170 | − | |
| 171 | − if (toolPath === "storage.open") { | |
| 421 | + const normalizedToolPath = toolPath === "kv.put" | |
| 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 | 444 | const parsed = storageOpenInputSchema.parse(payload); |
| 173 | 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 | 454 | return storageOpenOutputSchema.parse({ instance }); |
| 175 | 455 | } |
| 176 | 456 | |
| 177 | − if (toolPath === "storage.list") { | |
| 457 | + if (normalizedToolPath === "storage.list") { | |
| 178 | 458 | const parsed = storageListInputSchema.parse(payload); |
| 179 | 459 | const instances = await ctx.runQuery(internal.database.listStorageInstances, { |
| 180 | 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 | 473 | const parsed = storageCloseInputSchema.parse(payload); |
| 194 | 474 | const instance = await ctx.runMutation(internal.database.closeStorageInstance, { |
| 195 | 475 | workspaceId: task.workspaceId, |
| 196 | 476 | accountId: task.accountId, |
| 197 | 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 | 486 | return storageCloseOutputSchema.parse({ instance }); |
| 200 | 487 | } |
| 201 | 488 | |
| 202 | − if (toolPath === "storage.delete") { | |
| 489 | + if (normalizedToolPath === "storage.delete") { | |
| 203 | 490 | const parsed = storageDeleteInputSchema.parse(payload); |
| 204 | 491 | const existing = await ctx.runQuery(internal.database.getStorageInstance, { |
| 205 | 492 | workspaceId: task.workspaceId, |
| @@ -221,10 +508,17 @@ export async function runStorageSystemTool( · 15 unmodified lines | ||
| 221 | 508 | accountId: task.accountId, |
| 222 | 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 | 518 | return storageDeleteOutputSchema.parse({ instance }); |
| 225 | 519 | } |
| 226 | 520 | |
| 227 | − if (toolPath === "fs.read") { | |
| 521 | + if (normalizedToolPath === "fs.read") { | |
| 228 | 522 | const parsed = fsReadInputSchema.parse(payload); |
| 229 | 523 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 230 | 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 | 538 | const parsed = fsWriteInputSchema.parse(payload); |
| 245 | 539 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 246 | 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 | 552 | const parsed = fsReaddirInputSchema.parse(payload); |
| 259 | 553 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 260 | 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 | 566 | const parsed = fsStatInputSchema.parse(payload); |
| 273 | 567 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 274 | 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 | 579 | const parsed = fsMkdirInputSchema.parse(payload); |
| 286 | 580 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 287 | 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 | 592 | const parsed = fsRemoveInputSchema.parse(payload); |
| 299 | 593 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 300 | 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 | 608 | const parsed = kvGetInputSchema.parse(payload); |
| 315 | 609 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 316 | 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 | 622 | const parsed = kvSetInputSchema.parse(payload); |
| 329 | 623 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 330 | 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 | 635 | const parsed = kvListInputSchema.parse(payload); |
| 342 | 636 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 343 | 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 | 649 | const parsed = kvDeleteInputSchema.parse(payload); |
| 356 | 650 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 357 | 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 | 785 | const parsed = sqliteQueryInputSchema.parse(payload); |
| 369 | 786 | const instance = await resolveStorageInstance(ctx, task, payload); |
| 370 | 787 | const provider = getStorageProvider(instance.provider); |
| 371 | − const mode = parsed.mode ?? "read"; | |
| 788 | + const mode = parsed.mode ?? (isReadOnlySql(parsed.sql) ? "read" : "write"); | |
| 372 | 789 | const maxRows = Math.max(1, Math.min(1_000, Math.floor(parsed.maxRows ?? 200))); |
| 373 | − const result = await provider.sqliteQuery(instance, { | |
| 374 | − sql: parsed.sql, | |
| 375 | − params: parsed.params ?? [], | |
| 376 | − mode, | |
| 377 | − maxRows, | |
| 378 | − }); | |
| 790 | + const params = parsed.params ?? []; | |
| 791 | + let result; | |
| 792 | + try { | |
| 793 | + result = await provider.sqliteQuery(instance, { | |
| 794 | + sql: parsed.sql, | |
| 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 | 806 | await touchInstance(ctx, task, instance, provider, mode === "write"); |
| 380 | 807 | return sqliteQueryOutputSchema.parse({ |
| 381 | 808 | instanceId: instance.id, |
| @@ -159,7 +159,9 @@ function unknownToolErrorMessage(toolPath: string, suggestions: string[]): strin · 158 unmodified lines | ||
| 159 | 159 | : ""; |
| 160 | 160 | const queryHint = toolPath.split(".").filter(Boolean).join(" "); |
| 161 | 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 | 167 | export async function resolveToolForCall( |
| @@ -12,6 +12,10 @@ import type { · 11 unmodified lines | ||
| 12 | 12 | ToolRunContext, |
| 13 | 13 | } from "../../../core/src/types"; |
| 14 | 14 | import { executeSerializedTool, parseSerializedTool } from "../../../core/src/tool/source-serialization"; |
| 15 | +import { | |
| 16 | + compactArgTypeHintFromSchema, | |
| 17 | + compactReturnTypeHintFromSchema, | |
| 18 | +} from "../../../core/src/type-hints"; | |
| 15 | 19 | import { describeError } from "../../../core/src/utils"; |
| 16 | 20 | import { |
| 17 | 21 | decodeToolCallControlSignal, |
| @@ -239,6 +243,53 @@ async function listRegistryNamespaces( · 221 unmodified lines | ||
| 239 | 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 | 293 | async function searchRegistryTools( |
| 243 | 294 | ctx: ActionCtx, |
| 244 | 295 | args: { |
| @@ -403,13 +454,12 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 158 unmodified lines | ||
| 403 | 454 | accountId: task.accountId, |
| 404 | 455 | clientId: task.clientId, |
| 405 | 456 | }); |
| 406 | − if (buildIdResult.isErr()) { | |
| 407 | − throw buildIdResult.error; | |
| 408 | − } | |
| 409 | − const buildId = buildIdResult.value; | |
| 410 | − const state = await ctx.runQuery(internal.toolRegistry.getState, { | |
| 411 | − workspaceId: task.workspaceId, | |
| 412 | − }) as unknown as { openApiRefHintTables?: unknown } | null; | |
| 457 | + const buildId = buildIdResult.isErr() ? undefined : buildIdResult.value; | |
| 458 | + const state = buildId | |
| 459 | + ? await ctx.runQuery(internal.toolRegistry.getState, { | |
| 460 | + workspaceId: task.workspaceId, | |
| 461 | + }) as unknown as { openApiRefHintTables?: unknown } | null | |
| 462 | + : null; | |
| 413 | 463 | const refHintLookup = buildOpenApiRefHintLookup(state?.openApiRefHintTables); |
| 414 | 464 | |
| 415 | 465 | const payload = typeof input === "string" |
| @@ -439,11 +489,45 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 23 unmodified lines | ||
| 439 | 489 | const parsedInput = catalogNamespacesInputSchema.safeParse(payload); |
| 440 | 490 | const limitInput = parsedInput.success ? parsedInput.data.limit : undefined; |
| 441 | 491 | const limit = Math.max(1, Math.min(200, Number(limitInput ?? 200))); |
| 442 | − const namespaces = await listRegistryNamespaces(ctx, { | |
| 443 | − workspaceId: task.workspaceId, | |
| 444 | − buildId, | |
| 445 | − limit, | |
| 446 | − }); | |
| 492 | + const registryNamespaces = buildId | |
| 493 | + ? await listRegistryNamespaces(ctx, { | |
| 494 | + workspaceId: task.workspaceId, | |
| 495 | + buildId, | |
| 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 | 531 | const output = catalogNamespacesOutputSchema.parse({ |
| 448 | 532 | namespaces, |
| 449 | 533 | total: namespaces.length, |
| @@ -459,29 +543,47 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 9 unmodified lines | ||
| 459 | 543 | const limit = Math.max(1, Math.min(200, Number(limitInput ?? 50))); |
| 460 | 544 | const includeSchemas = parsedInput.success ? (parsedInput.data.includeSchemas ?? false) : false; |
| 461 | 545 | |
| 462 | − const raw = query | |
| 463 | − ? await searchRegistryTools(ctx, { | |
| 464 | − workspaceId: task.workspaceId, | |
| 465 | − buildId, | |
| 466 | − query, | |
| 467 | − limit, | |
| 468 | − includeSchemas, | |
| 469 | − }) | |
| 470 | − : namespace | |
| 471 | − ? await listRegistryToolsByNamespace(ctx, { | |
| 546 | + const raw = !buildId | |
| 547 | + ? [] | |
| 548 | + : query | |
| 549 | + ? await searchRegistryTools(ctx, { | |
| 472 | 550 | workspaceId: task.workspaceId, |
| 473 | 551 | buildId, |
| 474 | − namespace, | |
| 552 | + query, | |
| 475 | 553 | limit, |
| 476 | 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 | 583 | const refHintTable: Record<string, string> = {}; |
| 481 | − const results = raw | |
| 584 | + const registryResults = raw | |
| 482 | 585 | .filter((entry) => !namespace || String(entry.preferredPath ?? entry.path ?? "").toLowerCase().startsWith(`${namespace}.`)) |
| 483 | 586 | .filter((entry) => isAllowed(entry.path, entry.approval, entry.source)) |
| 484 | − .slice(0, limit) | |
| 485 | 587 | .map((entry) => { |
| 486 | 588 | const preferredPath = entry.preferredPath ?? entry.path; |
| 487 | 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 | 637 | const output = catalogToolsOutputSchema.parse({ |
| 508 | 638 | ...(Object.keys(refHintTable).length > 0 ? { refHintTable } : {}), |
| 509 | 639 | results, |
| @@ -519,20 +649,31 @@ export async function invokeTool(ctx: ActionCtx, task: TaskRecord, call: ToolCal · 9 unmodified lines | ||
| 519 | 649 | const limit = Math.max(1, Math.min(50, Number(limitInput ?? 8))); |
| 520 | 650 | const compact = parsedInput.success ? (parsedInput.data.compact ?? true) : true; |
| 521 | 651 | const includeSchemas = parsedInput.success ? (parsedInput.data.includeSchemas ?? false) : false; |
| 522 | − const hits = await searchRegistryTools(ctx, { | |
| 523 | − workspaceId: task.workspaceId, | |
| 524 | − buildId, | |
| 525 | − query, | |
| 526 | − limit: Math.max(limit * 2, limit), | |
| 527 | − includeSchemas, | |
| 528 | − }); | |
| 652 | + const hits = buildId | |
| 653 | + ? await searchRegistryTools(ctx, { | |
| 654 | + workspaceId: task.workspaceId, | |
| 655 | + buildId, | |
| 656 | + query, | |
| 657 | + limit: Math.max(limit * 2, limit), | |
| 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 | 671 | const filtered = hits |
| 531 | 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 | 675 | const refHintTable: Record<string, string> = {}; |
| 535 | − const results = filtered.map((entry) => { | |
| 676 | + const registryResults = filtered.map((entry) => { | |
| 536 | 677 | const preferredPath = entry.preferredPath ?? entry.path; |
| 537 | 678 | const description = compact ? String(entry.description ?? "").split("\n")[0] : entry.description; |
| 538 | 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 | 727 | const bestPath = results[0]?.path ?? null; |
| 559 | 728 | const output = discoverOutputSchema.parse({ |
| 560 | 729 | bestPath, |
| @@ -320,10 +320,13 @@ export class AgentFSStorageObject extends DurableObject<Env> { · 319 unmodified lines | ||
| 320 | 320 | const limit = typeof payload.limit === "number" && Number.isFinite(payload.limit) |
| 321 | 321 | ? Math.max(1, Math.min(500, Math.floor(payload.limit))) |
| 322 | 322 | : 100; |
| 323 | − const escaped = prefix.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_"); | |
| 323 | + const escaped = prefix | |
| 324 | + .replaceAll("^", "^^") | |
| 325 | + .replaceAll("%", "^%") | |
| 326 | + .replaceAll("_", "^_"); | |
| 324 | 327 | const rows = this.ctx.storage.sql |
| 325 | 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 | 330 | `${escaped}%`, |
| 328 | 331 | limit, |
| 329 | 332 | ) |
| @@ -81,6 +81,15 @@ function parseWorkersDevUrl(output: string): string | null { · 80 unmodified lines | ||
| 81 | 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 | 93 | async function deployCloudflareWorkerAndGetUrl(): Promise<string> { |
| 85 | 94 | console.log("[storage:smoke:execute-flow] deploying runner-sandbox-host..."); |
| 86 | 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 | 181 | const requestedRuntime: RuntimeId = options.runtimeId |
| 173 | 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 | 190 | await maybeDeployConvex(options.deployConvex); |
| 176 | 191 | |
| 177 | 192 | if (requestedRuntime === "cloudflare-worker-loader") { |