{"_id":"@ailink/sdk","_rev":"4-965cd5e5de8c425b302097bcfab020b3","name":"@ailink/sdk","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@ailink/sdk","version":"0.1.0","keywords":["ai","llm","gemini","openai","claude","groq","automation","chat","typescript","ailink"],"author":{"name":"ailink"},"license":"MIT","_id":"@ailink/sdk@0.1.0","maintainers":[{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"}],"homepage":"https://github.com/getailink/ailink-sdk#readme","bugs":{"url":"https://github.com/getailink/ailink-sdk/issues"},"dist":{"shasum":"e6a00789419f08978e07bb6e8d55d5fec21ec696","tarball":"https://registry.npmjs.org/@ailink/sdk/-/sdk-0.1.0.tgz","fileCount":67,"integrity":"sha512-rBhkHwPtoAV6UYUENR8oMe/ou0r6MfTXCu+posAh57NbgZPHb7KJGeOGkWJuULGbOdu23wcNn8hZY5YpCWiQ6g==","signatures":[{"sig":"MEUCIQDgUpEk6bsbWApMg3UEBwSfddtqgm8zui0mkcMcyo/lRgIgVVgS676X7Fq3UbXt5nBNU6KzAuxUrJSa2X0uLjvaees=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":142298},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./widget":{"types":"./dist/widget/AILinkWidget.d.ts","import":"./dist/widget/AILinkWidget.js","require":"./dist/widget/AILinkWidget.js"}},"gitHead":"f95c58abec98673cb02866b00d3c43e086f575ef","scripts":{"dev":"ts-node examples/demo.ts","test":"jest","build":"tsc","test:e2e":"jest --testPathPattern=e2e","test:unit":"jest --testPathPattern=unit","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm run build","test:integration":"jest --testPathPattern=integration"},"_npmUser":{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"},"repository":{"url":"git+https://github.com/getailink/ailink-sdk.git","type":"git"},"_npmVersion":"10.8.2","description":"Turn any existing function into an AI-callable tool without touching your existing code. Multi-provider support, role-based access, session memory, parallel execution, and automatic fallbacks.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ajv":"^8.17.1","openai":"^4.67.0","groq-sdk":"^0.9.0","@anthropic-ai/sdk":"^0.39.0","@google/generative-ai":"^0.21.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","dotenv":"^16.4.5","ts-jest":"^29.1.0","ts-node":"^10.9.0","typescript":"^5.4.0","@jest/types":"^29.5.0","@types/jest":"^29.5.0","@types/node":"^20.0.0","@types/react":"^18.0.0"},"peerDependencies":{"react":">=18.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.0_1780287356218_0.5892594113849767","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@ailink/sdk","version":"0.2.0","keywords":["ai","llm","gemini","openai","claude","groq","automation","chat","typescript","ailink"],"author":{"name":"ailink"},"license":"MIT","_id":"@ailink/sdk@0.2.0","maintainers":[{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"}],"homepage":"https://github.com/getailink/ailink-sdk#readme","bugs":{"url":"https://github.com/getailink/ailink-sdk/issues"},"dist":{"shasum":"b3f7f5b954ddd36865dd7607bb8374c866bb568f","tarball":"https://registry.npmjs.org/@ailink/sdk/-/sdk-0.2.0.tgz","fileCount":67,"integrity":"sha512-pog83BwsWhCp4FsAD7v/OCWUvAezRU8erl+OxsqJv+Ir3OIE5Ak0dMr0MPBxbwbZgpsrzSs5c6gi3RGOch23rg==","signatures":[{"sig":"MEQCIDYqX3D9Jufc9jowrKrUbI9B2hOkCTcPtbJxzSOv8NMXAiALTuDeHv62uaoeGZB9LDoFFvCYxcVnqD5ML0zCw2JwtA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":152440},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./widget":{"types":"./dist/widget/AILinkWidget.d.ts","import":"./dist/widget/AILinkWidget.js","require":"./dist/widget/AILinkWidget.js"}},"gitHead":"ef7f159f678dd6327d4200a773bfb1504d14e289","scripts":{"dev":"ts-node examples/demo.ts","test":"jest","build":"tsc","test:e2e":"jest --testPathPattern=e2e","test:unit":"jest --testPathPattern=unit","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm run build","test:integration":"jest --testPathPattern=integration"},"_npmUser":{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"},"repository":{"url":"git+https://github.com/getailink/ailink-sdk.git","type":"git"},"_npmVersion":"10.8.2","description":"Turn any existing function into an AI-callable tool without touching your existing code. Multi-provider support, role-based access, session memory, parallel execution, and automatic fallbacks.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ajv":"^8.17.1","openai":"^4.67.0","groq-sdk":"^0.9.0","@anthropic-ai/sdk":"^0.39.0","@google/generative-ai":"^0.21.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","dotenv":"^16.4.5","ts-jest":"^29.1.0","ts-node":"^10.9.0","langchain":"^1.4.4","typescript":"^5.4.0","@jest/types":"^29.5.0","@types/jest":"^29.5.0","@types/node":"^20.0.0","@types/react":"^18.0.0","@langchain/core":"^1.1.48","@langchain/groq":"^1.2.1","@langchain/openai":"^1.4.7"},"peerDependencies":{"react":">=18.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.2.0_1780591840663_0.5869905050716002","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@ailink/sdk","version":"0.2.1","keywords":["ai","llm","gemini","openai","claude","groq","automation","chat","typescript","ailink"],"author":{"name":"ailink"},"license":"MIT","_id":"@ailink/sdk@0.2.1","maintainers":[{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"}],"homepage":"https://github.com/getailink/ailink-sdk#readme","bugs":{"url":"https://github.com/getailink/ailink-sdk/issues"},"dist":{"shasum":"6a646dd7e91ea567e8ae9613d39dc49a7e9b8492","tarball":"https://registry.npmjs.org/@ailink/sdk/-/sdk-0.2.1.tgz","fileCount":67,"integrity":"sha512-KncMtrUadG4bfRC8J7rKsxHz+uK0/OHJvnpjxCOw3IE4fsfT2OZeDkPpOqajnmSLBDvjXlYFEMSaW4OjYGYsHw==","signatures":[{"sig":"MEYCIQDz1SG8JHajuQFIDrQIlPN6OcYD3BeEZ2AWabELnyyVfAIhAMWhvmE1+FVvni7XRTUYpVlYklVgS+y4mNfns7yOlxll","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":160389},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./widget":{"types":"./dist/widget/AILinkWidget.d.ts","import":"./dist/widget/AILinkWidget.js","require":"./dist/widget/AILinkWidget.js"}},"gitHead":"a65cb9f7d3fc2ec396df08bd82b45846243dc162","scripts":{"dev":"ts-node examples/demo.ts","test":"jest","build":"tsc","test:e2e":"jest --testPathPattern=e2e","test:unit":"jest --testPathPattern=unit","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm run build","test:integration":"jest --testPathPattern=integration"},"_npmUser":{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"},"repository":{"url":"git+https://github.com/getailink/ailink-sdk.git","type":"git"},"_npmVersion":"10.8.2","description":"Turn any existing function into an AI-callable tool. Wrap any external SDK — LangChain, Vercel AI, and more — without touching your existing code. Multi-provider, role-based access, session memory, parallel execution, and automatic fallbacks.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ajv":"^8.17.1","openai":"^4.67.0","groq-sdk":"^0.9.0","@anthropic-ai/sdk":"^0.39.0","@google/generative-ai":"^0.21.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","dotenv":"^16.4.5","ts-jest":"^29.1.0","ts-node":"^10.9.0","langchain":"^1.4.4","typescript":"^5.4.0","@jest/types":"^29.5.0","@types/jest":"^29.5.0","@types/node":"^20.0.0","@types/react":"^18.0.0","@langchain/core":"^1.1.48","@langchain/groq":"^1.2.1","@langchain/openai":"^1.4.7"},"peerDependencies":{"react":">=18.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.2.1_1780882567852_0.40521730056276084","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@ailink/sdk","version":"0.3.0","description":"Turn any existing function into an AI-callable tool. Wrap any external SDK — LangChain, Vercel AI, and more — without touching your existing code. Multi-provider, role-based access, session memory, parallel execution, and automatic fallbacks.","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"import":"./dist/index.js","require":"./dist/index.js","types":"./dist/index.d.ts"},"./widget":{"import":"./dist/widget/AILinkWidget.js","require":"./dist/widget/AILinkWidget.js","types":"./dist/widget/AILinkWidget.d.ts"}},"scripts":{"build":"tsc","dev":"ts-node examples/demo.ts","test":"jest","test:watch":"jest --watch","test:coverage":"jest --coverage","test:unit":"jest --testPathPattern=unit","test:integration":"jest --testPathPattern=integration","test:e2e":"jest --testPathPattern=e2e","prepublishOnly":"npm run build"},"keywords":["ai","llm","gemini","openai","claude","groq","automation","chat","typescript","ailink"],"author":{"name":"ailink"},"license":"MIT","dependencies":{"@anthropic-ai/sdk":"^0.39.0","@google/generative-ai":"^0.21.0","ajv":"^8.17.1","groq-sdk":"^0.9.0","openai":"^4.67.0"},"devDependencies":{"@jest/types":"^29.5.0","@langchain/core":"^1.1.48","@langchain/groq":"^1.2.1","@langchain/openai":"^1.4.7","@types/jest":"^29.5.0","@types/node":"^20.0.0","@types/react":"^18.0.0","dotenv":"^16.4.5","jest":"^29.7.0","langchain":"^1.4.4","ts-jest":"^29.1.0","ts-node":"^10.9.0","typescript":"^5.4.0"},"peerDependencies":{"react":">=18.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"engines":{"node":">=18.0.0"},"repository":{"type":"git","url":"git+https://github.com/getailink/ailink-sdk.git"},"_id":"@ailink/sdk@0.3.0","gitHead":"05d4f1f0417136322dbe0d3a00fec72bce9004fe","bugs":{"url":"https://github.com/getailink/ailink-sdk/issues"},"homepage":"https://github.com/getailink/ailink-sdk#readme","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-KDKunM0QE40GtsSmep4zZjyoyPp7wxL9ZP3Tf4HbxhvStBjrE8OpwLwhi4trXUSXkKMhT5wIhY7DGloWgl8ihw==","shasum":"8f723f13abafe3e69b6a1d095de682bdab15c61b","tarball":"https://registry.npmjs.org/@ailink/sdk/-/sdk-0.3.0.tgz","fileCount":67,"unpackedSize":166914,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDKEDzrQ++WbRbIbWajqokt7xN/WXJJ6XHjnvGq+gqfWAiACj8g9TuHowOaK7j172s2V5xPjiZ6LEL0BFg5Ux3ZShg=="}]},"_npmUser":{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"},"directories":{},"maintainers":[{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.3.0_1780989234645_0.7214005518777251"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-01T04:15:56.068Z","modified":"2026-06-09T07:13:54.892Z","0.1.0":"2026-06-01T04:15:56.369Z","0.2.0":"2026-06-04T16:50:40.836Z","0.2.1":"2026-06-08T01:36:08.024Z","0.3.0":"2026-06-09T07:13:54.788Z"},"bugs":{"url":"https://github.com/getailink/ailink-sdk/issues"},"author":{"name":"ailink"},"license":"MIT","homepage":"https://github.com/getailink/ailink-sdk#readme","keywords":["ai","llm","gemini","openai","claude","groq","automation","chat","typescript","ailink"],"repository":{"type":"git","url":"git+https://github.com/getailink/ailink-sdk.git"},"description":"Turn any existing function into an AI-callable tool. Wrap any external SDK — LangChain, Vercel AI, and more — without touching your existing code. Multi-provider, role-based access, session memory, parallel execution, and automatic fallbacks.","maintainers":[{"name":"ailink","email":"jaisankarpeddiboyina@gmail.com"}],"readme":"# AILink SDK\n\nYour app already has the functions. AILink makes them AI-callable.\nUse LangChain, Vercel AI SDK, or any other SDK without touching your existing code. AILink makes that possible.\n\nNo rewrites. No restructuring. Register once, your users talk to your app in natural language.\n\n**Works with:** OpenAI · Claude · Groq · Gemini\n**Use alongside:** LangChain · Vercel AI SDK · any existing SDK — zero rewrites\n\n```bash\nnpm install @ailink/sdk\n```\n\n---\n\n## Contents\n\n- [Why AILink](#why-ailink)\n- [How AILink Compares](#how-ailink-compares)\n- [Install](#install)\n- [Quick Start](#quick-start)\n- [Core Features](#core-features)\n  - [Function Registration](#function-registration)\n  - [Wrapping External SDKs](#wrapping-external-sdks)\n  - [Multi-Provider Support](#multi-provider-support)\n  - [Fallback Providers](#fallback-providers)\n  - [Role-Based Access Control](#role-based-access-control)\n  - [Group Filtering](#group-filtering)\n  - [Session Memory](#session-memory)\n  - [Parallel Tool Execution](#parallel-tool-execution)\n  - [Configurable Iteration Limit](#configurable-iteration-limit)\n  - [Chat Widget](#chat-widget)\n  - [Managing Tools](#managing-tools)\n- [Configuration Reference](#configuration-reference)\n- [Result Object](#result-object)\n- [Error Handling](#error-handling)\n- [Common Patterns](#common-patterns)\n- [FAQ](#faq)\n- [Troubleshooting](#troubleshooting)\n\n---\n\n## Why AILink\n\nEvery other AI SDK makes you rewrite your code to fit their format. AILink works the other way — your existing functions stay exactly as they are. You register them once and the AI can call them.\n\n```typescript\n// Your existing function — untouched\nasync function getWeather(city: string) {\n  return `22°C and sunny in ${city}`\n}\n\n// Register it once with AILink\nai.register('getWeather', async ({ city }) => getWeather(city), {\n  description: 'Get current weather for a city',\n  parameters: {\n    type: 'object',\n    properties: {\n      city: { type: 'string', description: 'City name' }\n    },\n    required: ['city']\n  }\n})\n\n// Now anyone can use it with natural language\nconst result = await ai.run('What is the weather in Tokyo?')\nconsole.log(result.response)\n// → \"It's currently 22°C and sunny in Tokyo.\"\n```\n\n---\n\n## How AILink Compares\n\n| Feature | AILink | Vercel AI SDK | LangChain |\n|---|---|---|---|\n| Works with existing code — no rewrites | ✅ | ❌ | ❌ |\n| Use any external SDK without touching your code | ✅ | ❌ | ❌ |\n| Role-based tool filtering — built in | ✅ | ❌ | ❌ |\n| Group-based tool filtering | ✅ | ❌ | ❌ |\n| Automatic fallback providers | ✅ | Via AI Gateway only | ✅ |\n| Session memory — built in | ✅ | ✅ | ✅ Via LangGraph |\n| Parallel tool execution | ✅ | ✅ | ✅ Via LangGraph |\n| Usage tracking — built in | ✅ | ✅ Via OpenTelemetry | ✅ Via Callbacks/OTel |\n| Multi-provider support | ✅ | ✅ | ✅ |\n| Drop-in chat widget | ✅ | ❌ | ❌ |\n| Streaming responses | ❌ via `ai.wrap()` | ✅ | ✅ |\n| RAG / document retrieval | ❌ via `ai.wrap()` | ❌ | ✅ |\n| Frontend React hooks | ❌ via `ai.wrap()` | ✅ | ❌ |\n\nThe ❌ via `ai.wrap()` entries are not missing features — they are deliberate. AILink does not rebuild what already exists. `ai.wrap()` connects you to any SDK that already does it best: one line, any async function, zero changes to your existing code. Your streaming SDK stays as-is. Your RAG pipeline stays as-is. Your React hooks stay as-is. AILink sits on top of all of them without touching any of them.\n\n---\n\n## Install\n\n```bash\nnpm install @ailink/sdk\n```\n\nRequires Node.js 18 or higher.\n\n---\n\n## Quick Start\n\n### 1. Get a provider API key\n\nPick one — all work the same way:\n\n| Provider | Free Tier | Get Key |\n|----------|-----------|---------|\n| Groq | Yes, no card needed | [console.groq.com](https://console.groq.com) |\n| Gemini | Yes, no card needed | [aistudio.google.com](https://aistudio.google.com) |\n| OpenAI | Paid | [platform.openai.com](https://platform.openai.com) |\n| Claude | Paid | [console.anthropic.com](https://console.anthropic.com) |\n\n### 2. Initialize\n\n```typescript\nimport { AILink } from '@ailink/sdk'\n\nconst ai = new AILink({\n  provider: 'groq',\n  providerKey: process.env.GROQ_KEY!\n})\n```\n\n### 3. Register your functions\n\n```typescript\nai.register('checkStock', async ({ productId }) => {\n  // your existing function\n  return inventory.check(productId)\n}, {\n  description: 'Check how many units of a product are in stock',\n  parameters: {\n    type: 'object',\n    properties: {\n      productId: { type: 'string', description: 'Product ID to check' }\n    },\n    required: ['productId']\n  }\n})\n```\n\n### 4. Run\n\n```typescript\nconst result = await ai.run('How many units of product-001 do we have?')\nconsole.log(result.response)\n// → \"You have 45 units of product-001 in stock.\"\n```\n\n---\n\n## Core Features\n\n### Function Registration\n\nRegister any async function as an AI-callable tool. The AI reads your description to decide when and how to call it.\n\n```typescript\nai.register('functionName', async (args) => {\n  return yourExistingFunction(args)\n}, {\n  description: 'Plain English — what does this function do?',\n  parameters: {\n    type: 'object',\n    properties: {\n      param1: { type: 'string', description: 'What is this parameter?' }\n    },\n    required: ['param1']\n  }\n})\n```\n\n---\n\n### Wrapping External SDKs\n\nEvery other SDK makes you rewrite your code to use it. AILink does not. `ai.wrap()` sits on top of any async function from any SDK — LangChain, Vercel AI SDK, Hugging Face, or anything else. Your existing code stays exactly as it is. Their code stays exactly as it is.\n\n**Already using LangChain or another SDK:**\nKeep using it exactly as you are. One line wraps it with AILink tracking and observability. Nothing underneath changes.\n\n```typescript\n// Your existing LangChain chain — not touching this\nconst chain = prompt.pipe(model).pipe(new StringOutputParser())\n\n// Wrap it with AILink — one line, nothing changes\nconst wrapped = ai.wrap(chain.invoke.bind(chain), {\n  toolName: 'ProductRagChain',\n  role: 'admin'\n})\n\n// Call it exactly as before — now tracked through AILink\nconst result = await wrapped({ question: 'What is the return policy?' })\n```\n\n**Want to add a new SDK to your existing project:**\nRegister your existing functions with `ai.register()` as normal. Wrap the new SDK's function with `ai.wrap()`. Both work together. Zero rewrites anywhere.\n\n```typescript\n// Your existing functions — registered as normal, not touched\nai.register('checkStock', async ({ productId }) => checkStock(productId), {\n  description: 'Check stock levels',\n  parameters: { type: 'object', properties: { productId: { type: 'string' } }, required: ['productId'] }\n})\n\n// New SDK function — wrapped with ai.wrap(), registered as a tool\nconst searchDocs = ai.wrap(retriever.getRelevantDocuments.bind(retriever), {\n  toolName: 'DocumentSearch'\n})\n\n// Both work together — AI picks which to call\nconst result = await ai.run('Check stock for laptop-001 and find the return policy')\n```\n\n**Streaming:**\nAILink does not stream — but your streaming SDK already does. Wrap it.\n\n```typescript\nimport { streamText } from 'ai'\nimport { openai } from '@ai-sdk/openai'\n\n// Your existing streaming function\nasync function streamResponse(prompt: string) {\n  return streamText({ model: openai('gpt-4o'), prompt })\n}\n\n// Wrap it — AILink tracks the call, streaming behavior is unchanged\nconst streamWithTracking = ai.wrap(streamResponse, { toolName: 'StreamedResponse' })\n\n// Call it exactly as you did before — stream still works\nconst stream = await streamWithTracking('Explain quantum entanglement')\nfor await (const chunk of stream.textStream) {\n  process.stdout.write(chunk)\n}\n```\n\n**RAG / document retrieval:**\nYour retrieval pipeline stays untouched. Wrap the function that calls it.\n\n```typescript\nimport { RetrievalQAChain } from 'langchain/chains'\n\nconst chain = RetrievalQAChain.fromLLM(llm, vectorStore.asRetriever())\n\n// Wrap it — LangChain does the retrieval, AILink tracks it\nconst ragSearch = ai.wrap(chain.call.bind(chain), { toolName: 'PolicyRag', role: 'user' })\n\n// Your product policy search — unchanged, now tracked\nconst answer = await ragSearch({ query: 'What is the refund window?' })\nconsole.log(answer.text)\n// → 'Refunds are accepted within 30 days of purchase.'\n```\n\n**React hooks — frontend integration:**\nUse any React hook from Vercel AI SDK or your own SDK. AILink connects from your backend. The frontend does not change.\n\n```typescript\n// Frontend — exactly as it was\nimport { useChat } from 'ai/react'\n\nexport function Chat() {\n  const { messages, input, handleSubmit } = useChat({ api: '/api/chat' })\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={e => setInput(e.target.value)} />\n    </form>\n  )\n}\n\n// Backend — your Express handler calls AILink\nimport { AILink } from '@ailink/sdk'\n\nconst ai = new AILink({ provider: 'groq', providerKey: process.env.GROQ_KEY! })\nai.register('getOrderStatus', async ({ orderId }) => db.orders.status(orderId), {\n  description: 'Get the current status of an order',\n  parameters: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] }\n})\n\napp.post('/api/chat', async (req, res) => {\n  const result = await ai.run(req.body.messages.at(-1).content)\n  res.json({ role: 'assistant', content: result.response })\n})\n```\n\n**onToolCall callbacks:**\nAILink executes tools directly — but if you need side-effects on every call, wrap the function.\n\n```typescript\nasync function getOrderStatus({ orderId }: { orderId: string }) {\n  return db.orders.status(orderId)\n}\n\n// Wrap it to add your own callback behavior\nconst tracked = ai.wrap(async (args: { orderId: string }) => {\n  const result = await getOrderStatus(args)\n  await myAnalytics.log({ tool: 'getOrderStatus', args, result })  // your callback\n  return result\n}, { toolName: 'getOrderStatus' })\n```\n\nSimple mode — zero configuration, works immediately:\n```typescript\nconst wrapped = ai.wrap(anyAsyncFunction)\n```\n\n`WrapOptions`:\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `toolName` | `string` | `fn.name` or `'anonymous'` | Name shown on AILink dashboard. Always set this when wrapping `.bind()` calls — `.bind()` destroys the native function name |\n| `role` | `RoleName` | `'user'` | Role used for tracking context only — not access control |\n\n> **These features are not coming directly to AILink.** Not because they are hard to build — but because adding them would require breaking the architectural foundation this SDK is built on. The architecture is complete and intentional. Every feature you need already exists in other SDKs. `ai.wrap()` connects you to all of them without changing a single line of your existing code.\n\n---\n\n### Multi-Provider Support\n\nSwitch between OpenAI, Claude, Groq, and Gemini by changing one line. Your registered functions never change.\n\n```typescript\n// Using Groq\nconst ai = new AILink({ provider: 'groq', providerKey: process.env.GROQ_KEY! })\n\n// Using OpenAI — everything else stays the same\nconst ai = new AILink({ provider: 'openai', providerKey: process.env.OPENAI_KEY! })\n```\n\n---\n\n### Fallback Providers\n\nIf the primary provider fails, AILink automatically tries the next one.\n\n```typescript\nconst ai = new AILink({\n  provider: 'openai',\n  providerKey: process.env.OPENAI_KEY!,\n  providerKeys: {\n    claude: process.env.CLAUDE_KEY!,\n    groq: process.env.GROQ_KEY!\n  },\n  fallback: ['claude', 'groq'],\n  retries: 3,\n  retryDelay: 1000,\n  platformKey: 'your-key'\n})\n```\n\n---\n\n### Role-Based Access Control\n\nRestrict which tools each role can call. Three built-in roles: `user`, `admin`, `developer`.\n\n```typescript\nai.register('viewData', async () => getData(), {\n  description: 'View public data',\n  parameters: { type: 'object', properties: {} },\n  roles: ['user', 'admin', 'developer']  // everyone\n})\n\nai.register('updateSettings', async (args) => updateSettings(args), {\n  description: 'Update system settings',\n  parameters: { type: 'object', properties: { setting: { type: 'string' } } },\n  roles: ['admin', 'developer']  // admin and above only\n})\n\nai.register('deleteAllData', async () => deleteAll(), {\n  description: 'Delete all data',\n  parameters: { type: 'object', properties: {} },\n  roles: ['developer']  // developer only\n})\n\n// Pass the role at runtime\nawait ai.run('Update the settings', { userRole: 'admin' })    // allowed\nawait ai.run('Delete all data', { userRole: 'user' })         // blocked\n```\n\n> **Note:** AILink trusts the role you pass in. Verifying that a user is actually that role is your responsibility. AILink is a tool orchestration layer, not an authentication system.\n\n**Default behavior:** If `roles` is not specified, the tool is available to all roles.\n\n---\n\n### Group Filtering\n\nOrganize tools into logical groups. Only expose the tools relevant to each request.\n\n```typescript\nai.register('checkStock', async (args) => checkInventory(args), {\n  description: 'Check stock levels',\n  parameters: { type: 'object', properties: { productId: { type: 'string' } } },\n  group: 'inventory'\n})\n\nai.register('processRefund', async (args) => refund(args), {\n  description: 'Process a customer refund',\n  parameters: { type: 'object', properties: { orderId: { type: 'string' } } },\n  group: 'payments'\n})\n\n// Only inventory tools are available for this request\nawait ai.run('How many laptops are in stock?', { groups: ['inventory'] })\n\n// Only payment tools\nawait ai.run('Process a refund for order-123', { groups: ['payments'] })\n\n// Multiple groups\nawait ai.run('Check stock and process refund', { groups: ['inventory', 'payments'] })\n```\n\n---\n\n### Session Memory\n\nUse `createSession()` for multi-turn conversations. The AI remembers previous messages.\n\n```typescript\nconst session = ai.createSession()\n\nawait session.run('My name is Jay and I work at Acme Corp')\nawait session.run('I want to order 5 laptops')\nconst result = await session.run('What is the total cost and who is placing the order?')\n// → AI remembers the name, company, and order from previous turns\nconsole.log(result.response)\n```\n\n> **Important:** Session history is stored in memory. A server restart will clear all active sessions. If you need sessions to survive restarts, save `session.getHistory()` to a database and restore it with `ai.createSession(sessionId, maxTurns, savedHistory)`.\n\n**Save and restore sessions:**\n\n```typescript\n// Save\nconst history = session.getHistory()\nconst sessionId = session.sessionId\n// Store both in your database\n\n// Restore later\nconst restored = ai.createSession(sessionId, 50, savedHistory)\nawait restored.run('Continue from where we left off')\n```\n\n**Session options:**\n\n```typescript\nai.createSession(\n  sessionId?,       // Custom ID — auto-generated if not provided\n  maxTurns?,        // Max turns to keep in memory. Default: 50. Older turns are pruned automatically.\n  initialHistory?   // Restore a saved session\n)\n```\n\n---\n\n### Parallel Tool Execution\n\nWhen the AI decides to call multiple tools, AILink runs them simultaneously.\n\n```typescript\nai.register('fetchUser', async ({ userId }) => getUser(userId), { ... })\nai.register('fetchOrders', async ({ userId }) => getOrders(userId), { ... })\nai.register('fetchPayments', async ({ userId }) => getPayments(userId), { ... })\n\n// All three run in parallel automatically\nconst result = await ai.run('Give me the full profile for user-123')\n```\n\n---\n\n### Usage Tracking\n\nEvery `ai.run()` call and every `ai.wrap()` call logs usage data asynchronously in the background. Logs include: prompt, tools called, provider used, execution time, role, and groups. For `run()` calls, session ID is also logged. For `wrap()` calls, session ID is always `null` — `wrap()` has no session context.\n\nUse `platformUrl` to send logs to any server or dashboard you choose:\n\n```typescript\nconst ai = new AILink({\n  provider: 'groq',\n  providerKey: process.env.GROQ_KEY!,\n  platformUrl: 'https://your-dashboard.com/logs',\n  platformKey: 'your-dashboard-key'\n})\n```\n\nIf `platformUrl` is not set, logging is skipped. The AILink platform dashboard is currently in development — when it launches, you will receive a `platformUrl` and `platformKey` from your AILink account.\n\nLogging always fails silently and never affects your application.\n\n---\n\n### Configurable Iteration Limit\n\nControl how many tool-call loops the engine runs before stopping.\n\n```typescript\nconst ai = new AILink({\n  provider: 'groq',\n  providerKey: process.env.GROQ_KEY!,\n  platformKey: 'your-key',\n  maxIterations: 5  // default is 10\n})\n```\n\n---\n\n### Chat Widget\n\nDrop a chat interface into any web page.\n\n**Plain HTML:**\n```html\n<div\n  id=\"ailink-widget\"\n  data-provider=\"groq\"\n  data-provider-key=\"your-groq-key\"\n  data-title=\"AI Assistant\"\n  data-position=\"bottom-right\"\n></div>\n<script src=\"./dist/widget/AILinkScript.js\"></script>\n```\n\n**React:**\n```tsx\nimport { AILinkWidget } from '@ailink/sdk/widget'\n\nexport default function App() {\n  return (\n    <AILinkWidget\n      provider=\"groq\"\n      providerKey={process.env.REACT_APP_GROQ_KEY!}\n      title=\"AI Assistant\"\n    />\n  )\n}\n```\n\n---\n\n### Managing Tools\n\nList all registered tool names:\n\n```typescript\nconst names = ai.tools()\n// → ['checkStock', 'processRefund', 'getWeather']\n```\n\nGet the full definition for every registered tool — name, description, schema, roles, and group:\n\n```typescript\nconst defs = ai.toolDefinitions()\nconsole.log(defs[0])\n// → {\n// →   name: 'checkStock',\n// →   description: 'Check how many units of a product are in stock',\n// →   schema: {\n// →     type: 'object',\n// →     properties: { productId: { type: 'string', description: 'Product ID to check' } },\n// →     required: ['productId']\n// →   },\n// →   roles: ['user', 'admin', 'developer'],\n// →   group: 'inventory'\n// → }\n```\n\nUse `toolDefinitions()` when you need the full tool metadata — for building custom dashboards, inspecting what the AI has access to, or generating documentation from your registered tools at runtime.\n\nRemove a registered tool at runtime:\n\n```typescript\nai.unregister('getWeather')\n// The tool is immediately removed from the registry.\n// Any subsequent ai.run() call will no longer have access to it.\n```\n\n---\n\n## Configuration Reference\n\n```typescript\nconst ai = new AILink({\n  // Required\n  provider: 'openai' | 'claude' | 'groq' | 'gemini',\n  providerKey: string,       // Your provider API key\n\n  // Optional\n  platformKey?: string,      // Auth key for your logging platform. Optional.\n  model?: string,            // Override the default model for your provider\n  providerKeys?: {           // API keys for fallback providers\n    openai?: string,\n    claude?: string,\n    groq?: string,\n    gemini?: string\n  },\n  fallback?: ProviderName[],    // Fallback providers in order of preference\n  retries?: number,             // Retry attempts per provider. Default: 3\n  retryDelay?: number,          // Delay between retries in ms. Default: 1000\n  maxIterations?: number,       // Max tool-call loop iterations. Default: 10\n  debug?: boolean,              // Log engine calls to console. Default: false\n  environment?: 'development' | 'staging' | 'production',\n  platformUrl?: string          // Send usage logs to your own server or dashboard\n})\n```\n\n### Default Models\n\n| Provider | Default Model |\n|----------|---------------|\n| OpenAI | `gpt-4o-mini` |\n| Claude | `claude-3-5-haiku-latest` |\n| Groq | `llama-3.3-70b-versatile` |\n| Gemini | `gemini-1.5-flash` |\n\n---\n\n## Result Object\n\nEvery `ai.run()` and `session.run()` returns:\n\n```typescript\n{\n  response: string        // The AI's final natural language response\n  toolsCalled: string[]   // Names of tools that were called\n  allowedTools: string[]  // Names of tools that were available for this request\n  executionTime: number   // Total time in milliseconds\n  provider: ProviderName  // Which provider was used (may differ from primary if fallback triggered)\n  userRole: RoleName      // Role used for this request\n  groups: string[] | null // Groups used for filtering, or null if none specified\n  promptTokens: number | null   // Input tokens used — null if provider did not return usage data\n  completionTokens: number | null // Output tokens used — null if provider did not return usage data\n}\n```\n\nWhat it looks like after a real tool call:\n\n```typescript\nconst result = await ai.run('How many units of laptop-001 are in stock?', { userRole: 'admin' })\nconsole.log(result)\n// → {\n// →   response: 'There are 42 units of laptop-001 currently in stock.',\n// →   toolsCalled: ['checkStock'],\n// →   allowedTools: ['checkStock', 'processRefund', 'getWeather'],\n// →   executionTime: 834,\n// →   provider: 'groq',\n// →   userRole: 'admin',\n// →   groups: null,\n// →   promptTokens: 312,\n// →   completionTokens: 28\n// → }\n```\n\n`session.run()` returns the same object.\n\n---\n\n## Error Handling\n\n```typescript\nimport {\n  AILinkConfigError,\n  AllProvidersFailedError,\n  EmptyGroupError,\n  ToolAlreadyExistsError,\n  ToolNotFoundError,\n  ValidationError,\n  ToolExecutionError,\n  UnsupportedProviderError\n} from '@ailink/sdk'\n\ntry {\n  const result = await ai.run('User request')\n  console.log(result.response)\n} catch (error) {\n  if (error instanceof AILinkConfigError) {\n    // Missing or invalid configuration\n  } else if (error instanceof AllProvidersFailedError) {\n    // Primary provider and all fallbacks failed\n  } else if (error instanceof EmptyGroupError) {\n    // No tools found for the specified groups\n  } else if (error instanceof ValidationError) {\n    // AI passed invalid arguments to a tool\n  }\n}\n```\n\n---\n\n## Project Structure\n\n```\nsrc/\n├── index.ts          — Exports\n├── ailink.ts         — Main AILink class\n├── engine.ts         — Tool-call loop\n├── session.ts        — Conversation memory\n├── registry.ts       — Function registration\n├── validator.ts      — JSON Schema validation\n├── tracker.ts        — Usage tracking\n├── types.ts          — TypeScript types\n├── errors.ts         — Error classes\n├── providers/        — Provider adapters\n│   ├── openai.ts\n│   ├── claude.ts\n│   ├── groq.ts\n│   └── gemini.ts\n└── widget/           — Chat widget\n    ├── AILinkWidget.tsx\n    └── AILinkScript.ts\n```\n\n---\n\n## Testing\n\n```bash\nnpm test                    # All tests\nnpm run test:unit           # Unit tests only (~5s)\nnpm run test:integration    # Integration tests\nnpm run test:e2e            # End-to-end tests\nnpm run test:coverage       # Coverage report\n```\n\n---\n\n## Common Patterns\n\n**Express backend:**\n```typescript\nimport { AILink } from '@ailink/sdk'\n\nconst ai = new AILink({ provider: 'groq', providerKey: process.env.GROQ_KEY! })\n\n// Register your existing functions\nai.register('getOrder', async ({ orderId }) => db.orders.find(orderId), {\n  description: 'Get order details by order ID',\n  parameters: {\n    type: 'object',\n    properties: { orderId: { type: 'string' } },\n    required: ['orderId']\n  }\n})\n\napp.post('/chat', async (req, res) => {\n  const result = await ai.run(req.body.message, { userRole: req.user.role })\n  res.json({ response: result.response })\n})\n```\n\n**Session with Express:**\n```typescript\nconst sessions = new Map()\n\napp.post('/chat/:sessionId', async (req, res) => {\n  let session = sessions.get(req.params.sessionId)\n  if (!session) {\n    session = ai.createSession(req.params.sessionId)\n    sessions.set(req.params.sessionId, session)\n  }\n  const result = await session.run(req.body.message)\n  res.json({ response: result.response })\n})\n```\n\n**Debug mode:**\n```typescript\nconst ai = new AILink({\n  provider: 'groq',\n  providerKey: process.env.GROQ_KEY!,\n  debug: true  // logs all engine calls to console\n})\n```\n\n---\n\n## FAQ\n\n**Do I need to rewrite my existing code?**\nNo. Register your existing functions as-is. AILink calls them — you don't modify them.\n\n**Which provider should I start with?**\nGroq — free tier, no credit card required, fast inference. Get a key at console.groq.com.\n\n**Can I use multiple providers?**\nYes. Set a primary provider and configure fallbacks. AILink switches automatically if the primary fails.\n\n**Are sessions persistent across server restarts?**\nNo. Sessions are in-memory. Save `session.getHistory()` to a database if you need persistence.\n\n**Who verifies that a user has a certain role?**\nYou do, before calling AILink. AILink trusts the role you pass in — it does not authenticate users.\n\n**What if I call a group that has no tools?**\nAILink throws `EmptyGroupError` immediately, before making any provider calls.\n\n**What happens when maxIterations is reached?**\nThe engine stops the loop and returns whatever response it has. No crash, no hanging.\n\n**Can I use LangChain, Vercel AI SDK, or any other SDK alongside AILink?**\nYes. Use `ai.wrap()` to wrap any async function from any SDK. Your existing code stays exactly as it is. Their code stays exactly as it is. AILink sits on top of everything. No rewrites. No restructuring. Any SDK. Any function. Zero changes.\n\n---\n\n## Troubleshooting\n\n**`AILinkConfigError: providerKey is required`**\nYou're missing the provider API key in your config.\n\n**`AllProvidersFailedError`**\nYour API key is invalid or expired, or you've hit a rate limit. Check your provider's dashboard.\n\n**`EmptyGroupError`**\nYou passed a group name that has no tools registered to it. Check your group names in `ai.register()`.\n\n**Tests timing out**\nProvider integration tests make real API calls. Use `npm run test:unit` for fast local testing.\n\n---\n\n## License\n\nMIT — free to use in commercial and personal projects.\n\n---\n\n## Contributing\n\nIssues and pull requests are welcome at [github.com/getailink/ailink-sdk](https://github.com/getailink/ailink-sdk).\n","readmeFilename":"README.md"}