diff --git a/aibridge/internal/integrationtest/bridge_internal_test.go b/aibridge/internal/integrationtest/bridge_internal_test.go index 7a8ae1116f..8c6804c247 100644 --- a/aibridge/internal/integrationtest/bridge_internal_test.go +++ b/aibridge/internal/integrationtest/bridge_internal_test.go @@ -1049,6 +1049,16 @@ func TestSessionIDTracking(t *testing.T) { "User-Agent": []string{"Zed/0.219.4+stable.119.abc123 (macos; aarch64)"}, }, }, + { + name: "opencode", + fixture: fixtures.AntSimple, + expectedClient: aibridge.ClientOpenCode, + expectSessionID: "ses_15a48edefffe7oY0YcIHRv29dD", + header: http.Header{ + "User-Agent": []string{"opencode/1.16.0 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14"}, + "X-OpenCode-Session": []string{"ses_15a48edefffe7oY0YcIHRv29dD"}, + }, + }, } for _, tc := range testCases { diff --git a/aibridge/session.go b/aibridge/session.go index a97fdaef2a..759a2e69f1 100644 --- a/aibridge/session.go +++ b/aibridge/session.go @@ -75,6 +75,8 @@ func GuessSessionID(client Client, r *http.Request) *string { return cleanRef(r.Header.Get("X-KILOCODE-TASKID")) case ClientCoderAgents: return cleanRef(r.Header.Get("X-Coder-Chat-Id")) + case ClientOpenCode: + return cleanRef(r.Header.Get("X-OpenCode-Session")) case ClientCrush: return nil // Crush does not send a session ID header. case ClientRoo: diff --git a/aibridge/session_test.go b/aibridge/session_test.go index 90b27ce705..f1fb7614f4 100644 --- a/aibridge/session_test.go +++ b/aibridge/session_test.go @@ -182,6 +182,23 @@ func TestGuessSessionID(t *testing.T) { name: "coder_agents_without_chat_id", client: aibridge.ClientCoderAgents, }, + // OpenCode. + { + name: "opencode_with_session_header", + client: aibridge.ClientOpenCode, + headers: map[string]string{"X-OpenCode-Session": "ses_15a48edefffe7oY0YcIHRv29dD"}, + sessionID: utils.PtrTo("ses_15a48edefffe7oY0YcIHRv29dD"), + }, + { + name: "opencode_with_whitespace_in_header", + client: aibridge.ClientOpenCode, + headers: map[string]string{"X-OpenCode-Session": " ses_15a48edefffe7oY0YcIHRv29dD "}, + sessionID: utils.PtrTo("ses_15a48edefffe7oY0YcIHRv29dD"), + }, + { + name: "opencode_without_session_header", + client: aibridge.ClientOpenCode, + }, // Crush. { name: "crush_returns_empty",