authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-07-17 14:15:53-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-17 23:15:53+02:00
logcf58cadd027f013b5b0f95693975d226d9fd178f
tree60c8aef84ec5ee1c56332268c5f61fcef913c559
parent6a17629670f31a00719fb92c7c9413661cd6def5
signature Signed by PGP key B5690EEEBB952194

feat(server): include runtime model and effort in Codex developer instructions (#3948)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julius Marminge <julius0216@outlook.com>

3 files changed, 105 insertions(+), 12 deletions(-)

apps/server/src/provider/CodexDeveloperInstructions.ts+25
...@@ -1,3 +1,5 @@...@@ -1,3 +1,5 @@
1import type { ProviderInteractionMode } from "@t3tools/contracts";
2
1const T3_CODE_BROWSER_TOOL_INSTRUCTIONS = `3const T3_CODE_BROWSER_TOOL_INSTRUCTIONS = `
24
3## T3 Code collaborative browser5## T3 Code collaborative browser
...@@ -145,3 +147,26 @@ The \`request_user_input\` tool is unavailable in Default mode. If you call it w...@@ -145,3 +147,26 @@ The \`request_user_input\` tool is unavailable in Default mode. If you call it w
145In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message.147In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message.
146${T3_CODE_BROWSER_TOOL_INSTRUCTIONS}148${T3_CODE_BROWSER_TOOL_INSTRUCTIONS}
147</collaboration_mode>`;149</collaboration_mode>`;
150
151export interface CodexRuntimeInfo {
152 readonly model: string;
153 readonly reasoningEffort: string;
154}
155
156// Values come from trusted config, but keep the block single-line regardless.
157function toSingleLine(value: string): string {
158 return value.replaceAll(/\s+/g, " ").trim();
159}
160
161export function buildCodexDeveloperInstructions(
162 interactionMode: ProviderInteractionMode,
163 runtime: CodexRuntimeInfo,
164): string {
165 const base =
166 interactionMode === "plan"
167 ? CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS
168 : CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS;
169 return `${base}
170
171<runtime_info>In case you're asked: you are running in T3 Code through the Codex harness, as ${toSingleLine(runtime.model)} with ${toSingleLine(runtime.reasoningEffort)} reasoning effort. No need to mention this otherwise.</runtime_info>`;
172}
apps/server/src/provider/Layers/CodexSessionRuntime.test.ts+73-3
...@@ -4,11 +4,12 @@ import { it } from "@effect/vitest";...@@ -4,11 +4,12 @@ import { it } from "@effect/vitest";
4import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
5import * as Schema from "effect/Schema";5import * as Schema from "effect/Schema";
6import { describe } from "vite-plus/test";6import { describe } from "vite-plus/test";
7import { ThreadId } from "@t3tools/contracts";7import { DEFAULT_MODEL, ThreadId } from "@t3tools/contracts";
8import * as CodexErrors from "effect-codex-app-server/errors";8import * as CodexErrors from "effect-codex-app-server/errors";
9import * as CodexRpc from "effect-codex-app-server/rpc";9import * as CodexRpc from "effect-codex-app-server/rpc";
1010
11import {11import {
12 buildCodexDeveloperInstructions,
12 CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,13 CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
13 CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,14 CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,
14} from "../CodexDeveloperInstructions.ts";15} from "../CodexDeveloperInstructions.ts";
...@@ -118,7 +119,10 @@ describe("buildTurnStartParams", () => {...@@ -118,7 +119,10 @@ describe("buildTurnStartParams", () => {
118 settings: {119 settings: {
119 model: "gpt-5.3-codex",120 model: "gpt-5.3-codex",
120 reasoning_effort: "medium",121 reasoning_effort: "medium",
121 developer_instructions: CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,122 developer_instructions: buildCodexDeveloperInstructions("plan", {
123 model: "gpt-5.3-codex",
124 reasoningEffort: "medium",
125 }),
122 },126 },
123 },127 },
124 });128 });
...@@ -163,12 +167,31 @@ describe("buildTurnStartParams", () => {...@@ -163,12 +167,31 @@ describe("buildTurnStartParams", () => {
163 settings: {167 settings: {
164 model: "gpt-5.3-codex",168 model: "gpt-5.3-codex",
165 reasoning_effort: "medium",169 reasoning_effort: "medium",
166 developer_instructions: CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,170 developer_instructions: buildCodexDeveloperInstructions("default", {
171 model: "gpt-5.3-codex",
172 reasoningEffort: "medium",
173 }),
167 },174 },
168 },175 },
169 });176 });
170 });177 });
171178
179 it("reports the same fallback model and effort in settings and instructions", () => {
180 const params = Effect.runSync(
181 buildTurnStartParams({
182 threadId: "provider-thread-1",
183 runtimeMode: "full-access",
184 prompt: "Go",
185 interactionMode: "default",
186 }),
187 );
188
189 const settings = params.collaborationMode?.settings;
190 NodeAssert.equal(settings?.model, DEFAULT_MODEL);
191 NodeAssert.equal(settings?.reasoning_effort, "medium");
192 NodeAssert.ok(settings?.developer_instructions?.includes(`as ${DEFAULT_MODEL} with medium`));
193 });
194
172 it("omits collaboration mode when interaction mode is absent", () => {195 it("omits collaboration mode when interaction mode is absent", () => {
173 const params = Effect.runSync(196 const params = Effect.runSync(
174 buildTurnStartParams({197 buildTurnStartParams({
...@@ -194,6 +217,53 @@ describe("buildTurnStartParams", () => {...@@ -194,6 +217,53 @@ describe("buildTurnStartParams", () => {
194 });217 });
195});218});
196219
220describe("buildCodexDeveloperInstructions", () => {
221 it("appends runtime info after the mode instructions", () => {
222 const instructions = buildCodexDeveloperInstructions("default", {
223 model: "gpt-5.3-codex",
224 reasoningEffort: "high",
225 });
226
227 NodeAssert.ok(instructions.startsWith(CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS));
228 NodeAssert.match(instructions, /T3 Code/);
229 NodeAssert.match(instructions, /Codex harness/);
230 NodeAssert.match(instructions, /as gpt-5\.3-codex with high reasoning effort/);
231 });
232
233 it("includes runtime info alongside plan mode instructions", () => {
234 const instructions = buildCodexDeveloperInstructions("plan", {
235 model: "gpt-5.3-codex",
236 reasoningEffort: "medium",
237 });
238
239 NodeAssert.ok(instructions.startsWith(CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS));
240 NodeAssert.match(instructions, /as gpt-5\.3-codex with medium reasoning effort/);
241 });
242
243 it("varies with the model and effort of each turn", () => {
244 const first = buildCodexDeveloperInstructions("default", {
245 model: "gpt-5.3-codex",
246 reasoningEffort: "medium",
247 });
248 const second = buildCodexDeveloperInstructions("default", {
249 model: "gpt-5.4",
250 reasoningEffort: "high",
251 });
252
253 NodeAssert.notEqual(first, second);
254 });
255
256 it("flattens multiline metadata into single-line runtime info", () => {
257 const instructions = buildCodexDeveloperInstructions("default", {
258 model: "gpt\n5.3\ncodex",
259 reasoningEffort: " high\neffort ",
260 });
261
262 NodeAssert.match(instructions, /as gpt 5\.3 codex with high effort reasoning effort/);
263 NodeAssert.doesNotMatch(instructions, /<runtime_info>[^<]*\n/);
264 });
265});
266
197describe("T3 browser developer instructions", () => {267describe("T3 browser developer instructions", () => {
198 it("prefers the product-native preview tools in both collaboration modes", () => {268 it("prefers the product-native preview tools in both collaboration modes", () => {
199 for (const instructions of [269 for (const instructions of [
apps/server/src/provider/Layers/CodexSessionRuntime.ts+7-9
...@@ -37,10 +37,7 @@ import * as EffectCodexSchema from "effect-codex-app-server/schema";...@@ -37,10 +37,7 @@ import * as EffectCodexSchema from "effect-codex-app-server/schema";
3737
38import { buildCodexInitializeParams } from "./CodexProvider.ts";38import { buildCodexInitializeParams } from "./CodexProvider.ts";
39import { expandHomePath } from "../../pathExpansion.ts";39import { expandHomePath } from "../../pathExpansion.ts";
40import {40import { buildCodexDeveloperInstructions } from "../CodexDeveloperInstructions.ts";
41 CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
42 CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,
43} from "../CodexDeveloperInstructions.ts";
44const decodeV2TurnStartResponse = Schema.decodeUnknownEffect(EffectCodexSchema.V2TurnStartResponse);41const decodeV2TurnStartResponse = Schema.decodeUnknownEffect(EffectCodexSchema.V2TurnStartResponse);
4542
46const PROVIDER = ProviderDriverKind.make("codex");43const PROVIDER = ProviderDriverKind.make("codex");
...@@ -331,15 +328,16 @@ function buildCodexCollaborationMode(input: {...@@ -331,15 +328,16 @@ function buildCodexCollaborationMode(input: {
331 return undefined;328 return undefined;
332 }329 }
333 const model = normalizeCodexModelSlug(input.model) ?? DEFAULT_MODEL;330 const model = normalizeCodexModelSlug(input.model) ?? DEFAULT_MODEL;
331 const reasoningEffort = input.effort ?? "medium";
334 return {332 return {
335 mode: input.interactionMode,333 mode: input.interactionMode,
336 settings: {334 settings: {
337 model,335 model,
338 reasoning_effort: input.effort ?? "medium",336 reasoning_effort: reasoningEffort,
339 developer_instructions:337 developer_instructions: buildCodexDeveloperInstructions(input.interactionMode, {
340 input.interactionMode === "plan"338 model,
341 ? CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS339 reasoningEffort,
342 : CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,340 }),
343 },341 },
344 };342 };
345}343}