|
| 1 | +# TeaQL Agent Data Tools for Vercel AI SDK |
| 2 | + |
| 3 | +Generated, typed, auditable business tools for AI SDK agents—without exposing raw SQL. |
| 4 | + |
| 5 | +> Don't give your AI agent unrestricted SQL. Give it a typed business language. |
| 6 | +
|
| 7 | +`@teaql/ai-sdk` converts an explicit allowlist of TeaQL business capabilities into native [Vercel AI SDK](https://ai-sdk.dev/) tools. The model receives business operations and their schemas. A trusted TeaQL `UserContext`, database resources, authorization state, and internal errors remain on the server. |
| 8 | + |
| 9 | +## Why |
| 10 | + |
| 11 | +| Raw SQL agent | TeaQL agent tools | |
| 12 | +| --- | --- | |
| 13 | +| Model guesses tables and joins | Model selects named business capabilities | |
| 14 | +| Broad database access | Explicit capability allowlist | |
| 15 | +| Untyped rows | Schema-validated input and output | |
| 16 | +| Authorization depends on prompts | Trusted server-side `context` | |
| 17 | +| Mutations are difficult to govern | AI SDK approval plus TeaQL audit semantics | |
| 18 | +| Database errors may leak | Safe public errors and observable internal failures | |
| 19 | +| Database-specific behavior | TeaQL domain semantics can span seven runtimes | |
| 20 | + |
| 21 | +This package does not replace the AI SDK agent loop, model providers, UI, or streaming. It supplies the governed business-data layer beneath those features. |
| 22 | + |
| 23 | +## Install |
| 24 | + |
| 25 | +```bash |
| 26 | +npm install @teaql/ai-sdk @teaql/teaql ai zod |
| 27 | +``` |
| 28 | + |
| 29 | +Node.js 22 or newer and AI SDK 7 are required by the initial release. |
| 30 | + |
| 31 | +## Define business capabilities |
| 32 | + |
| 33 | +Capabilities are an explicit allowlist. The adapter intentionally does not expose every entity and CRUD operation automatically. |
| 34 | + |
| 35 | +```ts |
| 36 | +import { defineTeaQLCapability } from '@teaql/ai-sdk'; |
| 37 | +import { z } from 'zod'; |
| 38 | + |
| 39 | +const searchSchools = defineTeaQLCapability({ |
| 40 | + name: 'searchSchools', |
| 41 | + description: 'Find schools by governed business criteria.', |
| 42 | + inputSchema: z.object({ |
| 43 | + schoolType: z.enum(['PRIMARY', 'SECONDARY']), |
| 44 | + name: z.string().optional(), |
| 45 | + }), |
| 46 | + risk: 'read', |
| 47 | + execute: async ({ context, input }) => |
| 48 | + Q.schools() |
| 49 | + .withSchoolType(input.schoolType) |
| 50 | + .withNameContaining(input.name) |
| 51 | + .comment('AI SDK tool: searchSchools') |
| 52 | + .purpose('Search schools requested by the authenticated user') |
| 53 | + .executeForList(context), |
| 54 | +}); |
| 55 | +``` |
| 56 | + |
| 57 | +The exact generated Q API follows the selected TeaQL model and generator version; capability definitions are ordinary typed application code and compile against it. |
| 58 | + |
| 59 | +## Create native AI SDK tools |
| 60 | + |
| 61 | +```ts |
| 62 | +import { UserContext } from '@teaql/teaql'; |
| 63 | +import { ToolLoopAgent } from 'ai'; |
| 64 | +import { createTeaQLTools } from '@teaql/ai-sdk'; |
| 65 | + |
| 66 | +const context = new UserContext() |
| 67 | + .insertResource('dataService', dataService) |
| 68 | + .insertResource('authorization', authorization); |
| 69 | + |
| 70 | +const agent = new ToolLoopAgent({ |
| 71 | + model: 'openai/gpt-5.4', |
| 72 | + instructions: 'Use only the provided business tools. Never invent SQL.', |
| 73 | + tools: createTeaQLTools({ |
| 74 | + context, |
| 75 | + capabilities: [searchSchools, updateSchoolContactPhone], |
| 76 | + }), |
| 77 | +}); |
| 78 | +``` |
| 79 | + |
| 80 | +Create `context` on the server for each request or session. Never accept it from model output or a browser payload. |
| 81 | + |
| 82 | +## Govern writes |
| 83 | + |
| 84 | +```ts |
| 85 | +const updateSchoolContactPhone = defineTeaQLCapability({ |
| 86 | + name: 'updateSchoolContactPhone', |
| 87 | + description: 'Update a school phone after explicit user approval.', |
| 88 | + inputSchema: z.object({ |
| 89 | + schoolId: z.number().int().positive(), |
| 90 | + contactPhone: z.string(), |
| 91 | + auditReason: z.string().min(8), |
| 92 | + }), |
| 93 | + risk: 'write', |
| 94 | + needsApproval: true, |
| 95 | + execute: async ({ context, input }) => { |
| 96 | + const school = await Q.schools() |
| 97 | + .withId(input.schoolId) |
| 98 | + .comment('Load school for approved contact update') |
| 99 | + .purpose(input.auditReason) |
| 100 | + .executeForOne(context); |
| 101 | + |
| 102 | + return school |
| 103 | + .updateContactPhone(input.contactPhone) |
| 104 | + .auditAs(input.auditReason) |
| 105 | + .save(context); |
| 106 | + }, |
| 107 | +}); |
| 108 | +``` |
| 109 | + |
| 110 | +AI SDK approval controls whether the agent may execute the tool. TeaQL audit and runtime authorization still apply when execution begins. Approval is not a replacement for runtime security. |
| 111 | + |
| 112 | +## Observe execution without leaking internals |
| 113 | + |
| 114 | +```ts |
| 115 | +const tools = createTeaQLTools({ |
| 116 | + context, |
| 117 | + capabilities, |
| 118 | + onEvent: event => telemetry.record(event), |
| 119 | + mapError: (_error, capability) => |
| 120 | + `${capability.name} could not be completed. Review the request or contact support.`, |
| 121 | +}); |
| 122 | +``` |
| 123 | + |
| 124 | +Lifecycle events contain capability name, risk, tool-call ID, timing, and the internal error on the server. Inputs are excluded by default because they may contain sensitive business data. The default model-visible error never includes the original database error. |
| 125 | + |
| 126 | +## Run the local demonstration |
| 127 | + |
| 128 | +The repository includes a deterministic, no-API-key school-management demonstration. It uses an in-memory SQLite resource inside the trusted `UserContext` to show the security boundary, approval metadata, optimistic version change, audit record, and model-visible tools. |
| 129 | + |
| 130 | +```bash |
| 131 | +npm install |
| 132 | +npm run example |
| 133 | +``` |
| 134 | + |
| 135 | +The SQLite repository is deliberately small and handwritten so the example is self-contained. A generated TeaQL project replaces that repository implementation with its generated Q, entity, Save, and Runtime Module APIs; the AI SDK adapter remains unchanged. |
| 136 | + |
| 137 | +See [`examples/school-agent`](examples/school-agent). |
| 138 | + |
| 139 | +## Security model |
| 140 | + |
| 141 | +- Capabilities are deny-by-absence: only definitions passed to `createTeaQLTools` exist. |
| 142 | +- `allow` can narrow the registered capabilities for a particular user or agent. |
| 143 | +- `context` is captured by the server-side execute closure and is not part of `inputSchema`. |
| 144 | +- Tool risk is metadata for policy and telemetry; applications must still enforce authorization in the runtime. |
| 145 | +- Writes can request AI SDK approval, but must also use TeaQL audit and validation. |
| 146 | +- Internal failures are available to server telemetry and hidden from the model by default. |
| 147 | +- Capability names and duplicates are validated during startup. |
| 148 | + |
| 149 | +## Current scope |
| 150 | + |
| 151 | +The initial release is a runtime adapter for explicit TypeScript capability definitions. Planned generator work will produce capability definitions, schemas, agent guidance, and conformance fixtures from a TeaQL model. MCP adapters can expose the same capability manifest to Java, Rust, TypeScript, Swift, Python, .NET, and Go runtimes. |
| 152 | + |
| 153 | +## Related projects |
| 154 | + |
| 155 | +- [TeaQL](https://teaql.io) |
| 156 | +- [TeaQL TypeScript Runtime](https://github.com/teaql/teaql-ts) |
| 157 | +- [TeaQL Code Generator](https://github.com/teaql/teaql-code-gen) |
| 158 | +- [TeaQL Agent Kit](https://github.com/teaql/teaql-agent-kit) |
| 159 | +- [TeaQL Conformance](https://github.com/teaql/teaql-conformance) |
| 160 | +- [Vercel AI SDK](https://github.com/vercel/ai) |
| 161 | + |
| 162 | +## License |
| 163 | + |
| 164 | +Apache-2.0 |
0 commit comments