Contextwaise

Contextwaise is in early access: API keys and the dashboard open at launch. Join the waitlist to get yours first.

Docs · OpenAI SDK›
Agent SDK

OpenAI SDK

You run the agent loop with the OpenAI SDK; one call turns the model's function calls into the outputs to send back, each one shrunk the moment the tool returns. Both the Responses API and Chat Completions.

Responses API#

Add the expand tool to your tools, and replace the code that runs function calls and builds function_call_output items with one call:

import OpenAI from "openai";
import { createContextwaise } from "contextwaise";
import { expandToolResponses, functionOutputs } from "contextwaise/openai";

const client = new OpenAI();
// Reads CONTEXTWAISE_API_KEY and CONTEXTWAISE_URL from the environment.
const cw = createContextwaise({ agent: "support-triage" });

const input: OpenAI.Responses.ResponseInputItem[] = [{ role: "user", content: question }];
while (true) {
  const response = await client.responses.create({
    model,
    tools: [...myTools, expandToolResponses],
    input,
  });
  input.push(...(response.output as OpenAI.Responses.ResponseInputItem[]));
  if (!response.output.some((item) => item.type === "function_call")) break;

  // Runs your tools, shrinks their outputs, answers expand.
  input.push(...(await functionOutputs(cw, response, runTool, { request: question })));
}

Chat Completions#

The same, with tool messages:

import { expandTool, toolMessages } from "contextwaise/openai";

const messages: OpenAI.ChatCompletionMessageParam[] = [{ role: "user", content: question }];
while (true) {
  const completion = await client.chat.completions.create({
    model,
    tools: [...myTools, expandTool],
    messages,
  });
  const message = completion.choices[0].message;
  messages.push(message);
  if (!message.tool_calls?.length) break;

  // A message doesn't carry usage: pass it so the dashboard can count what each expand cost.
  messages.push(...(await toolMessages(cw, message, runTool, { request: question, promptTokens: completion.usage?.prompt_tokens })));
}

Your tools stay as they are: runTool(name, args) returns a tool's raw output, text or any JSON value. The arguments arrive parsed.

What the call does#

For every function call in the response, in order:

  • Runs your tool and keeps the raw output on your side.
  • Shrinks the output for the model: noise removed, the relevant parts kept within the tool's budget. Small results, and results where a cut would save too little (under 1,000 tokens or 30%), pass whole.
  • Answers expand itself, from the original output. It never reaches your tools.
  • Reports failures as Error: … text: a tool that throws, or arguments that aren't valid JSON.

Pass request, the user's request for the turn: with the text the model wrote before its calls, it tells relevance what the agent is after.

Good to know#

  • The Responses API's expand tool is not strict, because its question is optional. Use expandToolResponses there, and expandTool for Chat Completions.
  • Several function calls in one response run in parallel.
  • Some models accept function tools on Chat Completions only with reasoning off: if the API refuses your tools, set reasoning_effort to "none" or use the Responses API.
Note
The adapter reads the SDK's response objects (or plain objects); the OpenAI SDK isn't a dependency of contextwaise. All options are on Options; for other loops, see the Core API.