Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ OPENAI_API_KEY=
#
# OPENAI_BASE_URL=https://gateway.internal/v1
# OPENAI_API_KEY=...
# BOT_MODEL=openai/gpt-4o
# BOT_MODEL=openai/gpt-5.6-terra
#
# OPENAI_BASE_URL=

Expand All @@ -141,11 +141,12 @@ OPENAI_API_KEY=
# ANTHROPIC_API_KEY=
# GOOGLE_API_KEY=

# Which model. Defaults per provider: gpt-5.5, claude-sonnet-4-5, gemini-2.5-flash.
# BOT_MODEL=gpt-5.5
# Which model. Defaults per provider: gpt-5.6-terra, claude-sonnet-4-5, gemini-2.5-flash.
# OpenAI's 5.6 tiers are sol (most capable), terra (the default here) and luna (cheapest).
# BOT_MODEL=gpt-5.6-terra

# OpenAI only. Its newer models require the Responses API, which the framework Bot handles and the
# proof-of-concept one cannot.
# OpenAI only, and rarely needed: the framework Bot turns the Responses API on by itself for models
# that require it. Set it when you are using a model this build has not heard of that needs it too.
# BOT_RESPONSES_API=false

# The Bot computer. Absent means the feature is off and its routes are not mounted.
Expand Down Expand Up @@ -237,8 +238,10 @@ MANAGED_AGENT_TOKEN=
# proof of concept, and is reached the same way: point MANAGED_AGENT_AG_UI_URL at it, or add it as a
# Bot of its own in the tenant package or at /agents.

# Which model the Bots use. agent-bot speaks /v1/chat/completions and cannot use gpt-5.6-*, which
# require the Responses API. agent-langgraph can: set BOT_RESPONSES_API=true and give it a 5.6 model.
# Which model the Bots use. agent-langgraph runs gpt-5.6-terra and switches to the Responses API by
# itself, because 5.6 rejects function tools on /v1/chat/completions. agent-bot speaks that endpoint
# by hand and stays on gpt-5.5: the alternative there is reasoning_effort 'none', and a Bot that has
# to decide when to ask a person for help should not be the one with its reasoning turned off.
# BOT_RESPONSES_API=false

