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-cloud/ai-sdk

v3.0.0

Published

Vercel AI SDK adapter for Ingram Cloud — a pre-configured OpenAI Responses provider plus smith identity, thread memory, and human-in-the-loop approval helpers. Server-side tool calls and approvals arrive as standard AI SDK stream parts; no proprietary pro

Readme

@ingram-cloud/ai-sdk

Drive an Ingram Cloud smith from the Vercel AI SDK: a pre-configured provider plus helpers for what Ingram Cloud adds on top of the AI SDK (smith identity, server-side memory, human-in-the-loop approvals).

The provider is @ai-sdk/openai's Responses model pointed at Ingram Cloud's Responses API. The smith's turn arrives as standard AI SDK parts: text, server-executed tool calls (tool-call/tool-result), and approval pauses (tool-approval-request). Memory is one request header. The agent loop (memory, tools, approvals, isolation) runs server-side; to streamText the smith is a model.

The native run envelope (/v1/smiths/{id}/runs) is available behind the /native subpath. It carries one thing the standard parts do not: the in-flight tool.executing frame when a tool starts.

Install

npm install @ingram-cloud/ai-sdk ai

ai (v7+) is a peer dependency. @ai-sdk/react is needed only for the client helpers.

Quickstart

Server (streamText, generateText, agents)

import { createIngramCloud } from "@ingram-cloud/ai-sdk";
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.6-sol") 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);

With a tenant-admin token, name the smith:

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

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

Server-side tool steps

When the agent's MCP tools run inside a turn, each call reaches the stream as a tool-call part (named mcp.<tool>, marked providerExecuted) followed by a tool-result part, between the text runs:

for await (const part of result.fullStream) {
	if (part.type === "tool-call") showStep(part.toolName, part.input);
	if (part.type === "tool-result") completeStep(part.toolCallId);
	if (part.type === "text-delta") appendText(part.text);
}

In useChat the same parts arrive as tool invocations on the message.

Client (useChat)

Use 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-cloud/ai-sdk/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

A stateless call sends the whole context each turn. With a threadId, Ingram Cloud holds the conversation server-side (the same thread model as a native run) and you send only the new turn; see memory. This holds with client-side tools too: the thread replays the prior turns, tool-call linkage included. Use a cnv_ conversation id as the threadId and the transcript accrues on the conversation.

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

Structured outputs (generateObject)

generateObject sends your schema as a strict text.format. Ingram Cloud returns conforming JSON or an error:

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 call is a stateless one-shot with no tools and no memory. Use a provider without threadId; a threadId provider is rejected with a 400.

Approvals (human-in-the-loop)

A tool the agent marks destructiveHint pauses the run for approval. The pause arrives as a tool-approval-request content part whose approvalId is "<run_id>::<tool_call_id>". Pull the pending approvals off the result and resume by sending a decision:

import {
	createIngramCloud,
	getApprovalRequests,
	approvalResponseMessages,
} from "@ingram-cloud/ai-sdk";
import { generateText } from "ai";

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

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

approvalResponseMessages returns the assistant turn that raised the approval plus the tool-approval-response — the AI SDK rejects a response whose request is not in the same messages (AI_InvalidToolApprovalError). With a threadId those two messages are the whole resume; stateless, put them after ...messages, ...first.response.messages. Keep the IngramApprovalRequest (it's plain JSON) between the pause and the decision.

On approve, Ingram Cloud executes the tool and continues; the executed call arrives as a tool-result part. On reject, the run completes with stop_reason: "approval_rejected" and nothing runs. When calling /v1/responses directly without AI SDK message conversion, use approvalWireItem(id, "approve") to build the raw mcp_approval_response input item.

Tools

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

    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, and its server-side MCP tools sit out that turn. With a threadId the loop is stateful and you send only the new turn.

  • Server-side tools, run by Ingram Cloud over MCP, with approval gating. Register the MCP server once and the smith has it; don't pass tools. Every call is visible on the stream (see Server-side tool steps).

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 chosen by the smith, not by an argument. The model argument is the inference LLM: "" uses the agent's configured model; a model id (e.g. gpt-5.6-sol) overrides it for that call.

Native fallback

@ingram-cloud/ai-sdk/native parses Ingram Cloud's native SSE envelope into an AI SDK UI message stream. Use it for the tool.executing frame; the standard provider covers everything else.

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

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

"unknown" means the stream closed without saying how the run ended: treat the text as partial and read the run record. result.warnings is set when a completed turn answered without a tool source or a skill it needed. The answer is shaped like a healthy one, so the warning is the only signal.

Notes

  • ESM-only, ships as dist/. Build with npm run build (plain tsc).
  • The intended long-term home of this package is @ai-sdk/ingram-cloud.