Skip to content

refactor: extract shared multi-provider JSON helpers to eliminate duplicate code#43329

Merged
pelikhan merged 2 commits into
mainfrom
copilot/duplicate-code-fix
Jul 4, 2026
Merged

refactor: extract shared multi-provider JSON helpers to eliminate duplicate code#43329
pelikhan merged 2 commits into
mainfrom
copilot/duplicate-code-fix

Conversation

Copilot AI commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

isValidProviderConfig, isValidModelConfig, and parseMultiProviderJson were copy-pasted identically between the production driver and the Node sample driver, creating a drift risk whenever the GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON shape changes.

Changes

  • New actions/setup/js/copilot_sdk_multi_provider.cjs — single source of truth for multi-provider config parsing and shape validation
  • copilot_sdk_driver.cjs — removes the three inline implementations; imports from the shared module; re-exports parseMultiProviderJson unchanged so copilot_harness.cjs callers are unaffected
  • .github/drivers/copilot_sdk_driver_sample_node.cjs — same removal; imports via ../../actions/setup/js/copilot_sdk_multi_provider.cjs; re-exports all three helpers to preserve its existing module.exports contract

Both drivers now resolve to the same function identity:

const shared = require("actions/setup/js/copilot_sdk_multi_provider.cjs");
const sample = require(".github/drivers/copilot_sdk_driver_sample_node.cjs");
shared.parseMultiProviderJson === sample.parseMultiProviderJson // true

…licate code

Move isValidProviderConfig, isValidModelConfig, and parseMultiProviderJson
into a new copilot_sdk_multi_provider.cjs shared module.

Both copilot_sdk_driver.cjs and the sample driver
(.github/drivers/copilot_sdk_driver_sample_node.cjs) now import from this
single source of truth, eliminating the drift risk when the shape of
GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON changes.

Closes #43328

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix duplicate code in Copilot SDK multi-provider parsing refactor: extract shared multi-provider JSON helpers to eliminate duplicate code Jul 4, 2026
Copilot AI requested a review from pelikhan July 4, 2026 06:37
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category refactor
Risk 🟢 Low
Score 35 / 100
Action 📦 batch_review
Batch pr-batch:js-refactor

Score breakdown: Impact 15 (dedup reduces drift risk in multi-provider JSON helpers) + Urgency 10 + Quality 10 (no tests, draft state)

Extracts shared multi-provider JSON helpers from copilot_sdk_driver.cjs and sample driver into a single module. +70/-66 lines. Currently DRAFT — no reviews yet. Low-priority refactor, suitable for batched review.

Generated by 🔧 PR Triage Agent · 82.8 AIC · ⌖ 8.09 AIC · ⊞ 5.5K ·

@pelikhan pelikhan marked this pull request as ready for review July 4, 2026 08:02
Copilot AI review requested due to automatic review settings July 4, 2026 08:02

Copilot AI left a comment

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.

Pull request overview

This PR refactors Copilot SDK multi-provider parsing/validation so both the production driver and the Node sample driver share a single implementation, reducing the risk of logic drift when the GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON shape evolves.

Changes:

  • Added a shared CommonJS helper module for multi-provider JSON parsing and minimal shape validation.
  • Updated the production Copilot SDK driver to import (and re-export) parseMultiProviderJson from the shared module.
  • Updated the Node sample driver to import the shared helpers and re-export them to preserve its existing module.exports surface.