# One computer per Bot. Unset, every Bot shares the computer at AGENT_COMPUTER_URL, suitable on a
Expand Down
22 changes: 16 additions & 6 deletions agent-langgraph/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ import { toLangChainMessages } from "./history";
* server changes.
*
* The model API stops being ours. `agent-bot` speaks `/v1/chat/completions` by hand, which is why
* gpt-5.6 cannot be used there without rewriting its streaming loop: those models reject function
* tools on that endpoint and require the Responses API. Here it is `useResponsesApi`, one line,
* because the migration belongs to the people who maintain the integration.
* gpt-5.6 costs it a rewritten streaming loop: those models reject function tools on that endpoint
* unless reasoning is turned off, and turning reasoning off on a Bot that has to decide when to ask
* a person for help is the wrong trade. Here it is `useResponsesApi`, one line, because the
* migration belongs to the people who maintain the integration.
*
* The tool loop still runs on the client, exactly as it does in `agent-bot`: a Bot's actions happen
* on a browser the person is watching, and the surface remains the place that executes those tools.
Expand Down Expand Up @@ -66,8 +67,17 @@ const PROVIDER = (process.env.BOT_PROVIDER ?? "openai").toLowerCase();
* missing configuration.
*/
const MODEL = process.env.BOT_MODEL?.trim() || defaultModelFor(PROVIDER);
/** OpenAI only. Its newer models require the Responses API, which the integration handles. */
const USE_RESPONSES_API = process.env.BOT_RESPONSES_API === "true";
/**
* OpenAI only. Its newer models require the Responses API, which the integration handles.
*
* Inferred from the model rather than left to a separate switch. `gpt-5.6-*` rejects function tools
* on `/v1/chat/completions`, so a deployment that set `BOT_MODEL` to one and did not also know about
* this flag got a Bot that started, looked healthy, and failed on its first tool call. The switch is
* still honoured, so a model this list has not heard of can be told to use it.
*/
const NEEDS_RESPONSES_API = /^gpt-5\.[6-9]|^gpt-[6-9]/.test(MODEL);
const USE_RESPONSES_API =
process.env.BOT_RESPONSES_API === "true" || NEEDS_RESPONSES_API;
/**
* OpenAI only, and the same variable the API server reads for its built-in agents.
*
Expand All @@ -90,7 +100,7 @@ const GOOGLE_BASE_URL =
function defaultModelFor(provider: string): string {
if (provider === "anthropic") return "claude-sonnet-4-5";
if (provider === "google") return "gemini-2.5-flash";
return "gpt-5.5";
return "gpt-5.6-terra";
}

/**
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ services:
# Unset means OpenAI. Set, it is any endpoint speaking the same API, and BOT_MODEL is sent
# to it verbatim.
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-}
BOT_MODEL: ${BOT_MODEL:-gpt-5.5}
BOT_MODEL: ${BOT_MODEL:-gpt-5.6-terra}
healthcheck:
test: ["CMD-SHELL", "bun -e \"await fetch('http://localhost:4200/health')\""]
interval: 10s
Expand All @@ -204,7 +204,7 @@ services:
ANTHROPIC_BASE_URL: ${ANTHROPIC_BASE_URL:-}
GOOGLE_API_KEY: ${GOOGLE_API_KEY:-}
GOOGLE_GENERATIVE_AI_BASE_URL: ${GOOGLE_GENERATIVE_AI_BASE_URL:-}
BOT_MODEL: ${BOT_MODEL:-gpt-5.5}
BOT_MODEL: ${BOT_MODEL:-gpt-5.6-terra}
BOT_RESPONSES_API: ${BOT_RESPONSES_API:-false}
# Where this Bot runs a tool: back through the deployment that granted it, never at the vendor.
# `host.docker.internal` because the API server runs on the host, not in this network.
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ A gateway that fronts several providers behind one key is addressed the usual wa
```sh
OPENAI_BASE_URL=https://gateway.internal/v1
OPENAI_API_KEY=...
BOT_MODEL=openai/gpt-4o
BOT_MODEL=openai/gpt-5.6-terra
```

and in the tenant package, where the name is namespaced the same way:
Expand All @@ -102,7 +102,7 @@ and in the tenant package, where the name is namespaced the same way:
model:
provider: openai
credential_secret_ref: openai-api-key
default_model: openai/gpt-4o
default_model: openai/gpt-5.6-terra
```

Most gateways publish a model list, which is the way to check a name before configuring it.
Expand Down Expand Up @@ -366,7 +366,7 @@ column nor `users.groups` is the wrong shape for it.
model:
provider: openai
credential_secret_ref: openai-api-key
default_model: gpt-4.1
default_model: gpt-5.6-terra
```

`provider` must be `openai`. `credential_secret_ref` is a reference to a stored credential, not a credential value. `default_model` is passed through as written, so an OpenAI-compatible endpoint reached through `OPENAI_BASE_URL` takes the name that endpoint publishes.
Expand Down
2 changes: 1 addition & 1 deletion examples/fintech/model.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
model:
provider: openai
credential_secret_ref: openai-api-key
default_model: gpt-4.1
default_model: gpt-5.6-terra
16 changes: 14 additions & 2 deletions examples/langgraph-bot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ import { serve } from "bun";
*/

const PORT = Number.parseInt(process.env.PORT ?? "4300", 10);
const MODEL = process.env.BOT_MODEL ?? "gpt-5.5";
const MODEL = process.env.BOT_MODEL ?? "gpt-5.6-terra";
/**
* `gpt-5.6-*` rejects function tools on `/v1/chat/completions` and needs the Responses API, which
* this integration speaks. Inferred from the model so setting `BOT_MODEL` alone cannot produce a
* Bot that starts, looks healthy, and fails on its first tool call.
*/
const USE_RESPONSES_API =
process.env.BOT_RESPONSES_API === "true" ||
/^gpt-5\.[6-9]|^gpt-[6-9]/.test(MODEL);

/**
* The graph's state.
Expand Down Expand Up @@ -131,7 +139,11 @@ function buildGraph(input: RunAgentInput) {
// the graph returns once it is finished, and the surface receives a single TEXT_MESSAGE_CONTENT
// carrying all of it, so a six-hundred-word reply is a blank conversation for eight seconds and
// then a wall of text. The tokens exist the whole time; nothing was passing them on.
const model = new ChatOpenAI({ model: MODEL, streaming: true });
const model = new ChatOpenAI({
model: MODEL,
streaming: true,
...(USE_RESPONSES_API ? { useResponsesApi: true } : {}),
});
const tools = toLangChainTools(input);
const bound = tools.length > 0 ? model.bindTools(tools) : model;

Expand Down
12 changes: 10 additions & 2 deletions examples/mastra-bot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ import { serve } from "bun";
*/

