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
57 changes: 47 additions & 10 deletions actions/setup/js/copilot_harness.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,37 @@ function detectCopilotErrors(output) {
};
}

/**
* Build child-process environment additions for Copilot SDK mode.
* @param {{
* sdkEnv: NodeJS.ProcessEnv,
* copilotSDKMode: boolean,
* copilotConnectionToken: string,
* providerBaseUrl: string,
* providerType: string,
* providerWireApi: string,
* resolvedModel: string,
* }} options
* @returns {NodeJS.ProcessEnv}
*/
function buildCopilotSDKChildEnv({ sdkEnv, copilotSDKMode, copilotConnectionToken, providerBaseUrl, providerType, providerWireApi, resolvedModel }) {
if (!copilotSDKMode) {
return sdkEnv;
}
return {
...sdkEnv,
COPILOT_CONNECTION_TOKEN: copilotConnectionToken,
GH_AW_COPILOT_SDK_PROVIDER_BASE_URL: providerBaseUrl,
GH_AW_COPILOT_SDK_PROVIDER_TYPE: providerType,
...(providerWireApi ? { GH_AW_COPILOT_SDK_PROVIDER_WIRE_API: providerWireApi } : {}),
COPILOT_MODEL: resolvedModel,
// Native Copilot CLI BYOK env vars — consumed by the headless sidecar for all sessions.
COPILOT_PROVIDER_BASE_URL: providerBaseUrl,
COPILOT_PROVIDER_TYPE: providerType,
...(providerWireApi ? { COPILOT_PROVIDER_WIRE_API: providerWireApi } : {}),
};
}

/**
* Write Copilot detection outputs to $GITHUB_OUTPUT.
* @param {{ inferenceAccessError: boolean, mcpPolicyError: boolean, agenticEngineTimeout: boolean, modelNotSupportedError: boolean, http400ResponseError: boolean }} results
Expand Down Expand Up @@ -746,16 +777,21 @@ async function main() {
// (started by the harness) and the SDK client share the same token.
// In SDK mode also inject the resolved BYOK provider base URL, type, and model so the driver
// subprocess does not need to re-read the reflect file.
const sdkChildEnv = copilotSDKMode
? {
...sdkEnv,
COPILOT_CONNECTION_TOKEN: copilotConnectionToken,
GH_AW_COPILOT_SDK_PROVIDER_BASE_URL: providerBaseUrl,
GH_AW_COPILOT_SDK_PROVIDER_TYPE: providerType,
GH_AW_COPILOT_SDK_PROVIDER_WIRE_API: providerWireApi,
COPILOT_MODEL: resolvedModel,
}
: sdkEnv;
//
// Additionally, forward BYOK config as native Copilot CLI COPILOT_PROVIDER_* env vars so
// the headless sidecar propagates the same provider to sub-agent sessions spawned via the
// task tool. Sub-agents do not inherit the SDK session-level `provider` config; the headless
// server instead reads COPILOT_PROVIDER_* from its own process env to configure each
// sub-agent session's inference backend.
const sdkChildEnv = buildCopilotSDKChildEnv({
sdkEnv,
copilotSDKMode,
copilotConnectionToken,
providerBaseUrl,
providerType,
providerWireApi,
resolvedModel,
});
const childEnv = Object.keys(sdkChildEnv).length > 0 ? { ...process.env, ...sdkChildEnv } : undefined;

// Pre-flight: skip the agent entirely when a noop has already been written by a prior step.
Expand Down Expand Up @@ -1109,6 +1145,7 @@ if (typeof module !== "undefined" && module.exports) {
fetchAWFReflect,
fetchModelsFromUrl,
buildCopilotProxyAuthFailureDiagnostic,
buildCopilotSDKChildEnv,
envFlagEnabled,
generateCopilotConnectionToken,
buildCopilotSDKServerArgs,
Expand Down
42 changes: 42 additions & 0 deletions actions/setup/js/copilot_harness.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
buildMissingToolAlternatives,
buildInfrastructureIncompletePayload,
buildCopilotProxyAuthFailureDiagnostic,
buildCopilotSDKChildEnv,
envFlagEnabled,
buildPromptFileFallbackInstruction,
countPermissionDeniedIssues,
Expand Down Expand Up @@ -183,6 +184,47 @@ describe("copilot_harness.cjs", () => {
});
});

describe("buildCopilotSDKChildEnv", () => {
it("includes native and gh-aw provider vars when wireApi is configured", () => {
const env = buildCopilotSDKChildEnv({
sdkEnv: { COPILOT_SDK_URI: "http://127.0.0.1:4000" },
copilotSDKMode: true,
copilotConnectionToken: "token-123",
providerBaseUrl: "http://api-proxy:10002",
providerType: "openai",
providerWireApi: "completions",
resolvedModel: "gpt-5.4",
});

expect(env).toMatchObject({
COPILOT_SDK_URI: "http://127.0.0.1:4000",
COPILOT_CONNECTION_TOKEN: "token-123",
GH_AW_COPILOT_SDK_PROVIDER_BASE_URL: "http://api-proxy:10002",
GH_AW_COPILOT_SDK_PROVIDER_TYPE: "openai",
GH_AW_COPILOT_SDK_PROVIDER_WIRE_API: "completions",
COPILOT_MODEL: "gpt-5.4",
COPILOT_PROVIDER_BASE_URL: "http://api-proxy:10002",
COPILOT_PROVIDER_TYPE: "openai",
COPILOT_PROVIDER_WIRE_API: "completions",
});
});

it("omits wireApi vars when wireApi is empty", () => {
const env = buildCopilotSDKChildEnv({
sdkEnv: { COPILOT_SDK_URI: "http://127.0.0.1:4000" },
copilotSDKMode: true,
copilotConnectionToken: "token-123",
providerBaseUrl: "http://api-proxy:10002",
providerType: "openai",
providerWireApi: "",
resolvedModel: "gpt-5.4",
});

expect(env.GH_AW_COPILOT_SDK_PROVIDER_WIRE_API).toBeUndefined();
expect(env.COPILOT_PROVIDER_WIRE_API).toBeUndefined();
});
});

describe("retry policy: continue on partial execution", () => {
// Inline the same retry-eligibility logic as the driver for unit testing.
// The driver retries whenever the session produced output (hasOutput), regardless
Expand Down
Loading