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

@ingram-tech/ai-sdk-adapter

v0.4.0

Published

Vercel AI SDK adapter for Ingram Cloud — a pre-configured OpenAI-compatible provider plus smith identity, thread memory, and human-in-the-loop approval helpers. Built on industry-standard surfaces; no proprietary protocol required.

Readme

@ingram-tech/ai-sdk-adapter

Drive an Ingram Cloud smith from the Vercel AI SDK. It is a thin, idiomatic extension of the AI SDK: a pre-configured provider plus small helpers for the few things Ingram Cloud adds on top — smith identity, server-side memory, and human-in-the-loop approvals.

Philosophy: stand on the standard

This package is deliberately not a bespoke protocol client. The main entry point is @ai-sdk/openai-compatible pointed at Ingram Cloud's OpenAI-compatible API, so your app speaks the OpenAI Chat Completions wire format end-to-end. Approvals ride the standard tool-call channel; memory rides a single request header. There is no custom SSE envelope to parse and no streamText replacement to learn.

A smith still runs the agent loop server-side (that's the whole point — memory, tools, approvals, isolation), but from your code it looks like any other model.

The native run envelope (/v1/smiths/{id}/runs) is available behind the opt-in /native subpath for the one thing the standard surface doesn't yet carry — live tool-progress frames. Prefer the standard provider.

Install

npm install @ingram-tech/ai-sdk-adapter ai

ai (v6+) is a peer dependency — you already have it. @ai-sdk/react is only needed for the client helpers.

Quickstart

Server (streamText, generateText, agents)

import { createIngramCloud } from "@ingram-tech/ai-sdk-adapter";
import { streamText } from "ai";

// A per-smith token names exactly one smith; the agent is the one that smith runs.
// The model id is the inference LLM: "" uses the agent's configured model, or pass
// a model id (e.g. "gpt-5.5") to override the LLM for that call.
const ingram = createIngramCloud({ apiKey: process.env.IC_SMITH_TOKEN! });

const result = streamText({
  model: ingram(""),
  prompt: "How do I reset my password?",
});

for await (const delta of result.textStream) process.stdout.write(delta);

Server-side with a tenant-admin token instead? Name the smith explicitly:

const ingram = createIngramCloud({
  apiKey: process.env.IC_TENANT_TOKEN!,
  smithId: "smt_…",
});

Never ship a tenant-admin token to the browser — proxy through your backend.

Client (useChat)

The recommended shape is a proxy route: the browser talks to your /api/chat route, which holds the token and runs createIngramCloud. The client is plain AI SDK:

"use client";
import { useChat } from "@ai-sdk/react";
import { ingramCloudTransport, approvalsSettled } from "@ingram-tech/ai-sdk-adapter/react";

export function Chat() {
  const { messages, sendMessage } = useChat({
    transport: ingramCloudTransport({ api: "/api/chat" }),
    // auto-resume a turn once every approval has a decision
    sendAutomaticallyWhen: approvalsSettled,
  });
  // …render messages, call sendMessage(...)
}

Memory: one header

Plain Chat Completions is stateless. Pass a threadId and Ingram Cloud holds the conversation server-side (the same thread model as a native run): you send only the new turn, and memory works.

const ingram = createIngramCloud({
  apiKey: SMITH_TOKEN,
  threadId: `chat_${conversationId}`, // sent as IC-Thread-Id
});

Structured outputs (generateObject)

The provider advertises structured outputs, so generateObject sends your schema as a strict response_format and Ingram Cloud enforces it — conforming JSON or an error, never a best-effort guess:

import { generateObject } from "ai";
import { z } from "zod";

const { object } = await generateObject({
  model: ingram(""),
  schema: z.object({
    invoice_number: z.string().nullable(),
    total: z.number().nullable(),
  }),
  prompt: "Invoice #A-1, total 100 EUR.",
});

The schema'd call is a stateless one-shot (no tools, no memory) — use a provider without threadId for it; a threadId provider is rejected with a 400.

Approvals (human-in-the-loop)

A tool the agent marks destructiveHint pauses the run for approval. On this surface the pause arrives as a normal tool call whose id is "<run_id>::<tool_call_id>", and the turn ends with finish_reason: "tool_calls". Pull the pending approvals off the result and resume by appending a decision:

import {
  createIngramCloud,
  getApprovalRequests,
  approvalToolResult,
} from "@ingram-tech/ai-sdk-adapter";
import { generateText } from "ai";

const ingram = createIngramCloud({ apiKey: SMITH_TOKEN, threadId });
const first = await generateText({ model: ingram(""), messages });

const approvals = getApprovalRequests(first.toolCalls);
if (approvals.length) {
  const decided = await askTheHuman(approvals); // your UI/policy
  const resumed = await generateText({
    model: ingram(""),
    messages: [
      ...messages,
      ...decided.map((a) => approvalToolResult(a.request, a.ok ? "approve" : "reject")),
    ],
  });
}

On approve, Ingram Cloud executes the tool itself and continues; on reject, the run completes with stop_reason: "approval_rejected" and nothing runs. Calling /v1/chat/completions directly without AI SDK message conversion? Use approvalWireMessage(id, "approve") to build the raw tool message.

Tools

Two models, both standard — pick per use case:

  • Client-side tools (you run them). Define tools with the AI SDK's tool() and pass them to streamText/generateText as with any provider. The model's calls come back for you to execute; the SDK loops by re-sending the conversation. Ingram Cloud executes nothing — the standard OpenAI function-call contract, no Ingram-specific setup.

    import { tool } from "ai";
    import { z } from "zod";
    
    const result = streamText({
      model: ingram(""),
      messages,
      tools: { get_weather: tool({ description: "…", inputSchema: z.object({ city: z.string() }) }) },
    });

    A turn that passes tools runs only those client tools (the agent still supplies instructions; its server-side tools/memory sit out that turn).

  • Server-side tools (MCP). Ingram Cloud calls your MCP server and runs the tools for you, with approval gating. Don't pass tools — register the MCP server once and it's available to the smith automatically. For shared/remote tools.

Identity & tokens

| Token | Use | How the smith is chosen | |---|---|---| | Smith token (sub = "<tenant>:<smith>") | browser-safe; the default | the token is the smith | | Tenant-admin token | server-side only | pass smithId (sent as IC-Smith-Id) |

The agent is the one the smith runs — chosen by the smith, never by an argument. The model argument is the upstream inference LLM: "" uses the agent's configured model; a model id (e.g. gpt-5.5) overrides the LLM for that call.

Native fallback

@ingram-tech/ai-sdk-adapter/native parses Ingram Cloud's native SSE envelope into an AI SDK UI message stream. Reach for it only when you need the native extras the standard surface doesn't carry yet — chiefly the live tool.executing / tool.completed progress frames:

import { pipeIngramCloudRun } from "@ingram-tech/ai-sdk-adapter/native";

const result = await pipeIngramCloudRun(icResponse, writer, {
  onToolActivity: ({ tool, phase }) => console.log(tool, phase),
  onApproval: (req) => surface(req),
});
// result.status: "completed" | "paused" | "failed" | "cancelled"

Notes

  • ESM-only, ships as dist/. Build with npm run build (plain tsc).
  • Independent of the API's api/web checks, like the pulumi/ package. Keep it in step when the OpenAI-compatible surface it wraps changes.
  • This is the seed of the official Ingram Cloud JavaScript SDK; the intended long-term home is @ai-sdk/ingram-cloud.