Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- [2026-08-21] codex keeps its shell and apply_patch tools on the gpt-5.6 models
- [2026-08-18] meter clients that hang up early
- [2026-08-18] reclaim bare provider tables
- [2026-08-18] settings shows pi
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@
"post-commit": "bun x @rubriclab/package post-commit"
},
"type": "module",
"version": "0.0.65"
"version": "0.0.66"
}
23 changes: 19 additions & 4 deletions src/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,11 @@ describe('chatgpt dialect adapter', () => {
expect(adapted.max_output_tokens).toBeUndefined()
})

test('merges lifted developer messages after existing instructions', () => {
test('merges lifted system messages after existing instructions', () => {
const adapted = JSON.parse(
adaptChatGptRequest(
JSON.stringify({
input: [
{ content: [{ text: 'Prefer short replies.', type: 'input_text' }], role: 'developer' }
],
input: [{ content: [{ text: 'Prefer short replies.', type: 'input_text' }], role: 'system' }],
instructions: 'You are a coding agent.'
})
)
Expand All @@ -59,6 +57,23 @@ describe('chatgpt dialect adapter', () => {
expect(adapted.input).toHaveLength(0)
})

test('keeps developer messages in input', () => {
const adapted = JSON.parse(
adaptChatGptRequest(
JSON.stringify({
input: [
{ content: [{ text: 'You are working inside bb.', type: 'input_text' }], role: 'developer' },
{ content: [{ text: 'Drop this one.', type: 'input_text' }], role: 'system' },
{ content: [{ text: 'hi', type: 'input_text' }], role: 'user' }
],
instructions: 'You are Codex.'
})
)
)
expect(adapted.instructions).toBe('You are Codex.\n\nDrop this one.')
expect(adapted.input.map((item: { role: string }) => item.role)).toEqual(['developer', 'user'])
})

test('leaves codex-shaped requests and non-json bodies alone', () => {
const codexShaped = JSON.stringify({
input: [{ content: [{ text: 'hi', type: 'input_text' }], role: 'user' }],
Expand Down
10 changes: 5 additions & 5 deletions src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,9 @@ function messageText(item: ResponsesInputMessage): string {
// The ChatGPT codex backend rejects requests third-party harnesses send to a
// standard Responses endpoint: system messages must ride in `instructions`
// ("System messages are not allowed") and `max_output_tokens` is unsupported.
// Codex's own requests already have that shape, so this is a no-op for them.
// Developer messages are accepted and must stay in `input`: codex sends its
// tool contract as developer items, and the gpt-5.6 models drop the shell and
// apply_patch tools when those items arrive folded into `instructions`.
export function adaptChatGptRequest(raw: string): string {
let parsed: { input?: unknown; instructions?: unknown; [key: string]: unknown }
try {
Expand All @@ -250,10 +252,8 @@ export function adaptChatGptRequest(raw: string): string {
if (typeof parsed !== 'object' || parsed === null || !Array.isArray(parsed.input)) {
return raw
}
const isSystem = (item: unknown): item is ResponsesInputMessage => {
const role = (item as ResponsesInputMessage | null)?.role
return role === 'system' || role === 'developer'
}
const isSystem = (item: unknown): item is ResponsesInputMessage =>
(item as ResponsesInputMessage | null)?.role === 'system'
const lifted = parsed.input.filter(isSystem).map(messageText)
const instructions = [
...(typeof parsed.instructions === 'string' ? [parsed.instructions] : []),
Expand Down
Loading