Ingram Cloud

Documentation

Flue

Flue

@ingram-cloud/flue drives a smith from a Flue agent — the Flue-ecosystem sibling of the Vercel AI SDK adapter. It is three thin seams, each over a surface you already know: a smith becomes a model provider, Ingram-hosted tools attach over MCP, and approvals ride the standard tool-call channel. No bespoke protocol.

Under the hood the provider stands on the OpenAI-compatible API via Flue's built-in openai-completions wire protocol, so a smith looks like any model to Flue while it runs the agent loop server-side — memory, tools, approvals, isolation.

npm install @ingram-cloud/flue

@flue/runtime (v2) is a peer dependency — you already have it in a Flue app. It must stay a peer: setProvider() writes to a module-scoped registry, so the adapter and your app share one instance.

The provider

Call registerIngramCloud() once in src/app.ts, before routing, like any other Flue provider. The token names the smith (the end-user's instance); the agent is the one that smith runs. Flue 2 resolves model ids against the provider's declared list, so models is required — name every model you plan to use ({} per model is enough).

import { registerIngramCloud } from "@ingram-cloud/flue";
import { createAgentRouter } from "@flue/runtime/routing";
import { Hono } from "hono";
import { Triage } from "./agents/triage.js";

// A per-smith token already names one smith — browser-safe.
export const ingram = registerIngramCloud({
  apiKey: process.env.IC_SMITH_TOKEN!,
  models: { "gpt-5.6-sol": {} },
});

const app = new Hono();
app.route("/agents/triage", createAgentRouter(Triage));
export default app;

Then point an agent at it:

// agents/triage.ts
"use agent";
import { useModel } from "@flue/runtime";
import { ingram } from "../app.js";

export function Triage() {
  useModel(ingram.model("gpt-5.6-sol"));
  return "Triage the incoming request.";
}

The model id is the LLM, not the agent

The agent a smith runs — its instructions, tools, and memory — is resolved from the smith, never from the model id. The model id is the upstream inference LLM for the turn: ingram.model("gpt-5.6-sol") runs the smith's agent on GPT-5.6 Sol.

Unlike the raw OpenAI-compatible surface, where an empty model means "the agent's configured model", Flue requires a non-empty model id in provider/model. So name the model you want; ingram.model("") throws to make that explicit.

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

const ingram = registerIngramCloud({
  apiKey: process.env.IC_TENANT_TOKEN!, // server-side only
  smithId: "smt_…",                      // sent as IC-Smith-Id
  models: { "gpt-5.6-sol": {} },
});

Tools over MCP

Expose an Ingram Cloud deployment of kind: "mcp" as Flue tools. defineIngramMcp() builds the connection definition; mount it in an agent render with useMcpConnection(). Ingram runs the tools for you, with approval gating.

import { defineIngramMcp } from "@ingram-cloud/flue";
import { useMcpConnection, useModel } from "@flue/runtime";
import { ingram } from "../app.js";

const ingramMcp = defineIngramMcp({
  apiKey: process.env.IC_SMITH_TOKEN!,
  deploymentId: "dep_…",
});

export function Assistant() {
  useModel(ingram.model("gpt-5.6-sol"));
  useMcpConnection(ingramMcp); // tools named mcp__ingram__<tool>
  return instructions;
}

The tools arrive as ordinary Flue tools, so they compose with native tools, skills, and the sandbox. Outside an agent render (trusted application code, Node target only), connectIngramMcp(settings) is the imperative form — it connects now and returns { tools, close }.

Approvals

A tool the agent marks destructiveHint pauses the run for a human decision. On this surface the pause is a tool call whose id is "<run_id>::<tool_call_id>" and the turn ends with finish_reason: "tool_calls". The helpers are pure and also live at @ingram-cloud/flue/approvals:

import { getApprovalRequests, approvalWireMessage } from "@ingram-cloud/flue";

// `toolCalls` from the model response — composite ids are Ingram approvals.
const approvals = getApprovalRequests(toolCalls);
for (const a of approvals) {
  const decision = (await askTheHuman(a)) ? "approve" : "reject";
  // Send this tool message back as the next turn to resume the paused run:
  sendNextTurn(approvalWireMessage(a, decision));
}

On approve, Ingram Cloud runs the tool itself and continues; on reject, the run completes with stop_reason: "approval_rejected" and nothing executes. This is the same approval a /submit or a deployment confirmation drives — one mechanism, several front doors.

Identity & tokens

TokenUseHow the smith is chosen
Smith token (sub = "<tenant>:<smith>")browser-safe; the defaultthe token is the smith
Tenant-admin tokenserver-side onlypass smithId (sent as IC-Smith-Id)

The agent is the one the smith runs — chosen by the smith, never by an argument.

When you need more