#17

Allow routing model calls through any OpenAI-compatible gateway

Openknowhycodatawants to merge intofrom

Closes #16.

Summary

The review model was pinned to anthropic/claude-sonnet-4.6 in lib/agent.ts, which DurableAgent resolves through the Vercel AI Gateway. This adds two optional environment variables so a self-hosted deployment can change the model, or point at a gateway it already uses. With neither set, behaviour is unchanged.

VariableEffect
MODELModel id on either path. Defaults to the current anthropic/claude-sonnet-4.6.
MODEL_BASE_URLRoutes calls through an OpenAI-compatible /chat/completions endpoint instead.
MODEL_API_KEYOptional key for that endpoint.

MODEL_API_KEY is optional so an unauthenticated endpoint — a local vLLM or Ollama server — works by setting MODEL_BASE_URL alone.

Routing lives in a new lib/model.ts; lib/agent.ts changes by one line (model: resolveModel()). Nothing in the workflow or the agent's tools is touched.

Two details worth a look in review

The provider factory has to be a workflow step. DurableAgent accepts string | (() => Promise<CompatibleLanguageModel>) and passes it to doStreamStep as a step argument, which the runtime cannot serialize as a plain closure. Marking it "use step" makes the compiler register it — confirmed in the build output as registerStepFunction("step//./lib/model//customGatewayModel", ...), and the step count goes 25 → 26. The provider instance itself is built inside the streaming step and is never serialized. This mirrors how @workflow/ai's own gateway() / openai() providers are written.

includeUsage: true is required, not cosmetic. Without stream_options.include_usage an OpenAI-compatible endpoint returns no usage on streamed responses, and the 200K token budget in runAgent would silently never trigger.

One unavoidable cast is commented in place: @workflow/ai types its model boundary against the AI SDK v2 language model while @ai-sdk/openai-compatible returns a v3 one. They are structurally compatible at runtime — doStreamStep calls doStream with v2-shaped options, which v3 models accept, as @workflow/ai's own CompatibleLanguageModel type documents — but the tool union does not line up ("provider-defined" vs "provider").

Validation

  • tsc --noEmit clean.
  • next build succeeds; manifest goes from 25 to 26 steps with the new provider step registered.
  • oxlint and oxfmt clean on every file touched. (The pre-existing findings in lib/utils.ts are untouched.)
  • Exercised end to end against a live OpenAI-compatible gateway through the AI SDK: streaming, tool calling across a 2-step agent loop, and usage reporting all confirmed working.

@ai-sdk/openai-compatible is added as a direct dependency. It was already in the lockfile transitively via @ai-sdk/xai, so the install graph does not grow.

Disclosure

I run LLMTR, one gateway this would make reachable, so I have an interest here and would rather say so up front than have it noticed later. That is why this is written as a seam rather than an integration — MODEL_BASE_URL takes any OpenAI-compatible endpoint, the README example lists LLMTR next to OpenRouter, Groq, vLLM and Ollama, and nothing in the code names a provider.

I also know unsolicited vendor-adjacent PRs have landed here before (#13, #14). If this direction is not wanted, or you would rather it were shaped differently, just say so and I will close it.

Activity

2
vercel[bot]2mo ago

@knowhycodata is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.