Cline rules
.clinerules/ai-sdk.mdCline rules
Quality
57/100
Scores the file, not the repository.Length
1,518 words
13 headings · 8 code blocksRepository
1
— · pushed 57 days agoLast changed
3 days ago
First indexed 3 days ago.1# Vercel AI SDK — Conventions & Gotchas23Project 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`).56## ⚠️ Deprecation: do NOT use `generateObject` / `streamObject`78In AI SDK v6 these are **deprecated**. Confirmed directly in the installed type9definitions (`node_modules/ai/dist/index.d.ts`):1011- `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.`1415### ✅ Correct: structured output via `generateText` / `streamText` + `Output`1617Use the same text/agent functions you already know, and pass an `output` setting18built with the `Output` helper. Read the typed result from `result.output`.1920```ts21import { generateText, streamText, Output, stepCountIs } from "ai";22import { openai } from "@ai-sdk/openai";23import { z } from "zod";2425// --- 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.3233// --- 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>4344// --- 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```5354### `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 exposes57 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).6162> Note: `Output` is exported as `output as Output` in the package; import it as63> `import { Output } from "ai"`.6465## The agent loop6667- An "agent" = an LLM in a loop that can call tools. Enable the loop with68 `stopWhen: stepCountIs(N)` on `generateText` / `streamText`. Without it, the69 call stops after the first tool call and never reaches a final answer.70- Inspect what the agent did via `result.steps` (`toolCalls`, `toolResults`).7172## Reusable agents: the `ToolLoopAgent` class7374For a named, reusable agent (instead of re-wiring `generateText` every call), use75the `ToolLoopAgent` class. Configure once, then call `.generate()` / `.stream()`.7677```ts78import { ToolLoopAgent, stepCountIs, Output } from "ai";79import { openai } from "@ai-sdk/openai";8081const 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 output87});8889const result = await assistant.generate({ prompt: "…" }); // same shape as generateText90const streamed = await assistant.stream({ prompt: "…" }); // same shape as streamText91```9293- 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 is97 the interface it implements.9899100## RAG: embeddings + retrieval101102To ground an agent in your own documents, embed them and do similarity search.103104```ts105import { embed, embedMany, cosineSimilarity } from "ai";106import { openai } from "@ai-sdk/openai";107108const embeddingModel = openai.embedding("text-embedding-3-small"); // ⚠️ `embedding`, NOT `textEmbedding` (deprecated)109110// Build a vector store (batch embed your docs):111const { embeddings } = await embedMany({ model: embeddingModel, values: docs });112113// At query time, embed the query and rank by cosine similarity:114const { embedding: queryVec } = await embed({ model: embeddingModel, value: query });115const ranked = store116 .map((d) => ({ ...d, score: cosineSimilarity(queryVec, d.embedding) }))117 .sort((a, b) => b.score - a.score);118```119120- `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 context124 on demand; instruct it to answer only from retrieved docs and to say when it125 doesn't know.126127## Robust agents: error handling & tool repair128129Make agents survive failures instead of crashing.130131```ts132import { generateText, stepCountIs, NoSuchToolError, type ToolCallRepairFunction } from "ai";133134// 1) Tools should RETURN structured errors, not throw — the model reads the135// 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},140141// 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 fix145 const match = (toolCall.input ?? "").match(/\{[\s\S]*\}/); // salvage JSON from bad input146 if (!match) return null;147 try { JSON.parse(match[0]); return { ...toolCall, input: match[0] }; } catch { return null; }148};149150await generateText({ model, tools, stopWhen: stepCountIs(6), experimental_repairToolCall: repairToolCall });151```152153- The repair hook's `error` is `NoSuchToolError | InvalidToolInputError`; use154 `NoSuchToolError.isInstance(error)` to distinguish them.155- `toolCall.input` is the raw (string) args; return `{ ...toolCall, input }` to retry.156157## Human-in-the-loop: approval gates for sensitive tools158159Before an agent takes a real action (send email, delete, move money), gate it160behind human approval. Simple CLI pattern: the sensitive tool's `execute` pauses161and asks the human, then returns a structured result either way.162163```ts164const 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/N170 if (!approved) return { ok: false, status: "DENIED_BY_HUMAN" }; // agent adapts171 return { ok: true, status: "SENT" };172 },173});174```175176- 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 acknowledges179 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 the183 Chat/UI message stream; the in-`execute` prompt above is simplest for a CLI.184185## Multi-agent orchestration: agents as tools186187To build a TEAM, make a "supervisor" agent that delegates to specialist agents.188The trick: **wrap each specialist `ToolLoopAgent` as a tool** the supervisor calls.189190```ts191// A specialist agent (Tutorial 4):192const researchAgent = new ToolLoopAgent({ model, instructions: "…", tools: { searchKnowledge }, stopWhen: stepCountIs(5) });193194// 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});203204// 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```212213- Inspect `result.steps[].toolCalls` to see which specialist was used for what.214- Give each specialist a narrow `instructions` + tool set; tell the supervisor to215 always delegate (not answer directly) so routing is reliable.216217## Observability: steps, token usage & cost218219Trace what an agent did and what it cost.220221```ts222const result = await generateText({223 model, tools, stopWhen: stepCountIs(10), prompt: "…",224 onStepFinish: ({ toolCalls, usage }) => { // live, fires after each step225 console.log(toolCalls.map(c => c.toolName), usage.totalTokens);226 },227});228229// 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}233234// Whole-run totals:235const { inputTokens, outputTokens, totalTokens } = result.usage;236```237238- `usage` fields (`inputTokens` / `outputTokens` / `totalTokens`) are239 `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 }`.243244## Other gotchas in this repo245246- **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 each249 turn append `(await result.response).messages` so the next turn has context.250- **`marked.parse()` is async** with `marked-terminal-renderer` — `await` it or251 you'll print `Promise { <pending> }`.252- **readline + piped stdin:** `rl.question` rejects on EOF; wrap in try/catch and253 treat it as "quit" so piped input doesn't crash with `ERR_USE_AFTER_CLOSE`.254255## How to verify SDK API before using it256257When unsure whether an SDK function is current, check the installed types258directly instead of guessing:259260```bash261grep -nE "@deprecated|declare function <name>" node_modules/ai/dist/index.d.ts262```263
Also in blendsdk/building-agents-vercel-ai-sdk
Diff this repo’s formatsOne 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?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| blendsdk/building-agents-vercel-ai-sdk.clinerules/project.md · 1 | Cline rules | buildteststylearch+7 | 84/100 | 3 days ago |
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| bashdeban/fastmind.clinerules/.project-consistency-keeper2.md · 5 | Cline rules | setupbuildtestlint-format+11 | 100/100 | 3 days ago | |
| JCodesMore/ai-website-cloner-template.clinerules · 31k | Cline rules | buildlint-formatstylearch+3 | 97/100 | 2 days ago | |
| u9401066/zotero-keeper.clinerules/50-pubmed-project.md · 6 | Cline rules | testlint-formatstylearch+1 | 94/100 | 3 days ago | |
| u9401066/zotero-keepervscode-extension/resources/repo-assets/pubmed-search-mcp/.clinerules/50-pubmed-project.md · 6 | Cline rules | testlint-formatstylearch+1 | 94/100 | 3 days ago | |
| VaillerTeeter/HoshimiNest.clinerules/project-identity.md · 1 | Cline rules | setuparchtypesdo-not | 93/100 | yesterday | |
| blendsdk/codeops-mcp.clinerules/project.md · 0 | Cline rules | buildteststylearch+7 | 91/100 | 3 days ago | |
| cline/cline.clinerules/general.md · 66k | Cline rules | setupbuildstylearch+2 | 86/100 | 3 days ago | |
| u9401066/zotero-keeper.clinerules/60-pubmed-python.md · 6 | Cline rules | setuptestlint-formatstyle+2 | 86/100 | 3 days ago |
