npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ailink/sdk

v0.3.0

Published

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.

Readme

AILink SDK

Your app already has the functions. AILink makes them AI-callable. Use LangChain, Vercel AI SDK, or any other SDK without touching your existing code. AILink makes that possible.

No rewrites. No restructuring. Register once, your users talk to your app in natural language.

Works with: OpenAI · Claude · Groq · Gemini Use alongside: LangChain · Vercel AI SDK · any existing SDK — zero rewrites

npm install @ailink/sdk

Contents


Why AILink

Every 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.

// Your existing function — untouched
async function getWeather(city: string) {
  return `22°C and sunny in ${city}`
}

// Register it once with AILink
ai.register('getWeather', async ({ city }) => getWeather(city), {
  description: 'Get current weather for a city',
  parameters: {
    type: 'object',
    properties: {
      city: { type: 'string', description: 'City name' }
    },
    required: ['city']
  }
})

// Now anyone can use it with natural language
const result = await ai.run('What is the weather in Tokyo?')
console.log(result.response)
// → "It's currently 22°C and sunny in Tokyo."

How AILink Compares

| Feature | AILink | Vercel AI SDK | LangChain | |---|---|---|---| | Works with existing code — no rewrites | ✅ | ❌ | ❌ | | Use any external SDK without touching your code | ✅ | ❌ | ❌ | | Role-based tool filtering — built in | ✅ | ❌ | ❌ | | Group-based tool filtering | ✅ | ❌ | ❌ | | Automatic fallback providers | ✅ | Via AI Gateway only | ✅ | | Session memory — built in | ✅ | ✅ | ✅ Via LangGraph | | Parallel tool execution | ✅ | ✅ | ✅ Via LangGraph | | Usage tracking — built in | ✅ | ✅ Via OpenTelemetry | ✅ Via Callbacks/OTel | | Multi-provider support | ✅ | ✅ | ✅ | | Drop-in chat widget | ✅ | ❌ | ❌ | | Streaming responses | ❌ via ai.wrap() | ✅ | ✅ | | RAG / document retrieval | ❌ via ai.wrap() | ❌ | ✅ | | Frontend React hooks | ❌ via ai.wrap() | ✅ | ❌ |

The ❌ 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.


Install

npm install @ailink/sdk

Requires Node.js 18 or higher.


Quick Start

1. Get a provider API key

Pick one — all work the same way:

| Provider | Free Tier | Get Key | |----------|-----------|---------| | Groq | Yes, no card needed | console.groq.com | | Gemini | Yes, no card needed | aistudio.google.com | | OpenAI | Paid | platform.openai.com | | Claude | Paid | console.anthropic.com |

2. Initialize

import { AILink } from '@ailink/sdk'

const ai = new AILink({
  provider: 'groq',
  providerKey: process.env.GROQ_KEY!
})

3. Register your functions

ai.register('checkStock', async ({ productId }) => {
  // your existing function
  return inventory.check(productId)
}, {
  description: 'Check how many units of a product are in stock',
  parameters: {
    type: 'object',
    properties: {
      productId: { type: 'string', description: 'Product ID to check' }
    },
    required: ['productId']
  }
})

4. Run

const result = await ai.run('How many units of product-001 do we have?')
console.log(result.response)
// → "You have 45 units of product-001 in stock."

Core Features

Function Registration

Register any async function as an AI-callable tool. The AI reads your description to decide when and how to call it.

ai.register('functionName', async (args) => {
  return yourExistingFunction(args)
}, {
  description: 'Plain English — what does this function do?',
  parameters: {
    type: 'object',
    properties: {
      param1: { type: 'string', description: 'What is this parameter?' }
    },
    required: ['param1']
  }
})

Wrapping External SDKs

Every 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.

Already using LangChain or another SDK: Keep using it exactly as you are. One line wraps it with AILink tracking and observability. Nothing underneath changes.

// Your existing LangChain chain — not touching this
const chain = prompt.pipe(model).pipe(new StringOutputParser())