Show a summary per file
File Description
actions/setup/js/copilot_sdk_multi_provider.cjs New shared parsing/validation helpers for multi-provider JSON env var.
actions/setup/js/copilot_sdk_driver.cjs Removes duplicated helper implementations; imports/re-exports shared parser.
.github/drivers/copilot_sdk_driver_sample_node.cjs Removes duplicated helper implementations; imports shared helpers and re-exports them.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +33 to +35
* 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.
Comment on lines +44 to +48
function parseMultiProviderJson(value) {
if (!value) return null;
try {
const parsed = JSON.parse(value);
if (!parsed || typeof parsed !== "object") return null;
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. Test Quality Sentinel skipped.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #43329 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100).

@github-actions github-actions Bot mentioned this pull request Jul 4, 2026

@github-actions github-actions Bot left a comment

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.

Skills-Based Review 🧠

Applied /tdd and /codebase-design — commenting with suggestions, no blocking issues.

📋 Key Themes & Highlights

Key Themes

  • Missing test file: The new copilot_sdk_multi_provider.cjs module has no paired *.test.cjs, breaking the consistent testing pattern of this directory. As the single source of truth, it should be the most tested file in this PR.
  • Over-exported surface: isValidProviderConfig and isValidModelConfig are internal predicates that don't need to be part of the public API. Exporting them (and re-exporting from the sample driver) unnecessarily widens the contract.
  • Fragile relative path: The cross-directory ../../actions/setup/js/ import in the sample driver is invisible to future movers.

Positive Highlights

  • ✅ Excellent motivation: eliminates a real drift risk between production and sample drivers
  • ✅ Backward compatibility is well-handled — both drivers re-export parseMultiProviderJson so existing callers are unaffected
  • ✅ Good JSDoc on the shared function, including typed return value
  • ✅ The // @ts-check`` pragma is a nice touch for a .cjs file
  • ✅ Clean, minimal PR scope — only touches what needs to change

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 45.1 AIC · ⌖ 5.96 AIC · ⊞ 6.6K
Comment /matt to run again

isValidProviderConfig,
isValidModelConfig,
parseMultiProviderJson,
};

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.

[/tdd] No paired test file for the new shared module — this is the single source of truth for multi-provider validation, and it currently has zero test coverage.

💡 Suggested test file: copilot_sdk_multi_provider.test.cjs

Every other module in actions/setup/js/ has a paired *.test.cjs. A minimal suite should cover: null/empty/invalid-JSON inputs, missing or empty providers/models arrays, invalid provider shape (missing baseUrl), invalid model shape, and a valid round-trip that confirms the model string is trimmed.

@copilot please address this.


module.exports = {
isValidProviderConfig,
isValidModelConfig,

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.

[/codebase-design] isValidProviderConfig and isValidModelConfig are implementation details of parseMultiProviderJson but are exported publicly. Exporting them widens the module's surface unnecessarily and invites callers to rely on the internal validation predicates directly.

💡 Suggestion

Keep the two helpers private (unexported) unless there is a concrete caller outside this module that needs them. If they genuinely need to be public, document their intended use case. The sample driver already only imports all three — but its module.exports re-exports them too, compounding the surface widening.

@copilot please address this.

"use strict";

const fs = require("node:fs");
const { isValidProviderConfig, isValidModelConfig, parseMultiProviderJson } = require("../../actions/setup/js/copilot_sdk_multi_provider.cjs");

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.

[/codebase-design] The relative path ../../actions/setup/js/copilot_sdk_multi_provider.cjs is fragile — if either file moves, the link silently breaks at runtime with no compile-time signal.

💡 Suggestion

Consider documenting why the relative path is used (e.g. in a comment) so future movers know this dependency exists. Alternatively, if the repo ever adopts a workspace/package structure, a named package import would be more robust. At minimum, a comment like // shared with actions/setup/js — do not move without updating this path keeps the coupling visible.

@copilot please address this.

@github-actions github-actions Bot left a comment

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.

Request changes: 3 blocking issues in the new shared module

The refactor is sound — eliminating the duplicate is the right call. However, copilot_sdk_multi_provider.cjs introduces three correctness/maintainability issues that should be fixed before this is the authoritative source of truth for multi-provider config validation.

Blocking findings
  1. Empty strings pass isValidProviderConfig / isValidModelConfig (high) — { name: "", baseUrl: "" } passes validation and causes an opaque SDK error at connection time. Add .length > 0 guards on every string field.
  2. Silent catch { return null; } (high) — all error context is lost; process.env.GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON misconfiguration becomes invisible. Log the caught error to stderr before returning null.
  3. model: "" returned when field is absent (medium) — the implicit "" == 'not set' contract works today only because callers use || undefined. Update the return type to string | null and return null explicitly so future callers have an unambiguous signal.

Also note: existing review threads already flag the JSDoc accuracy on null semantics (line 35) and missing unit tests (line 48) — those should be addressed too.

🔎 Code quality review by PR Code Quality Reviewer · 87.3 AIC · ⌖ 6.69 AIC · ⊞ 5.4K
Comment /review to run again

* @returns {boolean}
*/
function isValidProviderConfig(p) {
return p && typeof p.name === "string" && typeof p.type === "string" && typeof p.baseUrl === "string";

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.

Empty strings pass validation in isValidProviderConfig and isValidModelConfig: a provider with { name: "", type: "", baseUrl: "" } passes the shape check and reaches the Copilot SDK, causing opaque connection or URL errors far from the validation site.

💡 Suggested fix
function isValidProviderConfig(p) {
  return (
    p &&
    typeof p.name === "string" && p.name.length > 0 &&
    typeof p.type === "string" && p.type.length > 0 &&
    typeof p.baseUrl === "string" && p.baseUrl.length > 0
  );
}

function isValidModelConfig(m) {
  return (
    m &&
    typeof m.id === "string" && m.id.length > 0 &&
    typeof m.provider === "string" && m.provider.length > 0
  );
}

Non-empty checks are cheap and surface misconfigured GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON at the validation boundary rather than as a cryptic SDK runtime failure.

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 {

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.

Silent catch destroys all diagnostic information: every error — malformed JSON, unexpected TypeError, future bugs in validators — collapses to null with no log output. This is now the single source of truth for multi-provider parsing; when it silently returns null, the caller emits only a generic 'invalid' error message and the root cause is invisible.

💡 Suggested fix
} catch (err) {
  // Log to stderr so operator errors (malformed JSON, wrong shape) are diagnosable
  process.stderr.write(
    `[copilot-sdk-multi-provider] failed to parse GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON: ${err}\n`
  );
  return null;
}

Alternatively, accept an optional logger callback so callers can control where the diagnostic goes — useful for test isolation.

// 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() : "";

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.

model: "" when the field is absent creates an implicit, fragile contract: the function returns model: "" when parsed.model is missing or not a string. The only documented return type is { model: string, ... } with no indication that "" means 'not set'. The production caller handles this via process.env.COPILOT_MODEL || multiProviderConfig.model || undefined — correctly, but only because "" is falsy. A future caller that checks model !== undefined or model !== null will silently use the wrong value.

💡 Suggested fix
// Return null (or omit the field) when model is not set
const model = typeof parsed.model === "string" && parsed.model.trim() ? parsed.model.trim() : null;
return { model, providers: parsed.providers, models: parsed.models };

And update the JSDoc return type:

 * `@returns` {{ model: string | null, providers: ..., models: ... } | null}

This makes the absence of a model field explicit rather than encoding it as an empty string.

@github-actions github-actions Bot left a comment

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.

Review: refactor — extract shared multi-provider JSON helpers

Good refactor — the duplication elimination is correct and the module boundary is sensible. Two issues need to be addressed before merge.

🔴 Blocking

  1. No tests for the new shared module (copilot_sdk_multi_provider.cjs)
    This module is now the single source of truth for multi-provider config validation affecting both drivers. Every other sibling copilot_sdk_*.cjs has a test file. A regression here silently breaks both the production and sample drivers. See inline comment on line 65.

  2. Inaccurate @returns {boolean} annotation on isValidProviderConfig / isValidModelConfig
    The // @ts-check`` directive is present, so TypeScript will surface this. When p is falsy the function returns `p` (e.g. `null`) not `false`. Fix with `!!p &&`. See inline comment on line 19.

🟡 Non-blocking

  1. Brittle cross-tree relative require in .github/drivers/
    ../../actions/setup/js/copilot_sdk_multi_provider.cjs will silently break if either directory moves. Consider importing through the production driver re-export or document the structural assumption. See inline comment on line 5 of the sample driver.
What's good
  • Identical logic correctly extracted with zero semantic change
  • Re-exports from both drivers preserve backward compatibility for all existing callers
  • JSDoc is thorough in the new shared module
  • Header comment clearly describes consumers and purpose

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 71.9 AIC · ⌖ 5.73 AIC · ⊞ 4.8K

* @returns {boolean}
*/
function isValidProviderConfig(p) {
return p && typeof p.name === "string" && typeof p.type === "string" && typeof p.baseUrl === "string";

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.

The @returns {boolean} JSDoc annotation is inaccurate: when p is falsy, p && ... short-circuits and returns p itself (e.g. null, undefined) rather than false.\n\nWith // @ts-check`` enabled, TypeScript will flag callers that depend on the strict boolean type. Though `.every()` coerces truthy/falsy correctly today, the imprecise annotation is a latent maintenance risk.\n\nConsider:\njs\nfunction isValidProviderConfig(p) {\n return !!p && typeof p.name === 'string' && typeof p.type === 'string' && typeof p.baseUrl === 'string';\n}\n\n\nSame applies to `isValidModelConfig`.\n\n@copilot please address this.

isValidProviderConfig,
isValidModelConfig,
parseMultiProviderJson,
};

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.

No test file exists for the new copilot_sdk_multi_provider.cjs module. The repository has 362 test files, and every other copilot_sdk_*.cjs sibling has coverage (e.g. copilot_sdk_driver.test.cjs). The extracted logic is the single source of truth for multi-provider config validation, so regressions here would silently affect both drivers.\n\nPlease add a copilot_sdk_multi_provider.test.cjs covering at least:\n- parseMultiProviderJson(undefined)null\n- Invalid JSON → null\n- Missing providers/modelsnull\n- Invalid provider shape → null\n- Valid input → correct { model, providers, models }\n\n@copilot please address this.

"use strict";

const fs = require("node:fs");
const { isValidProviderConfig, isValidModelConfig, parseMultiProviderJson } = require("../../actions/setup/js/copilot_sdk_multi_provider.cjs");

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.

The require() path ../../actions/setup/js/copilot_sdk_multi_provider.cjs is a brittle relative path from a .github/drivers/ file into actions/setup/js/. This will silently break if either directory is moved or if the sample driver is copied elsewhere.\n\nThe production driver in actions/setup/js/ uses a safe local ./copilot_sdk_multi_provider.cjs reference. Consider:\n1. Embedding the sample driver's helpers via a thin wrapper re-export in the production driver (already done for parseMultiProviderJson), and importing from the production driver instead; or\n2. Adding a path-integrity test that verifies the relative path resolves correctly.\n\nAt minimum, add a comment explaining the expected repo layout so a future directory refactor doesn't silently break this.\n\n@copilot please address this.

@pelikhan pelikhan merged commit 5854682 into main Jul 4, 2026
67 checks passed
@pelikhan pelikhan deleted the copilot/duplicate-code-fix branch July 4, 2026 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[duplicate-code] Duplicate Code: Copilot SDK multi-provider parsing in driver sample and runtime entrypoint

3 participants