selan.ai

Docs · Codex SDK

Codex SDK on Selan

The Codex SDK runs a whole Codex session from your code — it reads the repository, runs commands and edits files. Pointing it at Selan is a base URL and a token. For running Codex at a terminal, start at the Codex page.

Install

npm install @openai/codex-sdk

Two different things share the name Codex. This SDK drives the agent. If you only want one answer from a model — a classification, a summary — you want the Responses format instead, which is the last section and needs no SDK of ours. And Anthropic-shaped code belongs on the Claude Agent SDK page; the gateway serves both formats, so there is nothing to gain by rewriting one into the other.

Point it at Selan

The constructor takes both values. The base URL carries the /openai/v1 namespace, which names the format the SDK speaks:

import { Codex } from "@openai/codex-sdk"

const codex = new Codex({
  baseUrl: "https://gw.selan.ai/openai/v1",
  apiKey: process.env.SELAN_TOKEN,
})

Mint the token in Selan under Settings → CLI tokens. It spends as itself, under an address like nightly-triage@tokens.selan.ai, so what the agent costs shows up as the agent in Usage rather than on whoever set it up.

The base URL has to end at /openai/v1. The SDK appends /responses to whatever you give it. Drop the version segment and every call lands on a path the gateway does not serve.

Your first session

A thread is one session; run is one turn in it. Call it again on the same thread and the agent keeps its context:

const thread = codex.startThread({
  model: "gpt-5.6-sol",
  workingDirectory: "/tmp/scratch",
  sandboxMode: "workspace-write",
  approvalPolicy: "never",
  skipGitRepoCheck: true,
})

const result = await thread.run("Fix the failing test in src/money.test.js")

console.log(result.finalResponse)
for (const item of result.items ?? []) {
  if (item.type === "command_execution") console.log("ran:", item.command)
  if (item.type === "file_change") console.log("changed:", item.changes.map((c) => c.path))
}

finalResponse is what the agent said. items is what it did — a typed record of every command it ran and every file it patched, which is how you check the work rather than trusting the reply.

Choosing what it may do

Two settings decide that, and they interact in a way worth knowing before the first unattended run.

sandboxMode is what the agent may touch. read-only lets it look and nothing else; workspace-write lets it edit inside workingDirectory.

approvalPolicy is what happens when the sandbox refuses something. The default asks a human. With nobody there to answer, never is the setting that works: the request is refused, the agent is told, and it adapts.

Leave the approval policy at its default and edits are reported but never written. The turn comes back with file_change items naming files that do not exist on disk, because the write was waiting on an approval nobody gave. Nothing errors, and the run looks like it worked.

skipGitRepoCheck is for a scratch directory that is not a repository. In a real checkout, leave it off and let the agent see the git history.

Without writing any code

If a shell command is enough, selan codex exec runs the same kind of session with nobody watching:

export SELAN_TOKEN=selanct_...
selan codex exec "update the changelog for the last ten commits"

It is the same launch as an interactive selan codex: same provider block, same company models, same limits. No sign-in step, because the token is in the environment. Setup is on the Codex page.

When you want one answer, not an agent

POST /openai/v1/responses against the gateway. The body is the one you would send to OpenAI directly — the Responses API reference documents every field, and none of them mean anything different here:

curl https://gw.selan.ai/openai/v1/responses \
  -H "Authorization: Bearer $SELAN_TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "model": "gpt-5.6-sol",
    "input": "Summarise this changelog in one sentence."
  }'

The official OpenAI SDKs take the same base URL and your Selan token as the key:

import OpenAI from "openai"

const client = new OpenAI({
  baseURL: "https://gw.selan.ai/openai/v1",
  apiKey: process.env.SELAN_TOKEN,
})

const response = await client.responses.create({
  model: "gpt-5.6-sol",
  input: "Summarise this changelog in one sentence.",
})
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://gw.selan.ai/openai/v1",
    api_key=os.environ["SELAN_TOKEN"],
)

response = client.responses.create(
    model="gpt-5.6-sol",
    input="Summarise this changelog in one sentence.",
)

No provider key anywhere in those. The gateway authenticates you, leases one of your company's connected credentials for the length of the request, streams the answer straight through, and releases it afterwards.

Load the token from wherever you keep secrets rather than committing it. Both examples read it from the environment for that reason.

Naming a model

Ask the gateway which ones this token may name. Under the OpenAI namespace the list comes back in the shape a Responses client expects:

curl https://gw.selan.ai/openai/v1/models \
  -H "Authorization: Bearer $SELAN_TOKEN"

Your company might serve one of these through OpenAI directly or through another connected account, and which credential is spent is decided per request. The id you name is the same either way, so the list is the thing to trust rather than a guess about who is behind it.

What your company sees

The same as every other Selan client. Each request is metered against the token that made it, the company-wide per-user spend limit applies, and it appears in Logs under its own address.

One thread is many requests rather than one, so an agent that fails to terminate keeps spending until something stops it. Set a budget on the token before you leave one running unattended.