RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/Cline rules/blendsdk/building-agents-vercel-ai-sdk

Cline rules

.clinerules/ai-sdk.md
Cline rules

Quality

57/100

Scores the file, not the repository.

Length

1,518 words

13 headings · 8 code blocks

Repository

1

— · pushed 57 days ago

Last changed

3 days ago

First indexed 3 days ago.
blendsdk/building-agents-vercel-ai-sdk/.clinerules/ai-sdk.mdRawGitHub
1# Vercel AI SDK — Conventions & Gotchas
2 
3Project rules for using the Vercel AI SDK (`ai` package) in this repo.
4Installed: `ai@6` + `@ai-sdk/openai@3`. Provider: OpenAI (`OPENAI_API_KEY` in `.env`).
5 
6## ⚠️ Deprecation: do NOT use `generateObject` / `streamObject`
7 
8In AI SDK v6 these are **deprecated**. Confirmed directly in the installed type
9definitions (`node_modules/ai/dist/index.d.ts`):
10 
11- `generateObject` → `@deprecated Use generateText with an output setting instead.`
12- `streamObject` → `@deprecated Use streamText with an output setting instead.`
13- `experimental_output` → `@deprecated Use 'output' instead.`
14 
15### ✅ Correct: structured output via `generateText` / `streamText` + `Output`
16 
17Use the same text/agent functions you already know, and pass an `output` setting
18built with the `Output` helper. Read the typed result from `result.output`.
19 
20```ts
21import { generateText, streamText, Output, stepCountIs } from "ai";
22import { openai } from "@ai-sdk/openai";
23import { z } from "zod";
24 
25// --- One-shot structured object ---
26const { output } = await generateText({
27 model: openai("gpt-4o-mini"),
28 prompt: "…",
29 output: Output.object({ schema: z.object({ /* … */ }) }),
30});
31// `output` is fully typed and validated against the schema.
32 
33// --- Streaming a structured object ---
34const result = streamText({
35 model: openai("gpt-4o-mini"),
36 prompt: "…",
37 output: Output.object({ schema }),
38});
39for await (const partial of result.partialOutputStream) {
40 // partial = object-so-far (fields may be undefined until filled)
41}
42const final = await result.output; // PromiseLike<final validated object>
43 
44// --- Tools + structured final answer (full agent combo) ---
45const { output } = await generateText({
46 model: openai("gpt-4o-mini"),
47 tools,
48 stopWhen: stepCountIs(10),
49 prompt: "…",
50 output: Output.object({ schema }),
51});
52```
53 
54### `Output` helpers (from `import { Output } from "ai"`)
55- `Output.object({ schema })` — a single JSON object matching a Zod schema.
56- `Output.array({ element })` — an array of elements; `streamText` also exposes
57 an element-by-element stream.
58- `Output.text()` — plain text (the default).
59- `Output.choice({ options })` — constrain to a fixed set of string options.
60- `Output.json()` — free-form JSON (no schema).
61 
62> Note: `Output` is exported as `output as Output` in the package; import it as
63> `import { Output } from "ai"`.
64 
65## The agent loop
66 
67- An "agent" = an LLM in a loop that can call tools. Enable the loop with
68 `stopWhen: stepCountIs(N)` on `generateText` / `streamText`. Without it, the
69 call stops after the first tool call and never reaches a final answer.
70- Inspect what the agent did via `result.steps` (`toolCalls`, `toolResults`).
71 
72## Reusable agents: the `ToolLoopAgent` class
73 
74For a named, reusable agent (instead of re-wiring `generateText` every call), use
75the `ToolLoopAgent` class. Configure once, then call `.generate()` / `.stream()`.
76 
77```ts
78import { ToolLoopAgent, stepCountIs, Output } from "ai";
79import { openai } from "@ai-sdk/openai";
80 
81const assistant = new ToolLoopAgent({
82 model: openai("gpt-4o-mini"),
83 instructions: "You are a concise assistant.", // ⚠️ NOT `system` — it's `instructions`
84 tools,
85 stopWhen: stepCountIs(10),
86 output: Output.object({ schema }), // optional: bake in structured JSON output
87});
88 
89const result = await assistant.generate({ prompt: "…" }); // same shape as generateText
90const streamed = await assistant.stream({ prompt: "…" }); // same shape as streamText
91```
92 
93- The system prompt field is named **`instructions`** here (not `system`).
94- `.generate()` returns a `generateText`-style result (`.text`, `.steps`,
95 `.output`, `.usage`); `.stream()` returns a `streamText`-style result.
96- `ToolLoopAgent` is also exported as `Experimental_Agent`; the `Agent` export is
97 the interface it implements.
98 
99 
100## RAG: embeddings + retrieval
101 
102To ground an agent in your own documents, embed them and do similarity search.
103 
104```ts
105import { embed, embedMany, cosineSimilarity } from "ai";
106import { openai } from "@ai-sdk/openai";
107 
108const embeddingModel = openai.embedding("text-embedding-3-small"); // ⚠️ `embedding`, NOT `textEmbedding` (deprecated)
109 
110// Build a vector store (batch embed your docs):
111const { embeddings } = await embedMany({ model: embeddingModel, values: docs });
112 
113// At query time, embed the query and rank by cosine similarity:
114const { embedding: queryVec } = await embed({ model: embeddingModel, value: query });
115const ranked = store
116 .map((d) => ({ ...d, score: cosineSimilarity(queryVec, d.embedding) }))
117 .sort((a, b) => b.score - a.score);
118```
119 
120- `embed` → returns `{ embedding }` (single vector); `embedMany` → `{ embeddings }`
121 (array, same order as `values`).
122- Embed the query with the **same** model used for the documents.
123- Expose retrieval as a **tool** (`searchKnowledge`) so the agent fetches context
124 on demand; instruct it to answer only from retrieved docs and to say when it
125 doesn't know.
126 
127## Robust agents: error handling & tool repair
128 
129Make agents survive failures instead of crashing.
130 
131```ts
132import { generateText, stepCountIs, NoSuchToolError, type ToolCallRepairFunction } from "ai";
133 
134// 1) Tools should RETURN structured errors, not throw — the model reads the
135// result and recovers (apologize, retry, pick another tool).
136execute: async ({ amount }) => {
137 if (amount > available) return { ok: false, error: "INSUFFICIENT_FUNDS", message: "…" };
138 return { ok: true, /* … */ };
139},
140 
141// 2) Repair malformed tool calls via the `experimental_repairToolCall` hook.
142// Return a corrected LanguageModelV3ToolCall, or `null` to let it fail.
143const repairToolCall: ToolCallRepairFunction<typeof tools> = async ({ toolCall, error, tools }) => {
144 if (NoSuchToolError.isInstance(error)) return null; // hallucinated tool → can't fix
145 const match = (toolCall.input ?? "").match(/\{[\s\S]*\}/); // salvage JSON from bad input
146 if (!match) return null;
147 try { JSON.parse(match[0]); return { ...toolCall, input: match[0] }; } catch { return null; }
148};
149 
150await generateText({ model, tools, stopWhen: stepCountIs(6), experimental_repairToolCall: repairToolCall });
151```
152 
153- The repair hook's `error` is `NoSuchToolError | InvalidToolInputError`; use
154 `NoSuchToolError.isInstance(error)` to distinguish them.
155- `toolCall.input` is the raw (string) args; return `{ ...toolCall, input }` to retry.
156 
157## Human-in-the-loop: approval gates for sensitive tools
158 
159Before an agent takes a real action (send email, delete, move money), gate it
160behind human approval. Simple CLI pattern: the sensitive tool's `execute` pauses
161and asks the human, then returns a structured result either way.
162 
163```ts
164const sendEmail = tool({
165 description: "Send an email. Sensitive — requires human approval.",
166 inputSchema: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
167 execute: async ({ to, subject, body }) => {
168 console.log(`Agent wants to email ${to}: ${subject}`);
169 const approved = await askApproval("Approve sending?"); // readline y/N
170 if (!approved) return { ok: false, status: "DENIED_BY_HUMAN" }; // agent adapts
171 return { ok: true, status: "SENT" };
172 },
173});
174```
175 
176- The agent loop is unchanged — the gate lives inside the tool. Safe (read-only)
177 tools run without prompting.
178- Return a **structured "denied"** result (don't throw) so the agent acknowledges
179 the refusal gracefully; instruct it not to retry.
180- Default to **deny** on empty/EOF input (fail safe).
181- The SDK also has a first-class approval flow (`needsApproval` on a tool +
182 `ToolApprovalRequest`/`addToolApprovalResponse`), but that's geared to the
183 Chat/UI message stream; the in-`execute` prompt above is simplest for a CLI.
184 
185## Multi-agent orchestration: agents as tools
186 
187To build a TEAM, make a "supervisor" agent that delegates to specialist agents.
188The trick: **wrap each specialist `ToolLoopAgent` as a tool** the supervisor calls.
189 
190```ts
191// A specialist agent (Tutorial 4):
192const researchAgent = new ToolLoopAgent({ model, instructions: "…", tools: { searchKnowledge }, stopWhen: stepCountIs(5) });
193 
194// Wrap it as a tool:
195const askResearcher = tool({
196 description: "Delegate a factual/lookup question to the research specialist.",
197 inputSchema: z.object({ question: z.string() }),
198 execute: async ({ question }) => {
199 const { text } = await researchAgent.generate({ prompt: question });
200 return { answer: text };
201 },
202});
203 
204// The supervisor just calls specialists like any other tool:
205const supervisor = new ToolLoopAgent({
206 model,
207 instructions: "Break the request into sub-tasks and delegate; never compute/recall yourself.",
208 tools: { askResearcher, askMathematician },
209 stopWhen: stepCountIs(10),
210});
211```
212 
213- Inspect `result.steps[].toolCalls` to see which specialist was used for what.
214- Give each specialist a narrow `instructions` + tool set; tell the supervisor to
215 always delegate (not answer directly) so routing is reliable.
216 
217## Observability: steps, token usage & cost
218 
219Trace what an agent did and what it cost.
220 
221```ts
222const result = await generateText({
223 model, tools, stopWhen: stepCountIs(10), prompt: "…",
224 onStepFinish: ({ toolCalls, usage }) => { // live, fires after each step
225 console.log(toolCalls.map(c => c.toolName), usage.totalTokens);
226 },
227});
228 
229// Post-run trace: every step has toolCalls, toolResults, finishReason, usage.
230for (const step of result.steps) {
231 console.log(step.finishReason, step.usage.totalTokens);
232}
233 
234// Whole-run totals:
235const { inputTokens, outputTokens, totalTokens } = result.usage;
236```
237 
238- `usage` fields (`inputTokens` / `outputTokens` / `totalTokens`) are
239 `number | undefined` — always guard with `?? 0` / `?? "?"`.
240- `finishReason` per step: `"tool-calls"` (loop continues) vs `"stop"` (final answer).
241- Cost is your own calc: `(inputTokens/1e6)*inPrice + (outputTokens/1e6)*outPrice`.
242- For full tracing/spans, pass `experimental_telemetry: { isEnabled: true }`.
243 
244## Other gotchas in this repo
245 
246- **System prompts:** pass via the `system` option, NOT as a `{ role: "system" }`
247 entry in `messages` (the SDK warns about prompt-injection risk otherwise).
248- **Conversation memory:** keep a `messages: ModelMessage[]` array; after each
249 turn append `(await result.response).messages` so the next turn has context.
250- **`marked.parse()` is async** with `marked-terminal-renderer` — `await` it or
251 you'll print `Promise { <pending> }`.
252- **readline + piped stdin:** `rl.question` rejects on EOF; wrap in try/catch and
253 treat it as "quit" so piped input doesn't crash with `ERR_USE_AFTER_CLOSE`.
254 
255## How to verify SDK API before using it
256 
257When unsure whether an SDK function is current, check the installed types
258directly instead of guessing:
259 
260```bash
261grep -nE &quot;@deprecated|declare function &lt;name&gt;&quot; node_modules/ai/dist/index.d.ts
262```
263 

Sections

  • Vercel AI SDK — Conventions & Gotchas
  • ⚠️ Deprecation: do NOT use `generateObject` / `streamObject`
  • ✅ Correct: structured output via `generateText` / `streamText` + `Output`
  • `Output` helpers (from `import { Output } from "ai"`)
  • The agent loop
  • Reusable agents: the `ToolLoopAgent` class
  • RAG: embeddings + retrieval
  • Robust agents: error handling & tool repair
  • Human-in-the-loop: approval gates for sensitive tools
  • Multi-agent orchestration: agents as tools
  • Observability: steps, token usage & cost
  • Other gotchas in this repo
  • How to verify SDK API before using it

What it covers

code-styleapido-notagent-behaviour

Stack — with the evidence

typescript

(1.00)

vitest

(1.00)

ai-agent

(1.00)

node

(0.70)

javascript

(0.60)

github-actions

(0.60)

Format

Cline rules

A single file or a folder of files, all always-on. The folder form is the simplest way any format here lets you split rules into topics without also learning an activation model.

What the corpus says about it

Repository

Owner
blendsdk
Language
—
License
—
Archived
no

All configs in this repo

Also in blendsdk/building-agents-vercel-ai-sdk

Diff this repo’s formats

One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?

The other instruction files in this repository
RepositoryFormatStackCoversScoreChanged
blendsdk/building-agents-vercel-ai-sdk.clinerules/project.md · 1Cline rulestypescriptvitest+4buildteststylearch+784/1003 days ago
Diff against .clinerules/project.md

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
bashdeban/fastmind.clinerules/.project-consistency-keeper2.md · 5Cline rulestypescriptnode+8setupbuildtestlint-format+11100/1003 days ago
JCodesMore/ai-website-cloner-template.clinerules · 31kCline rulestypescriptnode+7buildlint-formatstylearch+397/1002 days ago
u9401066/zotero-keeper.clinerules/50-pubmed-project.md · 6Cline rulespytestruff+6testlint-formatstylearch+194/1003 days ago
u9401066/zotero-keepervscode-extension/resources/repo-assets/pubmed-search-mcp/.clinerules/50-pubmed-project.md · 6Cline rulespytestruff+6testlint-formatstylearch+194/1003 days ago
VaillerTeeter/HoshimiNest.clinerules/project-identity.md · 1Cline rulestypescriptvite+4setuparchtypesdo-not93/100yesterday
blendsdk/codeops-mcp.clinerules/project.md · 0Cline rulestypescriptvitest+3buildteststylearch+791/1003 days ago
cline/cline.clinerules/general.md · 66kCline rulestypescriptnode+12setupbuildstylearch+286/1003 days ago
u9401066/zotero-keeper.clinerules/60-pubmed-python.md · 6Cline rulespytestruff+6setuptestlint-formatstyle+286/1003 days ago
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack