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
43 changes: 43 additions & 0 deletions src/copilot-api-resolver.internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Parse a provider base URL into a URL object, handling missing schemes.
* Returns undefined if the input is empty or unparseable.
*/
function parseProviderBaseUrl(providerBaseUrl: string | undefined): URL | undefined {
const trimmed = providerBaseUrl?.trim();
if (!trimmed) return undefined;

const candidate = trimmed.includes('://')
? trimmed
: `https://${trimmed}`;

try {
return new URL(candidate);
} catch {
return undefined;
}
}

/**
* Derive a Copilot API target hostname from COPILOT_PROVIDER_BASE_URL.
* Returns undefined when the value is empty or not a valid URL/host.
*/
export function deriveCopilotApiTargetFromProviderBaseUrl(
providerBaseUrl: string | undefined
): string | undefined {
return parseProviderBaseUrl(providerBaseUrl)?.hostname || undefined;
}

/**
* Derive a Copilot API base-path prefix from COPILOT_PROVIDER_BASE_URL.
* Returns undefined when the value is empty, invalid, or has no path.
*/
export function deriveCopilotApiBasePathFromProviderBaseUrl(
providerBaseUrl: string | undefined
): string | undefined {
const url = parseProviderBaseUrl(providerBaseUrl);
if (!url) return undefined;

const pathname = url.pathname.replace(/\/+$/, '');
if (!pathname || pathname === '/') return undefined;
return pathname.startsWith('/') ? pathname : `/${pathname}`;
}
12 changes: 10 additions & 2 deletions src/copilot-api-resolver.test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import {
deriveCopilotApiBasePathFromProviderBaseUrl,
deriveCopilotApiTargetFromProviderBaseUrl,
} from './copilot-api-resolver.internal';

/**
* Test-only re-export of internal helpers from copilot-api-resolver.
* Test-only helpers for copilot-api-resolver.
* Tests should import from this file, not directly from the production module.
*/
export { copilotApiResolverTestHelpers } from './copilot-api-resolver';
export const copilotApiResolverTestHelpers = {
deriveCopilotApiTargetFromProviderBaseUrl,
deriveCopilotApiBasePathFromProviderBaseUrl,
};
5 changes: 5 additions & 0 deletions src/copilot-api-resolver.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as copilotApiResolverModule from './copilot-api-resolver';
import {
resolveCopilotApiKey,
resolveCopilotApiRouting,
Expand All @@ -10,6 +11,10 @@ const {
} = copilotApiResolverTestHelpers;

describe('resolveCopilotApiKey', () => {
it('should not expose test helpers from the production module API', () => {
expect(copilotApiResolverModule).not.toHaveProperty('copilotApiResolverTestHelpers');
});

it('should return COPILOT_API_KEY when set', () => {
const env = { COPILOT_API_KEY: 'key123' };
expect(resolveCopilotApiKey(env)).toBe('key123');
Expand Down
56 changes: 5 additions & 51 deletions src/copilot-api-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
deriveCopilotApiBasePathFromProviderBaseUrl,
deriveCopilotApiTargetFromProviderBaseUrl,
} from './copilot-api-resolver.internal';

/**
* Resolve the Copilot BYOK key from supported environment variables.
* COPILOT_API_KEY takes precedence over COPILOT_PROVIDER_API_KEY.
Expand All @@ -8,50 +13,6 @@ export function resolveCopilotApiKey(
return env.COPILOT_API_KEY || env.COPILOT_PROVIDER_API_KEY;
}

/**
* Parse a provider base URL into a URL object, handling missing schemes.
* Returns undefined if the input is empty or unparseable.
*/
function parseProviderBaseUrl(providerBaseUrl: string | undefined): URL | undefined {
const trimmed = providerBaseUrl?.trim();
if (!trimmed) return undefined;

const candidate = trimmed.includes('://')
? trimmed
: `https://${trimmed}`;

try {
return new URL(candidate);
} catch {
return undefined;
}
}

/**
* Derive a Copilot API target hostname from COPILOT_PROVIDER_BASE_URL.
* Returns undefined when the value is empty or not a valid URL/host.
*/
function deriveCopilotApiTargetFromProviderBaseUrl(
providerBaseUrl: string | undefined
): string | undefined {
return parseProviderBaseUrl(providerBaseUrl)?.hostname || undefined;
}

/**
* Derive a Copilot API base-path prefix from COPILOT_PROVIDER_BASE_URL.
* Returns undefined when the value is empty, invalid, or has no path.
*/
function deriveCopilotApiBasePathFromProviderBaseUrl(
providerBaseUrl: string | undefined
): string | undefined {
const url = parseProviderBaseUrl(providerBaseUrl);
if (!url) return undefined;

const pathname = url.pathname.replace(/\/+$/, '');
if (!pathname || pathname === '/') return undefined;
return pathname.startsWith('/') ? pathname : `/${pathname}`;
}

/**
* Resolve Copilot target/base-path routing for BYOK provider-style env vars.
*
Expand Down Expand Up @@ -82,10 +43,3 @@ export function resolveCopilotApiRouting(
copilotApiBasePathFromProviderBaseUrl,
};
}

/** @internal Exposed only for unit tests — not part of the public API. */
// ts-prune-ignore-next
export const copilotApiResolverTestHelpers = {
deriveCopilotApiTargetFromProviderBaseUrl,
deriveCopilotApiBasePathFromProviderBaseUrl,
};
Loading