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
10 changes: 10 additions & 0 deletions .agentv/targets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ targets:
grader_target: grader
log_format: json

- name: copilot-sdk-azure
provider: copilot-sdk
model: ${{ AZURE_DEPLOYMENT_NAME }}
byok:
type: azure
base_url: ${{ AZURE_OPENAI_ENDPOINT }}
api_key: ${{ AZURE_OPENAI_API_KEY }}
grader_target: grader
log_format: json

- name: claude
provider: claude-cli
grader_target: grader
Expand Down
14 changes: 7 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ cd ../agentv.worktrees/<type>-<short-desc>
- Subagents for: research, file exploration, running tests, code review.
- For complex problems, throw more subagents at it — parallelize where possible.
- Name subagents descriptively.
- Before declaring a repo change complete or opening/finalizing a PR, spawn a subagent for a final code review pass unless the user explicitly says not to.
- Before declaring a repo change complete or opening/finalizing a PR, complete manual e2e verification first (see E2E Checklist), **then** spawn a subagent for a final code review pass. E2E must pass before code review — if e2e fails, fix the issue before investing time in review. The user may explicitly skip the review step.

### Autonomous Bug Fixes
- When you spot a bug, just fix it. Don't ask for hand-holding.
Expand Down Expand Up @@ -382,12 +382,12 @@ When working on a GitHub issue, **ALWAYS** follow this workflow:
```
Push incremental commits to the draft PR as you work so progress is visible and recoverable.

6. **Before marking the PR ready for review or merging a low-risk change**, ensure:
- **E2E verification completed** (see "Completing Work — E2E Checklist")
- For CLI or other user-facing changes, run at least one manual end-to-end check of the real user flow, not just unit/integration tests.
- A final subagent code review pass has been run and any findings addressed or called out.
- CI pipeline passes (all checks green)
- No merge conflicts with `main`
6. **Before marking the PR ready for review or merging a low-risk change**, ensure (in this order):
1. **E2E verification completed** (see "Completing Work — E2E Checklist") — this must pass first.
2. For CLI or other user-facing changes, run at least one manual end-to-end check of the real user flow, not just unit/integration tests.
3. **After e2e passes**, spawn a final subagent code review pass and address or call out any findings. Do NOT run the code review before e2e — if e2e fails you'll need to fix it first, which invalidates the review.
4. CI pipeline passes (all checks green).
5. No merge conflicts with `main`.

7. **Only after verification is complete**:
- Mark the draft PR ready for review, or
Expand Down
40 changes: 40 additions & 0 deletions packages/core/src/evaluation/providers/copilot-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,29 @@ export class CopilotSdkProvider implements Provider {
};
}

// BYOK — pass a provider block to route requests through a user-provided endpoint
// instead of GitHub's Copilot infrastructure. See copilot-sdk docs/auth/byok.md.
if (this.config.byokBaseUrl) {
const byokType = this.config.byokType ?? 'openai';
// biome-ignore lint/suspicious/noExplicitAny: SDK provider config shape is dynamic
const provider: any = {
type: byokType,
baseUrl: normalizeByokBaseUrl(this.config.byokBaseUrl, byokType),
};
if (this.config.byokBearerToken) {
provider.bearerToken = this.config.byokBearerToken;
} else if (this.config.byokApiKey) {
provider.apiKey = this.config.byokApiKey;
}
if (this.config.byokWireApi) {
provider.wireApi = this.config.byokWireApi;
}
if (this.config.byokType === 'azure' && this.config.byokApiVersion) {
provider.azure = { apiVersion: this.config.byokApiVersion };
}
sessionOptions.provider = provider;
}

// biome-ignore lint/suspicious/noExplicitAny: SDK session type is dynamically loaded
let session: any;
try {
Expand Down Expand Up @@ -386,6 +409,23 @@ function resolveSkillDirectories(cwd: string): string[] {
return candidates.filter((dir) => existsSync(dir));
}

/**
* Normalize a BYOK base URL for the Copilot SDK.
* For Azure type, if the value is a bare resource name (no https:// prefix),
* construct the full URL: https://{resourceName}.openai.azure.com
* This lets users reuse AZURE_OPENAI_ENDPOINT without a separate env var.
*/
function normalizeByokBaseUrl(baseUrl: string, type: string): string {
const trimmed = baseUrl.trim().replace(/\/+$/, '');
if (/^https?:\/\//i.test(trimmed)) {
return trimmed;
}
if (type === 'azure') {
return `https://${trimmed}.openai.azure.com`;
}
return trimmed;
}

