Skip to content

Commit 86417fe

Browse files
authored
fix: orphan function_call outputs (400) and add robust GitHub fallback (403)
2 parents b02b91d + 1f6eccc commit 86417fe

File tree

3 files changed

+62
-6
lines changed

3 files changed

+62
-6
lines changed

lib/prompts/codex.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { dirname, join } from "node:path";
44
import { fileURLToPath } from "node:url";
55
import type { CacheMetadata, GitHubRelease } from "../types.js";
66

7-
// Codex instructions constants
87
const GITHUB_API_RELEASES =
98
"https://api.github.com/repos/openai/codex/releases/latest";
9+
const GITHUB_HTML_RELEASES =
10+
"https://github.com/openai/codex/releases/latest";
1011
const CACHE_DIR = join(homedir(), ".opencode", "cache");
1112

1213
const __filename = fileURLToPath(import.meta.url);
@@ -61,11 +62,40 @@ export function getModelFamily(normalizedModel: string): ModelFamily {
6162
* @returns Release tag name (e.g., "rust-v0.43.0")
6263
*/
6364
async function getLatestReleaseTag(): Promise<string> {
64-
const response = await fetch(GITHUB_API_RELEASES);
65-
if (!response.ok)
66-
throw new Error(`Failed to fetch latest release: ${response.status}`);
67-
const data = (await response.json()) as GitHubRelease;
68-
return data.tag_name;
65+
try {
66+
const response = await fetch(GITHUB_API_RELEASES);
67+
if (response.ok) {
68+
const data = (await response.json()) as GitHubRelease;
69+
if (data.tag_name) {
70+
return data.tag_name;
71+
}
72+
}
73+
} catch {
74+
}
75+
76+
const htmlResponse = await fetch(GITHUB_HTML_RELEASES);
77+
if (!htmlResponse.ok) {
78+
throw new Error(
79+
`Failed to fetch latest release: ${htmlResponse.status}`,
80+
);
81+
}
82+
83+
const finalUrl = htmlResponse.url;
84+
if (finalUrl) {
85+
const parts = finalUrl.split("/tag/");
86+
const last = parts[parts.length - 1];
87+
if (last && !last.includes("/")) {
88+
return last;
89+
}
90+
}
91+
92+
const html = await htmlResponse.text();
93+
const match = html.match(/\/openai\/codex\/releases\/tag\/([^"]+)/);
94+
if (match && match[1]) {
95+
return match[1];
96+
}
97+
98+
throw new Error("Failed to determine latest release tag from GitHub");
6999
}
70100

71101
/**

lib/request/request-transformer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,14 @@ export async function transformRequestBody(
440440
// DEFAULT MODE: Keep original behavior with tool remap message
441441
body.input = addToolRemapMessage(body.input, !!body.tools);
442442
}
443+
444+
if (!body.tools && body.input) {
445+
body.input = body.input.filter(
446+
(item) =>
447+
item.type !== "function_call" &&
448+
item.type !== "function_call_output",
449+
);
450+
}
443451
}
444452

445453
// Configure reasoning (use normalized model family + model-specific config)

test/request-transformer.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,24 @@ describe('Request Transformer Module', () => {
847847
expect(result.reasoning?.effort).toBe('medium');
848848
});
849849

850+
it('should drop function_call items when no tools present', async () => {
851+
const body: RequestBody = {
852+
model: 'gpt-5-codex',
853+
input: [
854+
{ type: 'message', role: 'user', content: 'hello' },
855+
{ type: 'function_call', role: 'assistant', name: 'write', arguments: '{}' } as any,
856+
{ type: 'function_call_output', role: 'assistant', call_id: 'call_1', output: '{}' } as any,
857+
],
858+
};
859+
860+
const result = await transformRequestBody(body, codexInstructions);
861+
862+
expect(result.tools).toBeUndefined();
863+
expect(result.input).toHaveLength(1);
864+
expect(result.input![0].type).toBe('message');
865+
expect(result.input![0].role).toBe('user');
866+
});
867+
850868
describe('CODEX_MODE parameter', () => {
851869
it('should use bridge message when codexMode=true and tools present (default)', async () => {
852870
const body: RequestBody = {

0 commit comments

Comments
 (0)