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
2 changes: 1 addition & 1 deletion packages/openai-adapters/src/apis/openaiResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
ResponseUsage,
} from "openai/resources/responses/responses.js";

const RESPONSES_MODEL_REGEX = /^(?:gpt-5|gpt-5-codex|o)/i;
const RESPONSES_MODEL_REGEX = /^(?:gpt-5|gpt-5-codex|o[0-9])/i;

export function isResponsesModel(model: string): boolean {
return !!model && RESPONSES_MODEL_REGEX.test(model);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { describe, expect, it } from "vitest";
import { customFetch } from "../util.js";

/**
* Tests for the letRequestOptionsOverrideAuthHeaders function in customFetch
*
* This function removes duplicate Authorization and x-api-key headers when
* custom headers are provided in requestOptions.headers.
*
* The logic being tested:
* 1. If requestOptions.headers contains Authorization or x-api-key
* 2. Remove those headers from init.headers (sent by OpenAI SDK)
* 3. Let fetchwithRequestOptions merge in the custom headers
* 4. Results in single, correct header (not duplicate)
*/
describe("customFetch - auth header override logic", () => {
it("should export customFetch function", () => {
expect(typeof customFetch).toBe("function");
});

it("should return a function when called", () => {
const result = customFetch({
headers: { "x-api-key": "test" },
});
expect(typeof result).toBe("function");
});

it("should handle requestOptions with Authorization header", () => {
const result = customFetch({
headers: { Authorization: "Bearer custom-token" },
});
expect(typeof result).toBe("function");
});

it("should handle requestOptions with x-api-key header", () => {
const result = customFetch({
headers: { "x-api-key": "custom-key" },
});
expect(typeof result).toBe("function");
});

it("should handle requestOptions with both auth headers", () => {
const result = customFetch({
headers: {
Authorization: "Bearer custom-token",
"x-api-key": "custom-key",
},
});
expect(typeof result).toBe("function");
});

it("should handle empty requestOptions", () => {
const result = customFetch({});
expect(typeof result).toBe("function");
});

it("should handle undefined requestOptions", () => {
const result = customFetch(undefined);
expect(typeof result).toBe("function");
});

it("should handle case variations in header names", () => {
// lowercase authorization
const result1 = customFetch({
headers: { authorization: "Bearer custom" },
});
expect(typeof result1).toBe("function");

// uppercase X-Api-Key
const result2 = customFetch({
headers: { "X-Api-Key": "custom" },
});
expect(typeof result2).toBe("function");
});
});

/**
* Note: Full integration testing of the header override logic requires
* mocking the entire fetch stack (@continuedev/fetch package) which is
* complex. The above tests verify the function structure and basic behavior.
*
* The actual header removal logic is tested end-to-end by:
* - Manual testing with MITRE AIP endpoints
* - Real-world usage showing duplicate headers are resolved
*
* Related issues:
* - #7047: Duplicate headers bug
* - #8684: Authorization header fix (this extends it)
*/
57 changes: 37 additions & 20 deletions packages/openai-adapters/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,34 +159,51 @@ export function customFetch(
return patchedFetch;
}

function letRequestOptionsOverrideAuthorizationHeader(init: any): any {
if (
!init ||
!init.headers ||
!requestOptions ||
!requestOptions.headers ||
(!requestOptions.headers["Authorization"] &&
!requestOptions.headers["authorization"])
) {
function letRequestOptionsOverrideAuthHeaders(init: any): any {
if (!init || !init.headers || !requestOptions || !requestOptions.headers) {
return init;
}

if (init.headers instanceof Headers) {
init.headers.delete("Authorization");
} else if (Array.isArray(init.headers)) {
init.headers = init.headers.filter(
(header: [string, string]) =>
(header[0] ?? "").toLowerCase() !== "authorization",
);
} else if (typeof init.headers === "object") {
delete init.headers["Authorization"];
delete init.headers["authorization"];
// Check if custom Authorization or x-api-key headers are provided
const hasCustomAuth =
requestOptions.headers["Authorization"] ||
requestOptions.headers["authorization"];
const hasCustomXApiKey =
requestOptions.headers["x-api-key"] ||
requestOptions.headers["X-Api-Key"];

// Remove default auth headers if custom ones are provided
if (hasCustomAuth || hasCustomXApiKey) {
if (init.headers instanceof Headers) {
if (hasCustomAuth) {
init.headers.delete("Authorization");
}
if (hasCustomXApiKey) {
init.headers.delete("x-api-key");
}
} else if (Array.isArray(init.headers)) {
init.headers = init.headers.filter((header: [string, string]) => {
const headerLower = (header[0] ?? "").toLowerCase();
if (hasCustomAuth && headerLower === "authorization") return false;
if (hasCustomXApiKey && headerLower === "x-api-key") return false;
return true;
});
} else if (typeof init.headers === "object") {
if (hasCustomAuth) {
delete init.headers["Authorization"];
delete init.headers["authorization"];
}
if (hasCustomXApiKey) {
delete init.headers["x-api-key"];
delete init.headers["X-Api-Key"];
}
}
}
return init;
}

return (req: URL | string | Request, init?: any) => {
init = letRequestOptionsOverrideAuthorizationHeader(init);
init = letRequestOptionsOverrideAuthHeaders(init);
if (typeof req === "string" || req instanceof URL) {
return fetchwithRequestOptions(req, init, requestOptions);
} else {
Expand Down
Loading