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
8 changes: 6 additions & 2 deletions assistant/src/__tests__/managed-proxy-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,12 @@ describe("buildManagedBaseUrl", () => {
expect(await buildManagedBaseUrl("gemini")).toBe(
"https://platform.example.com/v1/runtime-proxy/gemini",
);
expect(await buildManagedBaseUrl("openai")).toBe(
"https://platform.example.com/v1/runtime-proxy/openai",
);
});

test("returns undefined for non-managed providers", async () => {
expect(await buildManagedBaseUrl("openai")).toBeUndefined();
expect(await buildManagedBaseUrl("fireworks")).toBeUndefined();
expect(await buildManagedBaseUrl("openrouter")).toBeUndefined();
expect(await buildManagedBaseUrl("ollama")).toBeUndefined();
Expand All @@ -130,6 +132,7 @@ describe("buildManagedBaseUrl", () => {
mockAssistantApiKey = null;
expect(await buildManagedBaseUrl("anthropic")).toBeUndefined();
expect(await buildManagedBaseUrl("gemini")).toBeUndefined();
expect(await buildManagedBaseUrl("openai")).toBeUndefined();
});
});

Expand All @@ -142,7 +145,7 @@ describe("managedFallbackEnabledFor", () => {
test("returns true only for managed fallback providers with prerequisites", async () => {
expect(await managedFallbackEnabledFor("anthropic")).toBe(true);
expect(await managedFallbackEnabledFor("gemini")).toBe(true);
expect(await managedFallbackEnabledFor("openai")).toBe(false);
expect(await managedFallbackEnabledFor("openai")).toBe(true);
});

test("returns false for non-managed provider", async () => {
Expand All @@ -158,5 +161,6 @@ describe("managedFallbackEnabledFor", () => {
mockAssistantApiKey = null;
expect(await managedFallbackEnabledFor("anthropic")).toBe(false);
expect(await managedFallbackEnabledFor("gemini")).toBe(false);
expect(await managedFallbackEnabledFor("openai")).toBe(false);
});
});
50 changes: 40 additions & 10 deletions assistant/src/__tests__/provider-managed-proxy-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const DIRECT_OR_MANAGED_PROVIDER_KEYS: string[] = [
"fireworks",
"openrouter",
];
const MANAGED_FALLBACK_PROVIDERS: string[] = ["anthropic", "gemini"];
const MANAGED_FALLBACK_PROVIDERS: string[] = ["anthropic", "gemini", "openai"];

function enableManagedProxy() {
mockPlatformBaseUrl = PLATFORM_BASE;
Expand Down Expand Up @@ -172,14 +172,18 @@ describe("managed proxy integration — credential precedence", () => {
},
);

test("managed bootstrap registers anthropic and gemini only", async () => {
test("managed bootstrap registers anthropic, openai, and gemini", async () => {
enableManagedProxy();
mockProviderKeys = {};
await initializeProviders(makeProvidersConfig("anthropic", "test-model"));
expect(listProviders()).toEqual(["anthropic", "gemini"]);
expect(listProviders()).toEqual(
expect.arrayContaining(["anthropic", "openai", "gemini"]),
);
expect(listProviders()).toHaveLength(3);
expect(getProviderRoutingSource("anthropic")).toBe("managed-proxy");
expect(getProviderRoutingSource("openai")).toBe("managed-proxy");
expect(getProviderRoutingSource("gemini")).toBe("managed-proxy");
for (const p of ["openai", "fireworks", "openrouter"]) {
for (const p of ["fireworks", "openrouter"]) {
expect(getProviderRoutingSource(p)).toBeUndefined();
}
});
Expand All @@ -205,6 +209,23 @@ describe("managed proxy integration — credential precedence", () => {
expect(baseURL).toContain("/v1/runtime-proxy/anthropic");
});

test("managed openai uses openai proxy path", async () => {
enableManagedProxy();
mockProviderKeys = {};
await initializeProviders(makeProvidersConfig("openai", "gpt-4o"));

const provider = getProvider("openai");

// Unwrap RetryProvider → OpenAIResponsesProvider to inspect the OpenAI
// SDK client's baseURL.
const retryInner = (provider as any).inner;
const openaiClient = (retryInner as any).client;

expect(openaiClient).toBeDefined();
const baseURL: string = openaiClient.baseURL;
expect(baseURL).toContain("/v1/runtime-proxy/openai");
});

test("managed gemini uses gemini proxy path", async () => {
enableManagedProxy();
mockProviderKeys = {};
Expand Down Expand Up @@ -242,16 +263,18 @@ describe("managed proxy integration — credential precedence", () => {
});

describe("mixed: some user keys + managed fallback fills gaps", () => {
test("user key for anthropic routes direct and managed fallback only fills gemini", async () => {
test("user key for anthropic routes direct and managed fallback fills openai and gemini", async () => {
enableManagedProxy();
setUserKeysFor("anthropic");
await initializeProviders(makeProvidersConfig("anthropic", "test-model"));
const registered = listProviders();
expect(registered).toContain("anthropic");
expect(getProviderRoutingSource("anthropic")).toBe("user-key");
expect(registered).toContain("openai");
expect(getProviderRoutingSource("openai")).toBe("managed-proxy");
expect(registered).toContain("gemini");
expect(getProviderRoutingSource("gemini")).toBe("managed-proxy");
for (const p of ["openai", "fireworks", "openrouter"]) {
for (const p of ["fireworks", "openrouter"]) {
expect(registered).not.toContain(p);
expect(getProviderRoutingSource(p)).toBeUndefined();
}
Expand All @@ -268,6 +291,7 @@ describe("managed proxy integration — credential precedence", () => {
expect(getProviderRoutingSource("anthropic")).toBe("managed-proxy");
expect(registered).toContain("gemini");
expect(getProviderRoutingSource("gemini")).toBe("managed-proxy");
// OpenAI has a user key so it's user-key, not managed-proxy
for (const p of ["fireworks", "openrouter"]) {
expect(registered).not.toContain(p);
expect(getProviderRoutingSource(p)).toBeUndefined();
Expand Down Expand Up @@ -307,8 +331,8 @@ describe("managed proxy integration — ollama exclusion", () => {
});

describe("managed proxy integration — constants integrity", () => {
test("anthropic and gemini have metadata with managed=true and a proxyPath", () => {
for (const provider of ["anthropic", "gemini"]) {
test("anthropic, openai, and gemini have metadata with managed=true and a proxyPath", () => {
for (const provider of ["anthropic", "openai", "gemini"]) {
const meta = MANAGED_PROVIDER_META[provider];
expect(meta).toBeDefined();
expect(meta.managed).toBe(true);
Expand All @@ -329,8 +353,14 @@ describe("managed proxy integration — constants integrity", () => {
);
});

test("openai-compatible providers are not managed proxy capable", () => {
for (const provider of ["openai", "fireworks", "openrouter"]) {
test("openai routes through openai proxy path", () => {
expect(MANAGED_PROVIDER_META.openai.proxyPath).toBe(
"/v1/runtime-proxy/openai",
);
});

test("fireworks and openrouter are not managed proxy capable", () => {
for (const provider of ["fireworks", "openrouter"]) {
expect(MANAGED_PROVIDER_META[provider].managed).toBe(false);
expect(MANAGED_PROVIDER_META[provider].proxyPath).toBeUndefined();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let providerRefreshCalls = 0;
const PLATFORM_BASE_URL = "https://platform.example.com";
const ASSISTANT_API_KEY_PATH = credentialKey("vellum", "assistant_api_key");
const PLATFORM_BASE_URL_PATH = credentialKey("vellum", "platform_base_url");
const MANAGED_PROVIDERS = ["anthropic", "gemini"] as const;
const MANAGED_PROVIDERS = ["anthropic", "openai", "gemini"] as const;

let platformBaseUrlOverride: string | undefined;

Expand Down
3 changes: 2 additions & 1 deletion assistant/src/providers/managed-proxy/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export interface ManagedProviderMeta {
export const MANAGED_PROVIDER_META: Record<string, ManagedProviderMeta> = {
openai: {
name: "openai",
managed: false,
managed: true,
proxyPath: "/v1/runtime-proxy/openai",
},
Comment on lines 29 to 33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 OpenAI SDK baseURL behavior with proxy path is correct but worth noting

When using the managed proxy, the OpenAI SDK's baseURL is set to something like https://platform.example.com/v1/runtime-proxy/openai. The SDK appends its own API paths (e.g. /responses) to this base, yielding https://platform.example.com/v1/runtime-proxy/openai/responses. This matches how the Anthropic SDK handles its baseURL for the anthropic managed proxy. The platform proxy server must be configured to handle this path structure — but that's a server-side concern in vellum-assistant-platform, not a bug here.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

anthropic: {
name: "anthropic",
Expand Down
Loading