// Wrap it with AILink — one line, nothing changes
const wrapped = ai.wrap(chain.invoke.bind(chain), {
  toolName: 'ProductRagChain',
  role: 'admin'
})

// Call it exactly as before — now tracked through AILink
const result = await wrapped({ question: 'What is the return policy?' })

Want to add a new SDK to your existing project: Register your existing functions with ai.register() as normal. Wrap the new SDK's function with ai.wrap(). Both work together. Zero rewrites anywhere.

// Your existing functions — registered as normal, not touched
ai.register('checkStock', async ({ productId }) => checkStock(productId), {
  description: 'Check stock levels',
  parameters: { type: 'object', properties: { productId: { type: 'string' } }, required: ['productId'] }
})

// New SDK function — wrapped with ai.wrap(), registered as a tool
const searchDocs = ai.wrap(retriever.getRelevantDocuments.bind(retriever), {
  toolName: 'DocumentSearch'
})

// Both work together — AI picks which to call
const result = await ai.run('Check stock for laptop-001 and find the return policy')

Streaming: AILink does not stream — but your streaming SDK already does. Wrap it.

import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'

// Your existing streaming function
async function streamResponse(prompt: string) {
  return streamText({ model: openai('gpt-4o'), prompt })
}

// Wrap it — AILink tracks the call, streaming behavior is unchanged
const streamWithTracking = ai.wrap(streamResponse, { toolName: 'StreamedResponse' })

// Call it exactly as you did before — stream still works
const stream = await streamWithTracking('Explain quantum entanglement')
for await (const chunk of stream.textStream) {
  process.stdout.write(chunk)
}

RAG / document retrieval: Your retrieval pipeline stays untouched. Wrap the function that calls it.

import { RetrievalQAChain } from 'langchain/chains'

const chain = RetrievalQAChain.fromLLM(llm, vectorStore.asRetriever())

// Wrap it — LangChain does the retrieval, AILink tracks it
const ragSearch = ai.wrap(chain.call.bind(chain), { toolName: 'PolicyRag', role: 'user' })

// Your product policy search — unchanged, now tracked
const answer = await ragSearch({ query: 'What is the refund window?' })
console.log(answer.text)
// → 'Refunds are accepted within 30 days of purchase.'

React hooks — frontend integration: Use any React hook from Vercel AI SDK or your own SDK. AILink connects from your backend. The frontend does not change.

// Frontend — exactly as it was
import { useChat } from 'ai/react'

export function Chat() {
  const { messages, input, handleSubmit } = useChat({ api: '/api/chat' })
  return (
    <form onSubmit={handleSubmit}>
      <input value={input} onChange={e => setInput(e.target.value)} />
    </form>
  )
}

// Backend — your Express handler calls AILink
import { AILink } from '@ailink/sdk'

const ai = new AILink({ provider: 'groq', providerKey: process.env.GROQ_KEY! })
ai.register('getOrderStatus', async ({ orderId }) => db.orders.status(orderId), {
  description: 'Get the current status of an order',
  parameters: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] }
})

app.post('/api/chat', async (req, res) => {
  const result = await ai.run(req.body.messages.at(-1).content)
  res.json({ role: 'assistant', content: result.response })
})

onToolCall callbacks: AILink executes tools directly — but if you need side-effects on every call, wrap the function.

async function getOrderStatus({ orderId }: { orderId: string }) {
  return db.orders.status(orderId)
}

// Wrap it to add your own callback behavior
const tracked = ai.wrap(async (args: { orderId: string }) => {
  const result = await getOrderStatus(args)
  await myAnalytics.log({ tool: 'getOrderStatus', args, result })  // your callback
  return result
}, { toolName: 'getOrderStatus' })

Simple mode — zero configuration, works immediately:

const wrapped = ai.wrap(anyAsyncFunction)

WrapOptions:

| Field | Type | Default | Description | |---|---|---|---| | toolName | string | fn.name or 'anonymous' | Name shown on AILink dashboard. Always set this when wrapping .bind() calls — .bind() destroys the native function name | | role | RoleName | 'user' | Role used for tracking context only — not access control |

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.


Multi-Provider Support

