|
| 1 | +import { highlight, normalizeLang } from "@/lib/highlight"; |
1 | 2 | import { tool } from "ai"; |
2 | 3 | import { z } from "zod"; |
3 | 4 |
|
| 5 | +// Reject obvious placeholder tokens — forces the model to substitute |
| 6 | +// real resource names instead of emitting {table-name} / your-api-key. |
| 7 | +// Allowed: real values like sk-sl_7b3719eb, contracts-registry, etc. |
| 8 | +const PLACEHOLDER_PATTERNS: Array<{ re: RegExp; label: string }> = [ |
| 9 | + { re: /\{[a-z][a-z0-9_-]*\}/i, label: "{placeholder}" }, |
| 10 | + { re: /\byour[-_][a-z0-9_-]+\b/i, label: "your-*" }, |
| 11 | + { re: /\bYOUR_[A-Z0-9_]+\b/, label: "YOUR_*" }, |
| 12 | + { re: /<[a-z][a-z0-9_-]*>/i, label: "<placeholder>" }, |
| 13 | +]; |
| 14 | + |
| 15 | +function findPlaceholder(code: string): string | null { |
| 16 | + for (const { re, label } of PLACEHOLDER_PATTERNS) { |
| 17 | + if (re.test(code)) return label; |
| 18 | + } |
| 19 | + return null; |
| 20 | +} |
| 21 | + |
4 | 22 | export const showCode = tool({ |
5 | 23 | description: |
6 | | - "Display a tabbed code example card to the user. Use this for multi-language examples with tabs: curl, Node.js, and SDK (@secondlayer/sdk). Do NOT include Python. Each tab gets syntax highlighting and a copy button.", |
| 24 | + "Display a tabbed code example card to the user. Use for multi-language examples with tabs: curl, Node.js, SDK (@secondlayer/sdk). Do NOT include Python. Each tab gets syntax highlighting and a copy button. CRITICAL: every tab's code must use concrete resource values from the user's account (real subgraph name, real table name, real API key prefix). Never emit placeholder tokens like {table-name}, your-api-key, or <id> — the tool will reject them.", |
7 | 25 | inputSchema: z.object({ |
8 | 26 | tabs: z |
9 | 27 | .array( |
10 | 28 | z.object({ |
11 | | - label: z.string().describe("Tab label (e.g. 'curl', 'JavaScript')"), |
| 29 | + label: z |
| 30 | + .string() |
| 31 | + .describe("Tab label (e.g. 'curl', 'Node.js', 'SDK')"), |
12 | 32 | lang: z |
13 | 33 | .string() |
14 | | - .describe( |
15 | | - "Language for syntax highlighting (bash, javascript, typescript, python, json, sql)", |
16 | | - ), |
17 | | - code: z.string().describe("Code content for this tab"), |
| 34 | + .describe("Language: bash, javascript, typescript, json, sql"), |
| 35 | + code: z.string().describe("Code content with real resource values"), |
18 | 36 | }), |
19 | 37 | ) |
20 | 38 | .describe("Array of code tabs to display"), |
21 | 39 | }), |
22 | | - execute: async ({ tabs }) => ({ tabs }), |
| 40 | + execute: async ({ tabs }) => { |
| 41 | + for (const t of tabs) { |
| 42 | + const hit = findPlaceholder(t.code); |
| 43 | + if (hit) { |
| 44 | + return { |
| 45 | + error: true, |
| 46 | + message: `Tab "${t.label}" contains placeholder token (${hit}). Rewrite using concrete values from the user's resources — real subgraph name, real table name, real API key prefix. Do not use {braces}, <angles>, or your-* / YOUR_* tokens.`, |
| 47 | + }; |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + const rendered = await Promise.all( |
| 52 | + tabs.map(async (t) => { |
| 53 | + const lang = normalizeLang(t.lang); |
| 54 | + const html = await highlight(t.code, lang); |
| 55 | + return { label: t.label, lang, code: t.code, html }; |
| 56 | + }), |
| 57 | + ); |
| 58 | + |
| 59 | + return { tabs: rendered }; |
| 60 | + }, |
23 | 61 | }); |
0 commit comments