diff --git a/.github/drivers/copilot_sdk_driver_sample_node.cjs b/.github/drivers/copilot_sdk_driver_sample_node.cjs index cc20fdac5c3..0b65e83ede6 100644 --- a/.github/drivers/copilot_sdk_driver_sample_node.cjs +++ b/.github/drivers/copilot_sdk_driver_sample_node.cjs @@ -2,6 +2,7 @@ "use strict"; const fs = require("node:fs"); +const { isValidProviderConfig, isValidModelConfig, parseMultiProviderJson } = require("../../actions/setup/js/copilot_sdk_multi_provider.cjs"); // Default timeout for a single sendAndWait call: 10 minutes. // Override via the COPILOT_SDK_SEND_TIMEOUT_MS environment variable. @@ -43,31 +44,6 @@ function extractAssistantContent(message) { return ""; } -function isValidProviderConfig(p) { - return p && typeof p.name === "string" && typeof p.type === "string" && typeof p.baseUrl === "string"; -} - -function isValidModelConfig(m) { - return m && typeof m.id === "string" && typeof m.provider === "string"; -} - -function parseMultiProviderJson(raw) { - if (!raw) return null; - try { - const parsed = JSON.parse(raw); - if (!parsed || typeof parsed !== "object") return null; - if (!Array.isArray(parsed.providers) || parsed.providers.length < 1) return null; - if (!Array.isArray(parsed.models) || parsed.models.length < 1) return null; - // Validate minimal shape: providers must have name/type/baseUrl, models must have id/provider - if (!parsed.providers.every(isValidProviderConfig)) return null; - if (!parsed.models.every(isValidModelConfig)) return null; - const model = typeof parsed.model === "string" ? parsed.model.trim() : ""; - return { model, providers: parsed.providers, models: parsed.models }; - } catch { - return null; - } -} - function buildSessionConfig(model, onPermissionRequest) { const config = { onPermissionRequest, diff --git a/actions/setup/js/copilot_sdk_driver.cjs b/actions/setup/js/copilot_sdk_driver.cjs index 21b2797fc67..2e609e0462a 100644 --- a/actions/setup/js/copilot_sdk_driver.cjs +++ b/actions/setup/js/copilot_sdk_driver.cjs @@ -19,8 +19,9 @@ * client connection, runs the session, and exits. * * Reusable helpers live in: - * copilot_sdk_permissions.cjs — permission config parsing and handler builder - * copilot_sdk_session.cjs — session runner and JSONL event serialization + * copilot_sdk_permissions.cjs — permission config parsing and handler builder + * copilot_sdk_session.cjs — session runner and JSONL event serialization + * copilot_sdk_multi_provider.cjs — multi-provider JSON parsing and validation */ "use strict"; @@ -28,6 +29,7 @@ const fs = require("fs"); const { runWithCopilotSDK, extractPromptFromArgs } = require("./copilot_sdk_session.cjs"); const { parsePermissionConfigFromServerArgs } = require("./copilot_sdk_permissions.cjs"); +const { parseMultiProviderJson } = require("./copilot_sdk_multi_provider.cjs"); // Re-export the session and permission helpers so that existing callers that // require("./copilot_sdk_driver.cjs") (e.g. copilot_harness.cjs) continue to work. @@ -45,45 +47,6 @@ function log(msg) { process.stderr.write(`[copilot-sdk-driver] ${msg}\n`); } -function isValidProviderConfig(p) { - return p && typeof p.name === "string" && typeof p.type === "string" && typeof p.baseUrl === "string"; -} - -function isValidModelConfig(m) { - return m && typeof m.id === "string" && typeof m.provider === "string"; -} - -/** - * Parse the GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON env var. - * - * Returns `null` when the env var is unset or contains invalid JSON. - * On success returns `{ model, providers, models }` where the shapes match the - * Copilot SDK `NamedProviderConfig` / `ProviderModelConfig` types. - * - * @param {string | undefined} value - * @returns {{ - * model: string, - * providers: import("@github/copilot-sdk").NamedProviderConfig[], - * models: import("@github/copilot-sdk").ProviderModelConfig[], - * } | null} - */ -function parseMultiProviderJson(value) { - if (!value) return null; - try { - const parsed = JSON.parse(value); - if (!parsed || typeof parsed !== "object") return null; - if (!Array.isArray(parsed.providers) || parsed.providers.length < 1) return null; - if (!Array.isArray(parsed.models) || parsed.models.length < 1) return null; - // Validate minimal shape: providers must have name/type/baseUrl, models must have id/provider - if (!parsed.providers.every(isValidProviderConfig)) return null; - if (!parsed.models.every(isValidModelConfig)) return null; - const model = typeof parsed.model === "string" ? parsed.model.trim() : ""; - return { model, providers: parsed.providers, models: parsed.models }; - } catch { - return null; - } -} - /** * Entry point when the driver is run directly with Node: * node copilot_sdk_driver.cjs diff --git a/actions/setup/js/copilot_sdk_multi_provider.cjs b/actions/setup/js/copilot_sdk_multi_provider.cjs new file mode 100644 index 00000000000..f5eda347657 --- /dev/null +++ b/actions/setup/js/copilot_sdk_multi_provider.cjs @@ -0,0 +1,65 @@ +// @ts-check + +/** + * Copilot SDK Multi-Provider Helpers + * + * Shared parsing and validation utilities for the GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON + * environment variable. Consumed by both the production SDK driver + * (copilot_sdk_driver.cjs) and any custom driver (e.g. the Node sample driver + * under .github/drivers/) so that the validation logic stays in one place. + */ + +"use strict"; + +/** + * @param {any} p + * @returns {boolean} + */ +function isValidProviderConfig(p) { + return p && typeof p.name === "string" && typeof p.type === "string" && typeof p.baseUrl === "string"; +} + +/** + * @param {any} m + * @returns {boolean} + */ +function isValidModelConfig(m) { + return m && typeof m.id === "string" && typeof m.provider === "string"; +} + +/** + * Parse the GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON env var. + * + * Returns `null` when the env var is unset or contains invalid JSON. + * On success returns `{ model, providers, models }` where the shapes match the + * Copilot SDK `NamedProviderConfig` / `ProviderModelConfig` types. + * + * @param {string | undefined} value + * @returns {{ + * model: string, + * providers: import("@github/copilot-sdk").NamedProviderConfig[], + * models: import("@github/copilot-sdk").ProviderModelConfig[], + * } | null} + */ +function parseMultiProviderJson(value) { + if (!value) return null; + try { + const parsed = JSON.parse(value); + if (!parsed || typeof parsed !== "object") return null; + if (!Array.isArray(parsed.providers) || parsed.providers.length < 1) return null; + if (!Array.isArray(parsed.models) || parsed.models.length < 1) return null; + // Validate minimal shape: providers must have name/type/baseUrl, models must have id/provider + if (!parsed.providers.every(isValidProviderConfig)) return null; + if (!parsed.models.every(isValidModelConfig)) return null; + const model = typeof parsed.model === "string" ? parsed.model.trim() : ""; + return { model, providers: parsed.providers, models: parsed.models }; + } catch { + return null; + } +} + +module.exports = { + isValidProviderConfig, + isValidModelConfig, + parseMultiProviderJson, +};