Repository Public Research preview
1// Builds the skill from the MCP package so both ship the same verified rules.2import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";3const src = new URL("./mcp/", import.meta.url), dst = new URL("./skill/openai-api-toolkit/", import.meta.url);4mkdirSync(new URL("scripts/lib/", dst), { recursive: true });5mkdirSync(new URL("references/", dst), { recursive: true });6for (const f of ["knowledge.mjs", "lint.mjs", "cost.mjs", "scaffold.mjs", "guides.mjs"]) copyFileSync(new URL(`lib/${f}`, src), new URL(`scripts/lib/${f}`, dst));7copyFileSync(new URL("knowledge.json", src), new URL("scripts/knowledge.json", dst));8copyFileSync(new URL("../anthropic/mcp/LICENSE", import.meta.url), new URL("LICENSE", dst));9copyFileSync(new URL("../anthropic/mcp/LICENSE", import.meta.url), new URL("LICENSE", src));1011const kb = JSON.parse(readFileSync(new URL("knowledge.json", src)));12const ms = Object.values(kb.models);13const date = kb.observed_at.slice(0, 10);14const k = (n) => (n >= 1e6 ? `${Math.round(n / 1e4) / 100}M` : `${Math.round(n / 1e3)}K`);15const usd = (x) => (x == null ? "—" : `$${x}`);16const head = `<!-- Generated by build-skill.mjs from live API probes and official documentation (${date}). Do not edit by hand. -->\n`;1718let models = `${head}# Models\n\nIds and behaviour observed ${date}. Context window, output cap: official model pages. Prices: official pricing page, USD per million tokens (standard tier, prompts up to 272K tokens).\n\nRecommended: **${kb.recommended.flagship}** (flagship), **${kb.recommended.balanced}** (balanced default), **${kb.recommended.fast}** (fast), **${kb.recommended.cheapest}** (cheapest).\n\n| Model | Context | Max output | Input | Cached input | Output | Batch in / out |\n|---|---|---|---|---|---|---|\n`;19for (const m of ms) {20 const p = m.pricing_usd_per_mtok;21 models += `| \`${m.id}\` | ${k(m.context_window)} | ${k(m.max_output_tokens)} | ${usd(p?.input)} | ${usd(p?.cached_input)} | ${usd(p?.output)} | ${p ? `${usd(p.batch_input)} / ${usd(p.batch_output)}` : "—"} |\n`;22}23models += `\nNotes:\n- "—" means the official pricing page lists no price; the scripts report it as unknown rather than guess.\n- Above 272K input tokens the newest models switch to long-context rates (about 2× input, 1.5× output). \`cost\` applies them.\n- Fast mode (formerly priority processing) costs about 2×; flex and batch about half.\n- Reasoning tokens are billed as output tokens and count against \`max_output_tokens\`.\n- Embeddings: \`text-embedding-3-small\` $0.02, \`text-embedding-3-large\` $0.13 per million tokens. Moderation is free.\n- Dated snapshots (\`gpt-5.4-2026-03-05\`) resolve to their alias in the scripts.\n- The models endpoint returns ids only. Never write an id from memory: run \`node scripts/openai-api.mjs models\`.\n`;24writeFileSync(new URL("references/models.md", dst), models);2526const S = { always: "always", effort_none_only: "only with effort `none`", never: "**never**" };27let rules = `${head}# What each model accepts\n\nEvery cell is an observed API response (${date}). "rejected" means HTTP 400 (or 404 for an unsupported endpoint).\n\n## Responses API\n\n| Model | Reasoning effort levels | Default effort is \`none\` | \`temperature\` / \`top_p\` | \`text.verbosity\` | Code interpreter |\n|---|---|---|---|---|---|\n`;28for (const m of ms) {29 const r = m.responses;30 rules += `| \`${m.id}\` | ${r.effort_levels.join(", ") || "**not a reasoning model**"} | ${r.effort_levels.length ? (r.default_effort_is_none ? "yes" : "no") : "—"} | ${S[r.sampling_params]} | ${r.verbosity_levels.join(", ")} | ${r.code_interpreter === "yes" ? "yes" : r.code_interpreter === "auto_only" ? "yes, `tool_choice: auto` only" : "**rejected**"} |\n`;31}32rules += `\nAccepted on the Responses API by every probed model: \`instructions\`, system and developer roles, strict structured outputs (\`text.format\` json_schema), \`json_object\`, function tools with \`tool_choice\` auto / required / none / forced function, \`parallel_tool_calls\`, web search, image input, \`store: false\`, \`truncation\`, \`prompt_cache_key\`, \`prompt_cache_retention\`, \`metadata\`, \`max_tool_calls\`, background mode, \`include: ["reasoning.encrypted_content"]\`. \`max_output_tokens\` below 16 is rejected everywhere. \`top_logprobs\` is rejected by every reasoning model.\n\n## Chat Completions\n\n| Model | Served | Legacy \`max_tokens\` | Custom \`temperature\` | \`stop\` | Function tools | \`logprobs\` |\n|---|---|---|---|---|---|---|\n`;33const yn = (b) => (b ? "yes" : "**rejected**");34for (const m of ms) {35 const c = m.chat_completions;36 rules += c.supported37 ? `| \`${m.id}\` | yes | ${yn(c.max_tokens_legacy)} | ${S[c.temperature]} | ${yn(c.stop)} | ${c.function_tools === "yes" ? "yes" : c.function_tools === "effort_none_only" ? 'only with `reasoning_effort: "none"`' : "**rejected** — use Responses"} | ${yn(c.logprobs)} |\n`38 : `| \`${m.id}\` | **no — Responses only** | — | — | — | — | — |\n`;39}40rules += `\n## Exact error texts\n\n`;41const texts = new Map();42for (const m of ms) for (const [probe, t] of Object.entries(m.observed_errors)) {43 const key = t.replaceAll(m.id, "<model>");44 if (!texts.has(key)) texts.set(key, probe);45}46for (const [t, probe] of texts) rules += `- \`${probe}\`: ${t}\n`;47writeFileSync(new URL("references/request-rules.md", dst), rules);48console.log(`skill built: ${ms.length} models, ${texts.size} distinct error texts`);49