const PORT = Number.parseInt(process.env.PORT ?? "4400", 10);
const MODEL = process.env.BOT_MODEL ?? "gpt-5.5";
const MODEL = process.env.BOT_MODEL ?? "gpt-5.6-terra";
/**
* `gpt-5.6-*` rejects function tools on `/v1/chat/completions` and needs the Responses API, which
* this provider exposes as `openai.responses`. Inferred from the model so setting `BOT_MODEL` alone
* cannot produce a Bot that starts, looks healthy, and fails on its first tool call.
*/
const NEEDS_RESPONSES_API =
process.env.BOT_RESPONSES_API === "true" ||
/^gpt-5\.[6-9]|^gpt-[6-9]/.test(MODEL);

const bot = new Agent({
name: "OpenBot Mastra coworker",
Expand All @@ -28,7 +36,7 @@ const bot = new Agent({
"NEVER state what a page contains unless you have just read it with a tool in this conversation. " +
"You cannot know a page's contents from memory. If you have not read it, call the tool first, " +
"and report exactly what the tool returned.",
model: openai(MODEL),
model: NEEDS_RESPONSES_API ? openai.responses(MODEL) : openai(MODEL),
});

/**
Expand Down
35 changes: 19 additions & 16 deletions server/tests/copilot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@ describe("registered Copilot agents", () => {
type: "built_in",
systemPrompt: "Be helpful.",
},
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
"openai-secret",
),
).toEqual({
model: "openai/gpt-4.1",
model: "openai/gpt-5.6-terra",
// The provenance rule is unconditional, so even a Bot with no tools and no computer carries
// it. That Bot needs it most: nothing it says was read anywhere.
prompt: `Be helpful.\n\n${PROVENANCE_GUIDANCE}`,
Expand All @@ -117,7 +117,7 @@ describe("registered Copilot agents", () => {
systemPrompt: "Be helpful.",
},
],
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
null,
);
const agent = agents["general-assistant"];
Expand Down Expand Up @@ -159,7 +159,7 @@ describe("registered Copilot agents", () => {
endpoint: "http://risk.internal/ag-ui",
},
],
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
"openai-secret",
);

Expand Down Expand Up @@ -200,7 +200,7 @@ describe("registered Copilot agents", () => {
endpoint: "http://risk.internal/ag-ui",
},
],
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
"openai-secret",
stallGuard,
);
Expand All @@ -227,7 +227,10 @@ describe("registered Copilot agents", () => {
endpoint: "http://risk.internal/ag-ui",
},
];
const model = { provider: "openai" as const, defaultModel: "gpt-4.1" };
const model = {
provider: "openai" as const,
defaultModel: "gpt-5.6-terra",
};

const guarded = (
await buildAgents(registered, model, null, {
Expand Down Expand Up @@ -261,12 +264,12 @@ describe("registered Copilot agents", () => {

const first = await resolveRuntimeAgents(
async () => registered,
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
resolveModelApiKey,
);
const second = await resolveRuntimeAgents(
async () => registered,
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
resolveModelApiKey,
);

Expand All @@ -293,7 +296,7 @@ describe("registered Copilot agents", () => {
endpoint: "http://risk.internal/ag-ui",
},
],
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
async () => {
resolverInvoked = true;
throw new Error("corrupt model credential");
Expand Down Expand Up @@ -339,7 +342,7 @@ describe("standing agent roles", () => {
await using endpoint = fakeAgUiEndpoint();
const agents = await buildAgents(
[remoteAgent(endpoint.url)],
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
null,
);

Expand All @@ -363,7 +366,7 @@ describe("standing agent roles", () => {
await using endpoint = fakeAgUiEndpoint();
const agents = await buildAgents(
[remoteAgent(endpoint.url)],
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
null,
);

Expand All @@ -389,7 +392,7 @@ describe("standing agent roles", () => {
reason: "Expense Manager has been deleted.",
},
],
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
null,
);

Expand Down Expand Up @@ -417,7 +420,7 @@ describe("standing agent roles", () => {
seen.actors.push(actor);
return [remoteAgent("http://coworker.internal/ag-ui")];
},
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
async () => null,
);

Expand All @@ -436,7 +439,7 @@ describe("standing agent roles", () => {
async () => [
remoteAgent("http://coworker.internal/ag-ui", { roleDescription }),
],
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
async () => null,
);
const request = new Request("http://openbot.test/api/copilotkit");
Expand Down Expand Up @@ -581,7 +584,7 @@ describe("what a Bot is told it holds", () => {
type: "built_in",
systemPrompt: "Investigate policies.",
},
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
"openai-secret",
drive,
"BROWSER GUIDANCE HERE",
Expand Down Expand Up @@ -621,7 +624,7 @@ describe("where a Bot says its answer came from", () => {
type: "built_in",
systemPrompt: "Be helpful.",
},
{ provider: "openai", defaultModel: "gpt-4.1" },
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
"openai-secret",
).prompt as string;

Expand Down
Loading