Switch between OpenAI, Claude, Groq, and Gemini by changing one line. Your registered functions never change.

// Using Groq
const ai = new AILink({ provider: 'groq', providerKey: process.env.GROQ_KEY! })

// Using OpenAI — everything else stays the same
const ai = new AILink({ provider: 'openai', providerKey: process.env.OPENAI_KEY! })

Fallback Providers

If the primary provider fails, AILink automatically tries the next one.

const ai = new AILink({
  provider: 'openai',
  providerKey: process.env.OPENAI_KEY!,
  providerKeys: {
    claude: process.env.CLAUDE_KEY!,
    groq: process.env.GROQ_KEY!
  },
  fallback: ['claude', 'groq'],
  retries: 3,
  retryDelay: 1000,
  platformKey: 'your-key'
})

Role-Based Access Control

Restrict which tools each role can call. Three built-in roles: user, admin, developer.

ai.register('viewData', async () => getData(), {
  description: 'View public data',
  parameters: { type: 'object', properties: {} },
  roles: ['user', 'admin', 'developer']  // everyone
})

ai.register('updateSettings', async (args) => updateSettings(args), {
  description: 'Update system settings',
  parameters: { type: 'object', properties: { setting: { type: 'string' } } },
  roles: ['admin', 'developer']  // admin and above only
})

ai.register('deleteAllData', async () => deleteAll(), {
  description: 'Delete all data',
  parameters: { type: 'object', properties: {} },
  roles: ['developer']  // developer only
})

// Pass the role at runtime
await ai.run('Update the settings', { userRole: 'admin' })    // allowed
await ai.run('Delete all data', { userRole: 'user' })         // blocked

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.

Default behavior: If roles is not specified, the tool is available to all roles.


Group Filtering

Organize tools into logical groups. Only expose the tools relevant to each request.

ai.register('checkStock', async (args) => checkInventory(args), {
  description: 'Check stock levels',
  parameters: { type: 'object', properties: { productId: { type: 'string' } } },
  group: 'inventory'
})

ai.register('processRefund', async (args) => refund(args), {
  description: 'Process a customer refund',
  parameters: { type: 'object', properties: { orderId: { type: 'string' } } },
  group: 'payments'
})

// Only inventory tools are available for this request
await ai.run('How many laptops are in stock?', { groups: ['inventory'] })

// Only payment tools
await ai.run('Process a refund for order-123', { groups: ['payments'] })

// Multiple groups
await ai.run('Check stock and process refund', { groups: ['inventory', 'payments'] })

Session Memory

Use createSession() for multi-turn conversations. The AI remembers previous messages.

const session = ai.createSession()

await session.run('My name is Jay and I work at Acme Corp')
await session.run('I want to order 5 laptops')
const result = await session.run('What is the total cost and who is placing the order?')
// → AI remembers the name, company, and order from previous turns
console.log(result.response)

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).

Save and restore sessions:

// Save
const history = session.getHistory()
const sessionId = session.sessionId
// Store both in your database

// Restore later
const restored = ai.createSession(sessionId, 50, savedHistory)
await restored.run('Continue from where we left off')

Session options:

ai.createSession(
  sessionId?,       // Custom ID — auto-generated if not provided
  maxTurns?,        // Max turns to keep in memory. Default: 50. Older turns are pruned automatically.
  initialHistory?   // Restore a saved session
)

Parallel Tool Execution

When the AI decides to call multiple tools, AILink runs them simultaneously.

ai.register('fetchUser', async ({ userId }) => getUser(userId), { ... })
ai.register('fetchOrders', async ({ userId }) => getOrders(userId), { ... })
ai.register('fetchPayments', async ({ userId }) => getPayments(userId), { ... })

// All three run in parallel automatically
const result = await ai.run('Give me the full profile for user-123')

Usage Tracking

Every 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 nullwrap() has no session context.

Use platformUrl to send logs to any server or dashboard you choose:

const ai = new AILink({
  provider: 'groq',
  providerKey: process.env.GROQ_KEY!,
  platformUrl: 'https://your-dashboard.com/logs',
  platformKey: 'your-dashboard-key'
})

