| name | agentuity-ai |
| description | Use when building model-backed Agentuity features, AI routes, structured output, chat, streaming, tool calling, or app-owned state and memory. Covers plain server functions, AIGatewayClient, provider SDK choices, validation, KV-backed history, and safe tool execution. |
| license | Apache-2.0 |
| metadata | {"author":"agentuity","version":"1.0.0"} |
Agentuity AI Features
An Agentuity agent is model-backed app code with a clear task. Put the task in a
plain server-side function, validate its inputs and outputs, then call it from
the framework route, worker, schedule, script, or queue handler that owns the
trigger.
Minimal Shape
npm install @agentuity/aigateway @agentuity/keyvalue zod
import { AIGatewayClient } from '@agentuity/aigateway';
import { KeyValueClient } from '@agentuity/keyvalue';
import { z } from 'zod';
const gateway = new AIGatewayClient();
const kv = new KeyValueClient();
const TRIAGE_MODEL = 'googleai/gemini-3-flash-preview';
const triageSchema = z.object({
priority: z.enum(['low', 'normal', 'high']),
summary: z.string(),
});
type TriageResult = z.infer<typeof triageSchema>;
export async function triageMessage(
customerId: string,
message: string
): Promise<TriageResult> {
const previous = await kv.get<TriageResult>('support-triage', customerId);
const { data } = await gateway.completeStructured({
model: TRIAGE_MODEL,
messages: [
{
role: 'system',
content: 'Classify support messages for a product engineering team.',
},
{
role: 'user',
content: [
`Customer message: ${message}`,
previous.exists ? `Previous summary: ${previous.data.summary}` : '',
].filter(Boolean).join('\n\n'),
},
],
response_schema: {
name: 'triage_result',
schema: triageSchema,
},
});
const output = triageSchema.parse(data);
await kv.set('support-triage', customerId, output, { ttl: 60 * 60 * 24 * 30 });
return output;
}
Keep the route thin:
import { triageMessage } from '@/lib/triage';
import { z } from 'zod';
const inputSchema = z.object({
customerId: z.string(),
message: z.string(),
});
export async function POST(request: Request): Promise<Response> {
const input = inputSchema.parse(await request.json());
const result = await triageMessage(input.customerId, input.message);
return Response.json(result);
}
Model Choice
Use the live Gateway catalog before hardcoding model IDs:
agentuity cloud aigateway models --recommended
agentuity cloud aigateway models --ids
agentuity cloud aigateway models --provider openai
Use AIGatewayClient when model choice should be project configuration. Use a
provider SDK or AI SDK provider when the feature depends on provider-specific
APIs.
| Need | Use |
|---|
| Plain text response | gateway.completeText() |
| Provider-agnostic JSON | gateway.completeStructured() plus validation |
| Raw provider-shaped response | gateway.complete() |
| SSE passthrough | gateway.streamRequest() |
| Automatic tool loop | AI SDK or provider SDK |
AIGatewayClient reads Agentuity project credentials from the environment. The
model ID is app config, not an extra secret.
Chat and Streaming
Use streaming when users should see output as it arrives. Store chat state
explicitly, usually after the stream completes:
import { AIGatewayClient } from '@agentuity/aigateway';
import { KeyValueClient } from '@agentuity/keyvalue';
interface ChatMessage {
role: 'user' | 'assistant';
content: string;
}
const gateway = new AIGatewayClient();
const kv = new KeyValueClient();
const DEFAULT_MODEL = 'deepseek/deepseek-v4-flash';
export async function streamChat(
conversationId: string,
message: string
): Promise<Response> {
const stored = await kv.get<ChatMessage[]>('chat-history', conversationId);
const history = stored.exists ? stored.data : [];
const next = [...history, { role: 'user' as const, content: message }];
const { stream } = await gateway.streamRequest({
path: '/',
body: { model: DEFAULT_MODEL, stream: true, messages: next },
});
return new Response(stream, {
headers: { 'content-type': 'text/event-stream' },
});
}
If you need to persist the assistant turn, tee the stream and parse the copy in a
background promise owned by your route or worker.
Tools
Tools should be bounded app functions:
| Good tool | Avoid |
|---|
| Read one KV or database record | Broad unbounded scans |
| Create a small task or enqueue a job | Long-running work inside the model call |
| Return compact typed data | Returning huge files or transcripts |
| Propose a risky action for approval | Irreversible side effects without a gate |
Use an idempotency key or stored approval record for side effects. Never let a
model directly execute irreversible work without an app-owned decision boundary.
State and Memory
State belongs to the app:
| State need | Store |
|---|
| Browser session identity | Signed cookie or auth session |
| Compact memory or preferences | Key-Value Storage |
| Relational user data | Database |
| Generated files or transcripts | Durable Streams or Object Storage |
| Async job status | KV status record plus Queue |
Validate data when reading from KV or databases. Old records, manual test data,
and partial writes are normal production boundaries.
Common Mistakes
| Mistake | Better approach |
|---|
| Putting model calls directly in UI components | Call a server route or server function |
| Trusting structured model data as typed | Validate with Zod, Valibot, ArkType, or another schema library |
| Storing full transcripts in KV forever | Store summaries or pointers; use DB or durable streams for large data |
| Running long jobs inside a request | Enqueue work and return a status handle |
| Hardcoding an unverified model ID | Read the Gateway catalog first |
Useful Docs