Skip to content
Merged
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
6 changes: 6 additions & 0 deletions intercept/responses/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ type responsesInterceptionBase struct {
func (i *responsesInterceptionBase) newResponsesService() responses.ResponseService {
opts := []option.RequestOption{option.WithBaseURL(i.cfg.BaseURL), option.WithAPIKey(i.cfg.Key)}

// Add extra headers if configured.
// Some providers require additional headers that are not added by the SDK.
for key, value := range i.cfg.ExtraHeaders {
opts = append(opts, option.WithHeader(key, value))
}

// Add API dump middleware if configured
if mw := apidump.NewMiddleware(i.cfg.APIDumpDir, config.ProviderOpenAI, i.Model(), i.id, i.logger, quartz.NewReal()); mw != nil {
opts = append(opts, option.WithMiddleware(mw))
Expand Down
47 changes: 47 additions & 0 deletions provider/copilot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,53 @@ func TestCopilot_CreateInterceptor(t *testing.T) {
assert.Contains(t, err.Error(), "unmarshal responses request body")
})

t.Run("Responses_ForwardsHeadersToUpstream", func(t *testing.T) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: since there's common behaviour I'd expect a common test.
This is fine for now but in general I'd like to support the same tests (using impl-specific fixtures) across both impls where it's simply input/output behaviour being tested.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agree! Also thought about it, but wanted to keep the PR simple. I can address this in a follow-up PR 👍

t.Parallel()

var receivedHeaders http.Header

// Mock upstream that captures headers
mockUpstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
receivedHeaders = r.Header.Clone()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"id":"resp-123","object":"responses.response","created":1677652288,"model":"gpt-5-mini","output":[],"usage":{"input_tokens":5,"output_tokens":10,"total_tokens":15}}`))
}))
t.Cleanup(mockUpstream.Close)

// Create provider with mock upstream URL
provider := NewCopilot(config.Copilot{
BaseURL: mockUpstream.URL,
})

body := `{"model": "gpt-5-mini", "input": "hello", "stream": false}`
req := httptest.NewRequest(http.MethodPost, routeCopilotResponses, bytes.NewBufferString(body))
req.Header.Set("Authorization", "Bearer test-token")
req.Header.Set("Editor-Version", "vscode/1.85.0")
req.Header.Set("Copilot-Integration-Id", "test-integration")
req.Header.Set("X-Custom-Header", "should-not-forward")
w := httptest.NewRecorder()

interceptor, err := provider.CreateInterceptor(w, req, testTracer)
require.NoError(t, err)
require.NotNil(t, interceptor)

// Setup and process request
logger := slog.Make()
interceptor.Setup(logger, &testutil.MockRecorder{}, nil)

processReq := httptest.NewRequest(http.MethodPost, routeCopilotResponses, nil)
err = interceptor.ProcessRequest(w, processReq)
require.NoError(t, err)

// Verify headers were forwarded
assert.Equal(t, "vscode/1.85.0", receivedHeaders.Get("Editor-Version"))
assert.Equal(t, "test-integration", receivedHeaders.Get("Copilot-Integration-Id"))

// Verify non-Copilot headers are not forwarded
assert.Empty(t, receivedHeaders.Get("X-Custom-Header"), "non-Copilot headers should not be forwarded")
})

t.Run("UnknownRoute", func(t *testing.T) {
t.Parallel()

Expand Down