If 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.

Logging always fails silently and never affects your application.


Configurable Iteration Limit

Control how many tool-call loops the engine runs before stopping.

const ai = new AILink({
  provider: 'groq',
  providerKey: process.env.GROQ_KEY!,
  platformKey: 'your-key',
  maxIterations: 5  // default is 10
})

Chat Widget

Drop a chat interface into any web page.

Plain HTML:

<div
  id="ailink-widget"
  data-provider="groq"
  data-provider-key="your-groq-key"
  data-title="AI Assistant"
  data-position="bottom-right"
></div>
<script src="./dist/widget/AILinkScript.js"></script>

React:

import { AILinkWidget } from '@ailink/sdk/widget'

export default function App() {
  return (
    <AILinkWidget
      provider="groq"
      providerKey={process.env.REACT_APP_GROQ_KEY!}
      title="AI Assistant"
    />
  )
}

Managing Tools

List all registered tool names:

const names = ai.tools()
// → ['checkStock', 'processRefund', 'getWeather']

Get the full definition for every registered tool — name, description, schema, roles, and group:

const defs = ai.toolDefinitions()
console.log(defs[0])
// → {
// →   name: 'checkStock',
// →   description: 'Check how many units of a product are in stock',
// →   schema: {
// →     type: 'object',
// →     properties: { productId: { type: 'string', description: 'Product ID to check' } },
// →     required: ['productId']
// →   },
// →   roles: ['user', 'admin', 'developer'],
// →   group: 'inventory'
// → }

Use 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.

Remove a registered tool at runtime:

ai.unregister('getWeather')
// The tool is immediately removed from the registry.
// Any subsequent ai.run() call will no longer have access to it.

Configuration Reference

const ai = new AILink({
  // Required
  provider: 'openai' | 'claude' | 'groq' | 'gemini',
  providerKey: string,       // Your provider API key

  // Optional
  platformKey?: string,      // Auth key for your logging platform. Optional.
  model?: string,            // Override the default model for your provider
  providerKeys?: {           // API keys for fallback providers
    openai?: string,
    claude?: string,
    groq?: string,
    gemini?: string
  },
  fallback?: ProviderName[],    // Fallback providers in order of preference
  retries?: number,             // Retry attempts per provider. Default: 3
  retryDelay?: number,          // Delay between retries in ms. Default: 1000
  maxIterations?: number,       // Max tool-call loop iterations. Default: 10
  debug?: boolean,              // Log engine calls to console. Default: false
  environment?: 'development' | 'staging' | 'production',
  platformUrl?: string          // Send usage logs to your own server or dashboard
})

Default Models

| Provider | Default Model | |----------|---------------| | OpenAI | gpt-4o-mini | | Claude | claude-3-5-haiku-latest | | Groq | llama-3.3-70b-versatile | | Gemini | gemini-1.5-flash |


Result Object

Every ai.run() and session.run() returns:

{
  response: string        // The AI's final natural language response
  toolsCalled: string[]   // Names of tools that were called
  allowedTools: string[]  // Names of tools that were available for this request
  executionTime: number   // Total time in milliseconds
  provider: ProviderName  // Which provider was used (may differ from primary if fallback triggered)
  userRole: RoleName      // Role used for this request
  groups: string[] | null // Groups used for filtering, or null if none specified
  promptTokens: number | null   // Input tokens used — null if provider did not return usage data
  completionTokens: number | null // Output tokens used — null if provider did not return usage data
}

What it looks like after a real tool call:

const result = await ai.run('How many units of laptop-001 are in stock?', { userRole: 'admin' })
console.log(result)
// → {
// →   response: 'There are 42 units of laptop-001 currently in stock.',
// →   toolsCalled: ['checkStock'],
// →   allowedTools: ['checkStock', 'processRefund', 'getWeather'],
// →   executionTime: 834,
// →   provider: 'groq',
// →   userRole: 'admin',
// →   groups: null,
// →   promptTokens: 312,
// →   completionTokens: 28
// → }

session.run() returns the same object.


Error Handling

