Skip to content
Preview: outbound-sdk is not on npm yet and its repository is private. Instantly and HeyReach are beta, Smartlead is unverified.What is verified
outbound-sdk
Esc
↑↓navigate↵open⌘Jpreview
On this page

OpenAI SDK tools

The outbound tools as OpenAI function definitions for the Responses and Chat Completions APIs, with one function that runs a call from the model and returns the string to send back.

openaiTools(outbound) from outbound-sdk/tools returns the tools in the shape the OpenAI SDK takes. It is built without importing openai, so it adds no dependency, and it works with any client that accepts the same function-tool shape.

It returns three things:

Property What it is
tools Function tools for the Responses API: type, name, description, parameters, strict. Pass as tools to client.responses.create.
chatTools The same tools for Chat Completions, each nested under function. Pass as tools to client.chat.completions.create.
handle(name, argumentsJson) Runs one function call. Takes the call’s name and its arguments string, returns the result as a JSON string.

openaiTools takes the same only and extra options as createTools.

Build the tools and run a call

import { createOutbound } from "outbound-sdk";
import { demoData, memory } from "outbound-sdk/memory";
import { openaiTools } from "outbound-sdk/tools";

const outbound = createOutbound({
  adapter: memory({ data: demoData(), workspaceId: "acme" }),
});

const { chatTools, handle, tools } = openaiTools(outbound);

// `tools` is the Responses API shape, `chatTools` the Chat Completions shape.
const [first] = tools;

console.log(tools.length, "tools");

console.log({
  description: `${first?.description.slice(0, 40)}...`,
  name: first?.name,
  parameters: "(JSON Schema)",
  strict: first?.strict,
  type: first?.type,
});

console.log(Object.keys(chatTools[0] ?? {}), chatTools[0]?.function.name);

// `handle` takes the call's name and its `arguments` string, and returns the
// string to send back as the call's output.
console.log(await handle("campaign_overview", '{"status":"paused","limit":1}'));

// It never throws. A tool the set does not have and arguments that are not
// JSON are results too.
console.log(await handle("drop_database", "{}"));

console.log(await handle("sender_health", "{not json"));
9 tools
{
  description: 'How every campaign is doing right now: s...',
  name: 'campaign_overview',
  parameters: '(JSON Schema)',
  strict: false,
  type: 'function'
}
[ 'function', 'type' ] campaign_overview
{"data":{"campaigns":[{"campaign":"memory:acme:cmp_agencies","channel":"email","name":"Agency owners","metrics":{"bounce_rate":0.0667,"bounced":4,"connections_accepted":null,"connections_sent":null,"contacted":60,"opened":18,"positive":0,"positive_reply_rate":0,"replied":1,"reply_rate":0.0167,"reply_rate_basis":"contacted","sent":60},"status":"paused"}]},"meta":{"evidence":"exact","provider":"memory","requests":1,"today":"2026-11-14"},"next":"Use campaign_trend for one campaign over time, or winning_steps to see which step drives replies.","ok":true}
{"error":{"code":"Invalid","message":"Unknown tool drop_database"},"next":"Call one of the listed tools with a JSON object.","ok":false}
{"error":{"code":"Invalid","message":"The arguments are not valid JSON"},"next":"Call one of the listed tools with a JSON object.","ok":false}
  • strict is false. The schemas have optional fields, which OpenAI’s strict mode does not accept. The tool validates the arguments itself and reports anything wrong as a result.
  • An empty arguments string means no arguments. handle reads it as {}.
  • handle never throws. An unknown tool name and arguments that are not JSON come back as { ok: false } with the code Invalid.

Answer the model’s calls

When a response contains function_call items, run each through handle and send the outputs back in the next request.

import { createOutbound } from "outbound-sdk";
import { demoData, memory } from "outbound-sdk/memory";
import { openaiTools } from "outbound-sdk/tools";

const outbound = createOutbound({
  adapter: memory({ data: demoData(), workspaceId: "acme" }),
});

const { handle } = openaiTools(outbound);

/** A `function_call` item from a Responses API response. */
interface FunctionCall {
  readonly type: "function_call";
  readonly call_id: string;
  readonly name: string;
  readonly arguments: string;
}

/** Runs each call and builds the `function_call_output` items to send back. */
const answer = (calls: readonly FunctionCall[]) =>
  Promise.all(
    calls.map(async (call) => ({
      call_id: call.call_id,
      output: await handle(call.name, call.arguments),
      type: "function_call_output" as const,
    }))
  );

// Written by hand in the shape a model emits. No model was called.
const outputs = await answer([
  {
    arguments: '{"limit":1}',
    call_id: "call_1",
    name: "sender_health",
    type: "function_call",
  },
]);

console.log(JSON.stringify(outputs, null, 2));
[
  {
    "call_id": "call_1",
    "output": "{\"data\":{\"senders\":[{\"daily_limit\":40,\"kind\":\"mailbox\",\"sender\":\"memory:acme:mbx_1\",\"status\":\"active\",\"warmup_enabled\":true,\"warmup_score\":96}],\"unhealthy\":0},\"meta\":{\"evidence\":\"exact\",\"provider\":\"memory\",\"requests\":0,\"today\":\"2026-11-14\"},\"next\":\"A sender address is not included. Look a sender up by its ref if you need more.\",\"ok\":true}",
    "type": "function_call_output"
  }
]

Wiring it to a model

The loop around these pieces is yours:

  1. Call client.responses.create with your model, your input and tools.
  2. For each function_call item in the output, call handle(item.name, item.arguments).
  3. Append a function_call_output item with the same call_id and the returned string, and call the API again.
  4. Stop when a response has no function calls.

With Chat Completions, pass chatTools, read message.tool_calls, and answer each with a tool message whose content is the string handle returned.

Reply text

A model given reply_samples can ask for reply text. It comes back truncated and marked as untrusted. See untrusted content.

Was this page helpful?