Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
10 KiB
JSON
1 line
10 KiB
JSON
{"repo": "7flash/jsx-ai", "files": {"README.md": {"eca6e7ab1568f07695dd31dc37b8c9cbe0047a11": {"t": 1786068586.1974843, "n": 9535, "h": "7fb3e4ca66bd0fd8", "c": "# jsx-ai\n\n[](https://www.npmjs.com/package/jsx-ai)\n[](https://bundlephobia.com/package/jsx-ai)\n\nJSX interface for structured LLM calls. Tools, messages, and prompts become composable components.\n\n```tsx\nimport { callLLM } from \"jsx-ai\"\n\nconst result = await callLLM(\n <>\n <system>You are a coding agent</system>\n <tool name=\"exec\" description=\"Run a shell command\">\n <param name=\"command\" type=\"string\" required>The command to run</param>\n </tool>\n <message role=\"user\">List all TypeScript files</message>\n </>,\n { model: \"gemini-2.5-flash\" }\n)\n\nresult.toolCalls // [{ name: \"exec\", args: { command: \"find . -name '*.ts'\" } }]\nresult.text // \"\"\nresult.usage // { inputTokens: 42, outputTokens: 15 }\n```\n\n## Why JSX?\n\n**Before** — tools as JSON schemas, stringly-typed, not reusable:\n\n```ts\nconst response = await fetch(url, {\n body: JSON.stringify({\n model: \"gemini-2.5-flash\",\n systemInstruction: { parts: [{ text: \"You are a coding agent\" }] },\n tools: [{ functionDeclarations: [{\n name: \"exec\",\n description: \"Run a shell command\",\n parameters: { type: \"object\", properties: {\n command: { type: \"string\", description: \"The command to run\" }\n }, required: [\"command\"] }\n }] }],\n contents: [{ role: \"user\", parts: [{ text: \"List all TypeScript files\" }] }],\n })\n})\nconst data = await response.json()\nconst toolCall = data.candidates[0].content.parts[0].functionCall\n```\n\n**After** — same call, composable and provider-agnostic:\n\n```tsx\nconst ExecTool = () => (\n <tool name=\"exec\" description=\"Run a shell command\">\n <param name=\"command\" type=\"string\" required>The command to run</param>\n </tool>\n)\n\nconst result = await callLLM(\n <>\n <system>You are a coding agent</system>\n <ExecTool />\n <message role=\"user\">List all TypeScript files</message>\n </>,\n { model: \"gemini-2.5-flash\" } // or \"gpt-4o\" or \"claude-3-sonnet-20240229\"\n)\n\nresult.toolCalls // [{ name: \"exec\", args: { command: \"find . -name '*.ts'\" } }]\n```\n\n## Installation\n\n```sh\nbun add jsx-ai\n# or: npm install jsx-ai\n```\n\nAdd to `tsconfig.json`:\n```json\n{\n \"compilerOptions\": {\n \"jsx\": \"react-jsx\",\n \"jsxImportSource\": \"jsx-ai\"\n }\n}\n```\n\n## ✨ What You Get\n\n- **Multi-provider** → Gemini, OpenAI, Anthropic, DeepSeek — auto-detected from model name\n- **5 strategies** → native FC, NLT, XML, natural, hybrid — same prompt, different encodings\n- **Composable** → tools and prompts are reusable JSX components\n- **Skills** → two-phase skill loading from `.md` files (discovery → resolution)\n- **Type-safe** → full TypeScript types, custom JSX runtime (not React)\n- **Benchmarked** → multi-turn agentic scenarios scored per strategy\n\n## 🔌 Providers\n\nAuto-detected from model name. Override with `{ provider: \"openai\" }`.\n\n| Model | Provider | Auth | Env var |\n|-------|----------|------|---------|\n| `gemini-*` | Gemini | x-goog-api-key | `GEMINI_API_KEY` |\n| `gpt-*`, `o4-*` | OpenAI | Bearer | `OPENAI_API_KEY` |\n| `claude-*` | Anthropic | x-api-key + version | `ANTHROPIC_API_KEY` |\n| `deepseek-*` | OpenAI (compat) | Bearer | `DEEPSEEK_API_KEY` |\n\n```tsx\n// Gemini (default)\nawait callLLM(<>...</>, { model: \"gemini-2.5-flash\" })\n\n// OpenAI\nawait callLLM(<>...</>, { model: \"gpt-4o\" })\n\n// Anthropic\nawait callLLM(<>...</>, { model: \"claude-3-sonnet-20240229\" })\n```\n\nProvider nuances handled automatically:\n- Gemini: merges consecutive same-role messages (API rejects them otherwise)\n- OpenAI `o4-*`: uses `max_completion_tokens` + forced `temperature=1.0`\n- Anthropic: system prompt as top-level field, `tool_use` blocks, `input_schema`\n- DeepSeek: routes to `api.deepseek.com` with OpenAI-compatible format\n\n### Custom providers\n\n```tsx\nimport { registerProvider } from \"jsx-ai\"\nimport type { Provider } from \"jsx-ai\"\n\nclass MyProvider implements Provider {\n name = \"custom\"\n buildRequest(prepared, model, apiKey) { /* ... */ }\n parseResponse(data) { /* ... */ }\n}\n\nregisterProvider(\"custom\", new MyProvider())\nawait callLLM(<>...</>, { provider: \"custom\", model: \"my-model\" })\n```\n\n## 🎯 Strategies\n\nSame JSX prompt, different tool encodings. Each strategy controls how tools appear to the model and how responses are parsed.\n\n| Strategy | Tools sent as | Response parsed from | Best for |\n|----------|---------------|---------------------|----------|\n| `native` | API `tools` field | Structured FC | Single tool calls, lowest tokens |\n| `nlt` | Text descriptions + native FC | Structured FC | Multi-turn agentic loops |\n| `xml` | Text with XML schema | XML in text | Multi-tool batching |\n| `natural` | Text descriptions | Action blocks in text | Complex reasoning + tools |\n| `hybrid` | API `tools` + text schema | Either | Balanced |\n\n```tsx\n// Strategy via options\nawait callLLM(<>...</>, { strategy: \"nlt\" })\n\n// Or register a custom one\nimport { registerStrategy } from \"jsx-ai\"\nregisterStrategy(\"my-strategy\", { prepare, parseResponse })\n```\n\n### Benchmark results (gemini-2.5-flash, kv-store scenario)\n\n3-turn agentic loop: Plan → Execute → Adapt\n\n| Strategy | Turn 1 (Plan) | Turn 2 (Execute) | Turn 3 (Adapt) | Total |\n|----------|:---:|:---:|:---:|:---:|\n| **nlt** | 100% | 73% | 84% | **86%** |\n| **natural** | 100% | 67% | 69% | **79%** |\n| **native** | 46% | 5% | 33% | **28%** |\n\n> Native FC underperforms in agentic loops because it batches homogeneous tool calls — calling 5× `use_skill` but skipping `set_objectives` in the same turn.\n\n## 📦 JSX Elements\n\n| Element | Props | Description |\n|---------|-------|-------------|\n| `<system>` | — | System instruction (text children) |\n| `<tool>` | `name`, `description` | Tool/function declaration |\n| `<param>` | `name`, `type`, `required`, `enum` | Tool parameter (children = description) |\n| `<message>` | `role` (`user` \\| `assistant`) | Conversation message |\n| `<prompt>` | `model`, `temperature`, `maxTokens`, `strategy` | Optional config wrapper |\n\n## 🧠 Skills\n\nTwo-phase skill loading from `.md` files with YAML frontmatter:\n\n```md\n---\nname: bun-expert\ndescription: Bun runtime expertise — Bun.serve(), bun:sqlite, bun:test\n---\n## Bun Runtime\n- HTTP: Bun.serve() with export default { port, fetch } pattern\n- Database: import { Database } from \"bun:sqlite\"\n- Testing: import { describe, it, expect } from \"bun:test\"\n```\n\n**Phase 1 — Discovery:** skills appear as a lightweight catalog\n\n```tsx\nimport { Skill, UseSkillTool } from \"jsx-ai\"\n\nawait callLLM(\n <>\n <Skill path=\"skills/bun-expert.md\" />\n <Skill path=\"skills/security.md\" />\n <UseSkillTool />\n <message role=\"user\">Build a KV store API</message>\n </>\n)\n// Model sees: \"Available skill: bun-expert — Bun runtime expertise\"\n// Model calls: use_skill({ skill_name: \"bun-expert\" })\n```\n\n**Phase 2 — Resolution:** requested skills expand to full content\n\n```tsx\nimport { Skill, resolveSkills } from \"jsx-ai\"\n\nconst resolved = resolveSkills(skillPaths, [\"bun-expert\"])\n\nawait callLLM(\n <>\n <Skill path=\"skills/bun-expert.md\" resolve />\n <Skill path=\"skills/security.md\" />\n <message role=\"user\">Now implement it</message>\n </>\n)\n// Model sees full bun-expert methodology + just the catalog entry for security\n```\n\n## 🔍 `render(tree)`\n\nInspect the extracted prompt without calling the LLM:\n\n```tsx\nimport { render } from \"jsx-ai\"\n\nconst extracted = render(\n <>\n <system>You are helpful</system>\n <tool name=\"exec\" description=\"Run command\">\n <param name=\"command\" type=\"string\" required>Command</param>\n </tool>\n <message role=\"user\">List files</message>\n </>\n)\n\nextracted.tools // [{ name: \"exec\", parameters: { ... } }]\nextracted.messages // [{ role: \"user\", content: \"List files\" }]\nextracted.system // \"You are helpful\"\n```\n\n## ⚙️ CallOptions\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `model` | `string` | `\"gemini-2.5-flash\"` | Model name (also determines provider) |\n| `provider` | `\"gemini\" \\| \"openai\" \\| \"anthropic\"` | auto-detected | Force a specific provider |\n| `strategy` | `\"native\" \\| \"nlt\" \\| \"xml\" \\| \"natural\" \\| \"hybrid\"` | `\"auto\"` | Tool encoding strategy |\n| `apiKey` | `string` | from env | Override API key |\n| `temperature` | `number` | `0.1` | Sampling temperature |\n| `maxTokens` | `number` | `4000` | Max output tokens |\n\n## 💬 `callText(model, messages, options?)`\n\nSimple text-in/text-out LLM call — no JSX needed. Uses the same provider routing and auth:\n\n```ts\nimport { callText } from \"jsx-ai\"\n\nconst text = await callText(\"gemini-2.5-flash\", [\n { role: \"system\", content: \"You are a planner. Break tasks into steps.\" },\n { role: \"user\", content: \"Build a REST API with authentication\" },\n])\n\nconsole.log(text) // \"1. Set up project with Bun.serve()...\"\n```\n\n## 🔄 `streamLLM(model, messages, options?)`\n\nStream LLM responses token-by-token via SSE. Same provider routing as `callText`:\n\n```ts\nimport { streamLLM } from \"jsx-ai\"\n\nfor await (const chunk of streamLLM(\"gemini-2.5-flash\", [\n { role: \"system\", content: \"You are a storyteller\" },\n { role: \"user\", content: \"Tell me a short story\" },\n])) {\n process.stdout.write(chunk)\n}\n```\n\nOptions for both `callText` and `streamLLM`:\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `temperature` | `number` | `0.3` | Sampling temperature |\n| `maxTokens` | `number` | `8000` | Max output tokens |\n| `apiKey` | `string` | from env | Override API key |\n\n## License\n\nMIT\n"}}}} |