import {
  AILinkConfigError,
  AllProvidersFailedError,
  EmptyGroupError,
  ToolAlreadyExistsError,
  ToolNotFoundError,
  ValidationError,
  ToolExecutionError,
  UnsupportedProviderError
} from '@ailink/sdk'

try {
  const result = await ai.run('User request')
  console.log(result.response)
} catch (error) {
  if (error instanceof AILinkConfigError) {
    // Missing or invalid configuration
  } else if (error instanceof AllProvidersFailedError) {
    // Primary provider and all fallbacks failed
  } else if (error instanceof EmptyGroupError) {
    // No tools found for the specified groups
  } else if (error instanceof ValidationError) {
    // AI passed invalid arguments to a tool
  }
}

Project Structure

src/
├── index.ts          — Exports
├── ailink.ts         — Main AILink class
├── engine.ts         — Tool-call loop
├── session.ts        — Conversation memory
├── registry.ts       — Function registration
├── validator.ts      — JSON Schema validation
├── tracker.ts        — Usage tracking
├── types.ts          — TypeScript types
├── errors.ts         — Error classes
├── providers/        — Provider adapters
│   ├── openai.ts
│   ├── claude.ts
│   ├── groq.ts
│   └── gemini.ts
└── widget/           — Chat widget
    ├── AILinkWidget.tsx
    └── AILinkScript.ts

Testing

npm test                    # All tests
npm run test:unit           # Unit tests only (~5s)
npm run test:integration    # Integration tests
npm run test:e2e            # End-to-end tests
npm run test:coverage       # Coverage report

Common Patterns

Express backend:

import { AILink } from '@ailink/sdk'

const ai = new AILink({ provider: 'groq', providerKey: process.env.GROQ_KEY! })

// Register your existing functions
ai.register('getOrder', async ({ orderId }) => db.orders.find(orderId), {
  description: 'Get order details by order ID',
  parameters: {
    type: 'object',
    properties: { orderId: { type: 'string' } },
    required: ['orderId']
  }
})

app.post('/chat', async (req, res) => {
  const result = await ai.run(req.body.message, { userRole: req.user.role })
  res.json({ response: result.response })
})

Session with Express:

const sessions = new Map()

app.post('/chat/:sessionId', async (req, res) => {
  let session = sessions.get(req.params.sessionId)
  if (!session) {
    session = ai.createSession(req.params.sessionId)
    sessions.set(req.params.sessionId, session)
  }
  const result = await session.run(req.body.message)
  res.json({ response: result.response })
})

Debug mode:

const ai = new AILink({
  provider: 'groq',
  providerKey: process.env.GROQ_KEY!,
  debug: true  // logs all engine calls to console
})

FAQ

Do I need to rewrite my existing code? No. Register your existing functions as-is. AILink calls them — you don't modify them.

Which provider should I start with? Groq — free tier, no credit card required, fast inference. Get a key at console.groq.com.

Can I use multiple providers? Yes. Set a primary provider and configure fallbacks. AILink switches automatically if the primary fails.

Are sessions persistent across server restarts? No. Sessions are in-memory. Save session.getHistory() to a database if you need persistence.

Who verifies that a user has a certain role? You do, before calling AILink. AILink trusts the role you pass in — it does not authenticate users.

What if I call a group that has no tools? AILink throws EmptyGroupError immediately, before making any provider calls.

What happens when maxIterations is reached? The engine stops the loop and returns whatever response it has. No crash, no hanging.

Can I use LangChain, Vercel AI SDK, or any other SDK alongside AILink? Yes. 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.


Troubleshooting

AILinkConfigError: providerKey is required You're missing the provider API key in your config.

AllProvidersFailedError Your API key is invalid or expired, or you've hit a rate limit. Check your provider's dashboard.

EmptyGroupError You passed a group name that has no tools registered to it. Check your group names in ai.register().

Tests timing out Provider integration tests make real API calls. Use npm run test:unit for fast local testing.


License

MIT — free to use in commercial and personal projects.


Contributing

Issues and pull requests are welcome at github.com/getailink/ailink-sdk.