function summarizeSdkEvent(eventType: string, data: unknown): string | undefined {
if (!data || typeof data !== 'object') {
return eventType;
Expand Down
76 changes: 76 additions & 0 deletions packages/core/src/evaluation/providers/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,18 @@ export interface CopilotSdkResolvedConfig {
readonly logDir?: string;
readonly logFormat?: 'summary' | 'json';
readonly systemPrompt?: string;
/** BYOK provider type: "azure", "openai", or "anthropic". */
readonly byokType?: string;
/** BYOK base URL for the provider endpoint. */
readonly byokBaseUrl?: string;
/** BYOK API key for authenticating with the provider. */
readonly byokApiKey?: string;
/** BYOK bearer token (takes precedence over apiKey when set). */
readonly byokBearerToken?: string;
/** BYOK Azure API version (e.g. "2024-10-21"). Only used when byokType is "azure". */
readonly byokApiVersion?: string;
/** BYOK wire API format: "completions" or "responses". */
readonly byokWireApi?: string;
}

export interface CopilotLogResolvedConfig {
Expand Down Expand Up @@ -1427,6 +1439,64 @@ function resolveCopilotSdkConfig(
? systemPromptSource.trim()
: undefined;

// BYOK (Bring Your Own Key) — allows routing through a user-provided endpoint
// instead of GitHub's Copilot infrastructure. The byok block maps to the SDK's
// `provider` option on createSession(). See copilot-sdk docs/auth/byok.md.
const byok = target.byok as Record<string, unknown> | undefined;
let byokType: string | undefined;
let byokBaseUrl: string | undefined;
let byokApiKey: string | undefined;
let byokBearerToken: string | undefined;
let byokApiVersion: string | undefined;
let byokWireApi: string | undefined;

if (byok && typeof byok === 'object') {
byokType = resolveOptionalString(byok.type, env, `${target.name} byok type`, {
allowLiteral: true,
optionalEnv: true,
});

byokBaseUrl = resolveOptionalString(byok.base_url, env, `${target.name} byok base URL`, {
allowLiteral: true,
optionalEnv: true,
});

byokApiKey = resolveOptionalString(byok.api_key, env, `${target.name} byok API key`, {
allowLiteral: false,
optionalEnv: true,
});

byokBearerToken = resolveOptionalString(
byok.bearer_token,
env,
`${target.name} byok bearer token`,
{
allowLiteral: false,
optionalEnv: true,
},
);

byokApiVersion = resolveOptionalString(
byok.api_version,
env,
`${target.name} byok API version`,
{
allowLiteral: true,
optionalEnv: true,
},
);

byokWireApi = resolveOptionalString(byok.wire_api, env, `${target.name} byok wire API`, {
allowLiteral: true,
optionalEnv: true,
});

// base_url is required when byok is specified
if (!byokBaseUrl) {
throw new Error(`${target.name}: 'byok.base_url' is required when 'byok' is specified`);
}
}

return {
cliUrl,
cliPath,
Expand All @@ -1438,6 +1508,12 @@ function resolveCopilotSdkConfig(
logDir,
logFormat,
systemPrompt,
byokType,
byokBaseUrl,
byokApiKey,
byokBearerToken,
byokApiVersion,
byokWireApi,
};
}

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/evaluation/providers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ export interface TargetDefinition {
readonly cli_url?: string | unknown | undefined;
readonly cli_path?: string | unknown | undefined;
readonly github_token?: string | unknown | undefined;
// Copilot SDK BYOK (Bring Your Own Key) — routes through a user-provided endpoint
readonly byok?: Record<string, unknown> | undefined;
// Retry configuration fields
readonly max_retries?: number | unknown | undefined;
readonly retry_initial_delay_ms?: number | unknown | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const COPILOT_SDK_SETTINGS = new Set([
'log_format',
'system_prompt',
'workspace_template',
'byok',
]);

const COPILOT_CLI_SETTINGS = new Set([
Expand Down
Loading
Loading