Skip to content

Commit c8cbb00

Browse files
committed
codex: fix silently dropping GenOptionText.SystemPrompt
Forward system prompts as developer instructions when starting or resuming Codex threads, preserving prompt context across requests.
1 parent 7a34cf5 commit c8cbb00

2 files changed

Lines changed: 47 additions & 7 deletions

File tree

providers/codex/client.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ func (c *Client) GenSync(ctx context.Context, msgs genai.Messages, opts ...genai
325325
if err := c.ensureBin(); err != nil {
326326
return genai.Result{}, err
327327
}
328-
_, optsErr := parseOpts(opts)
328+
co, optsErr := parseOpts(opts)
329329
if optsErr != nil {
330330
if _, ok := errors.AsType[*base.ErrNotSupported](optsErr); !ok {
331331
return genai.Result{}, optsErr
@@ -352,7 +352,7 @@ func (c *Client) GenSync(ctx context.Context, msgs genai.Messages, opts ...genai
352352
}()
353353

354354
sc := newScanner(stdout)
355-
newThreadID, err := handshake(stdin, sc, c.model, threadID)
355+
newThreadID, err := handshake(stdin, sc, c.model, threadID, co.systemPrompt)
356356
if err != nil {
357357
return genai.Result{}, err
358358
}
@@ -369,7 +369,7 @@ func (c *Client) GenStream(ctx context.Context, msgs genai.Messages, opts ...gen
369369
if err := c.ensureBin(); err != nil {
370370
return yieldNothing, errFinish(err)
371371
}
372-
_, optsErr := parseOpts(opts)
372+
co, optsErr := parseOpts(opts)
373373
if optsErr != nil {
374374
if _, ok := errors.AsType[*base.ErrNotSupported](optsErr); !ok {
375375
return yieldNothing, errFinish(optsErr)
@@ -397,7 +397,7 @@ func (c *Client) GenStream(ctx context.Context, msgs genai.Messages, opts ...gen
397397
}()
398398

399399
sc := newScanner(stdout)
400-
newThreadID, hsErr := handshake(stdin, sc, c.model, threadID)
400+
newThreadID, hsErr := handshake(stdin, sc, c.model, threadID, co.systemPrompt)
401401
if hsErr != nil {
402402
finalErr = hsErr
403403
return
@@ -542,15 +542,18 @@ func initAndListModels(stdin io.Writer, sc *bufio.Scanner) ([]ModelInfo, error)
542542

543543
// handshake performs the JSON-RPC initialize → initialized → model/list →
544544
// thread/start (or thread/resume) sequence. Returns the thread ID.
545-
func handshake(stdin io.Writer, sc *bufio.Scanner, mdl, resumeThreadID string) (string, error) {
545+
func handshake(stdin io.Writer, sc *bufio.Scanner, mdl, resumeThreadID, systemPrompt string) (string, error) {
546546
if _, err := initAndListModels(stdin, sc); err != nil {
547547
return "", err
548548
}
549549

550550
// Send thread/start or thread/resume.
551551
var threadReq JSONRPCRequest
552552
if resumeThreadID != "" {
553-
params, err := marshalJSONRaw(ThreadResumeParams{ThreadID: resumeThreadID})
553+
params, err := marshalJSONRaw(ThreadResumeParams{
554+
ThreadID: resumeThreadID,
555+
DeveloperInstructions: systemPrompt,
556+
})
554557
if err != nil {
555558
return "", fmt.Errorf("marshal thread/resume params: %w", err)
556559
}
@@ -561,7 +564,10 @@ func handshake(stdin io.Writer, sc *bufio.Scanner, mdl, resumeThreadID string) (
561564
Params: params,
562565
}
563566
} else {
564-
params, err := marshalJSONRaw(ThreadStartParams{Model: mdl})
567+
params, err := marshalJSONRaw(ThreadStartParams{
568+
Model: mdl,
569+
DeveloperInstructions: systemPrompt,
570+
})
565571
if err != nil {
566572
return "", fmt.Errorf("marshal thread/start params: %w", err)
567573
}

providers/codex/internal_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
package codex
88

99
import (
10+
"bufio"
11+
"bytes"
1012
"encoding/json"
1113
"errors"
1214
"slices"
15+
"strings"
1316
"testing"
1417
"time"
1518

@@ -82,6 +85,37 @@ func TestParseOpts(t *testing.T) {
8285
})
8386
}
8487

88+
func TestHandshake(t *testing.T) {
89+
responses := strings.Join([]string{
90+
`{"id":1,"result":{}}`,
91+
`{"id":2,"result":{"data":[]}}`,
92+
`{"id":3,"result":{"thread":{"id":"thread"}}}`,
93+
}, "\n")
94+
var out bytes.Buffer
95+
threadID, err := handshake(&out, bufio.NewScanner(strings.NewReader(responses)), "model", "", "write commit messages")
96+
if err != nil {
97+
t.Fatal(err)
98+
}
99+
if threadID != "thread" {
100+
t.Errorf("thread ID = %q, want thread", threadID)
101+
}
102+
lines := strings.Split(strings.TrimSpace(out.String()), "\n")
103+
if len(lines) != 4 {
104+
t.Fatalf("wrote %d messages, want 4", len(lines))
105+
}
106+
var req JSONRPCRequest
107+
if err := json.Unmarshal([]byte(lines[3]), &req); err != nil {
108+
t.Fatal(err)
109+
}
110+
var params ThreadStartParams
111+
if err := json.Unmarshal(req.Params, &params); err != nil {
112+
t.Fatal(err)
113+
}
114+
if params.DeveloperInstructions != "write commit messages" {
115+
t.Errorf("developer instructions = %q, want write commit messages", params.DeveloperInstructions)
116+
}
117+
}
118+
85119
func TestJSONRPCMessage(t *testing.T) {
86120
t.Run("notification", func(t *testing.T) {
87121
var m JSONRPCMessage

0 commit comments

Comments
 (0)