JSPM

@ailink/sdk

0.3.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 26
  • Score
    100M100P100Q76471F
  • License MIT

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.

Package Exports

  • @ailink/sdk
  • @ailink/sdk/widget

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


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

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.