From c6949302f61e5b07915db1c7dddecfb8225060dc Mon Sep 17 00:00:00 2001 From: Kinsey Durham Grace <74373246+kinseydurhamgrace@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:32:30 +0000 Subject: [PATCH 1/7] Improvements: --- cli/cmd/engine-cli/main.go | 33 ++++++++++--- cli/internal/runner/runner.go | 50 ++++++++++++++------ cli/internal/server/server.go | 31 ++++++++++++- docs/integration-guide.md | 23 ++++++++- src/client.ts | 87 +++++++++++++++++++++++++++++++++++ src/index.ts | 4 +- 6 files changed, 202 insertions(+), 26 deletions(-) diff --git a/cli/cmd/engine-cli/main.go b/cli/cmd/engine-cli/main.go index 73703dc..b49ed84 100644 --- a/cli/cmd/engine-cli/main.go +++ b/cli/cmd/engine-cli/main.go @@ -33,6 +33,11 @@ var ( commitLogin string commitEmail string assignmentID string + enableModelSelection bool + selectedEngine string + selectedModel string + defaultModel string + availableModels []string ) func main() { @@ -88,6 +93,11 @@ func init() { runCmd.Flags().StringVar(&commitLogin, "commit-login", "engine-cli-user", "Git author name for commits") runCmd.Flags().StringVar(&commitEmail, "commit-email", "engine-cli@users.noreply.github.com", "Git author email for commits") runCmd.Flags().StringVar(&assignmentID, "assignment-id", "", "Assignment ID to enable cross-run history persistence") + runCmd.Flags().BoolVar(&enableModelSelection, "enable-model-selection", false, "Enable the model selection feature flag in the job response") + runCmd.Flags().StringVar(&selectedEngine, "selected-engine", "", "Selected engine family for this job (for example: claude or codex)") + runCmd.Flags().StringVar(&selectedModel, "selected-model", "", "Selected model for this job") + runCmd.Flags().StringVar(&defaultModel, "default-model", "", "Default model for this engine") + runCmd.Flags().StringSliceVar(&availableModels, "available-model", nil, "Available model for this engine (repeatable)") _ = runCmd.MarkFlagRequired("repo") } @@ -149,6 +159,11 @@ func runEngine(cmd *cobra.Command, args []string) error { BranchName: branchName, CommitLogin: commitLogin, CommitEmail: commitEmail, + EnableModelSelection: enableModelSelection, + SelectedEngine: selectedEngine, + SelectedModel: selectedModel, + DefaultModel: defaultModel, + AvailableModels: availableModels, } prNumber := setup.PRNumber @@ -258,13 +273,17 @@ func runEngine(cmd *cobra.Command, args []string) error { } env := runner.Environment{ - JobID: jobID, - APIToken: githubToken, - APIURL: apiURL, - JobNonce: mockServer.Nonce(), - InferenceToken: githubToken, - InferenceURL: inferenceURL, - GitToken: githubToken, + JobID: jobID, + APIToken: githubToken, + APIURL: apiURL, + JobNonce: mockServer.Nonce(), + InferenceToken: githubToken, + InferenceURL: inferenceURL, + GitToken: githubToken, + SelectedEngine: selectedEngine, + SelectedModel: selectedModel, + DefaultModel: defaultModel, + AvailableModels: availableModels, } result := runner.Run(ctx, command, env, runner.Options{WorkingDir: workingDir}, runnerCallbacks) diff --git a/cli/internal/runner/runner.go b/cli/internal/runner/runner.go index 7597808..3a0af31 100644 --- a/cli/internal/runner/runner.go +++ b/cli/internal/runner/runner.go @@ -6,6 +6,7 @@ package runner import ( "bufio" "context" + "encoding/json" "fmt" "os" "os/exec" @@ -15,13 +16,17 @@ import ( // Environment contains the platform environment variables for the engine. type Environment struct { - JobID string - APIToken string - APIURL string - JobNonce string - InferenceToken string - InferenceURL string - GitToken string + JobID string + APIToken string + APIURL string + JobNonce string + InferenceToken string + InferenceURL string + GitToken string + SelectedEngine string + SelectedModel string + DefaultModel string + AvailableModels []string } // Callbacks contains optional callbacks for runner events. @@ -124,18 +129,37 @@ func buildEnv(env Environment, extra map[string]string) []string { // Add platform environment variables // Note: We use GITHUB_* prefix for consistency with GitHub platform conventions platformVars := map[string]string{ - "GITHUB_JOB_ID": env.JobID, - "GITHUB_JOB_NONCE": env.JobNonce, - "GITHUB_PLATFORM_API_TOKEN": env.APIToken, - "GITHUB_PLATFORM_API_URL": env.APIURL, - "GITHUB_INFERENCE_TOKEN": env.InferenceToken, - "GITHUB_GIT_TOKEN": env.GitToken, + "GITHUB_JOB_ID": env.JobID, + "GITHUB_JOB_NONCE": env.JobNonce, + "GITHUB_PLATFORM_API_TOKEN": env.APIToken, + "GITHUB_PLATFORM_API_URL": env.APIURL, + "GITHUB_INFERENCE_TOKEN": env.InferenceToken, + "GITHUB_GIT_TOKEN": env.GitToken, } if env.InferenceURL != "" { platformVars["GITHUB_INFERENCE_URL"] = env.InferenceURL } + if env.SelectedEngine != "" { + platformVars["GITHUB_SELECTED_ENGINE"] = env.SelectedEngine + } + + if env.SelectedModel != "" { + platformVars["GITHUB_SELECTED_MODEL"] = env.SelectedModel + } + + if env.DefaultModel != "" { + platformVars["GITHUB_DEFAULT_MODEL"] = env.DefaultModel + } + + if len(env.AvailableModels) > 0 { + // json.Marshal cannot fail for []string, but handle the error defensively. + if encoded, err := json.Marshal(env.AvailableModels); err == nil { + platformVars["GITHUB_AVAILABLE_MODELS"] = string(encoded) + } + } + for k, v := range platformVars { result = append(result, fmt.Sprintf("%s=%s", k, v)) } diff --git a/cli/internal/server/server.go b/cli/internal/server/server.go index cdd1d43..681eb88 100644 --- a/cli/internal/server/server.go +++ b/cli/internal/server/server.go @@ -31,6 +31,11 @@ type JobConfig struct { CommitLogin string CommitEmail string MCPProxyURL string + EnableModelSelection bool + SelectedEngine string + SelectedModel string + DefaultModel string + AvailableModels []string } // ProgressEvent represents a progress event received from an engine. @@ -129,8 +134,8 @@ func (s *MockPlatformServer) Stop(ctx context.Context) error { } var ( - getJobRegex = regexp.MustCompile(`^/agent/jobs/([^/]+)$`) - progressRegex = regexp.MustCompile(`^/agent/jobs/([^/]+)/progress$`) + getJobRegex = regexp.MustCompile(`^/agent/jobs/([^/]+)$`) + progressRegex = regexp.MustCompile(`^/agent/jobs/([^/]+)/progress$`) ) func (s *MockPlatformServer) handleRequest(w http.ResponseWriter, r *http.Request) { @@ -206,6 +211,28 @@ func (s *MockPlatformServer) handleGetJob(w http.ResponseWriter, r *http.Request response["mcp_proxy_url"] = s.jobConfig.MCPProxyURL } + if s.jobConfig.EnableModelSelection { + response["features"] = map[string]any{ + "model_selection": true, + } + + if s.jobConfig.SelectedEngine != "" { + response["selected_engine"] = s.jobConfig.SelectedEngine + } + + if s.jobConfig.SelectedModel != "" { + response["selected_model"] = s.jobConfig.SelectedModel + } + + if s.jobConfig.DefaultModel != "" { + response["default_model"] = s.jobConfig.DefaultModel + } + + if len(s.jobConfig.AvailableModels) > 0 { + response["available_models"] = s.jobConfig.AvailableModels + } + } + if s.callbacks.OnJobFetched != nil { s.callbacks.OnJobFetched() } diff --git a/docs/integration-guide.md b/docs/integration-guide.md index d9fb6a6..fe68409 100644 --- a/docs/integration-guide.md +++ b/docs/integration-guide.md @@ -94,6 +94,10 @@ The platform injects these environment variables into the engine process at runt | `GITHUB_INFERENCE_TOKEN` | Yes | Token used by your inference client / SDK for model calls. | | `GITHUB_INFERENCE_URL` | Yes | Base URL for the inference API (e.g. Copilot API). Use this along with `GITHUB_INFERENCE_TOKEN` to make LLM inference calls. | | `GITHUB_GIT_TOKEN` | Yes | Token used for authenticated `git clone` / `git push`. | +| `GITHUB_SELECTED_ENGINE` | No | Engine family selected for this run (e.g. `claude`, `codex`). Only set when model selection is enabled. | +| `GITHUB_SELECTED_MODEL` | No | Model selected by the platform for this run. Only set when model selection is enabled. | +| `GITHUB_DEFAULT_MODEL` | No | Default model for the selected engine. Only set when model selection is enabled. | +| `GITHUB_AVAILABLE_MODELS` | No | JSON array of models the engine can choose from (e.g. `["claude-sonnet-4.5","claude-opus-4.1"]`). Only set when model selection is enabled. | ## Step 2: Fetch Job Details @@ -143,6 +147,13 @@ Headers: "branch_name": "copilot/fix-123", "commit_login": "copilot-bot", "commit_email": "copilot-bot@users.noreply.github.com", + "features": { + "model_selection": true + }, + "selected_engine": "claude", + "selected_model": "claude-sonnet-4.5", + "default_model": "claude-sonnet-4.5", + "available_models": ["claude-sonnet-4.5", "claude-opus-4.1"], "mcp_proxy_url": "http://127.0.0.1:2301" } ``` @@ -157,6 +168,11 @@ Headers: | `branch_name` | Branch to checkout or create. | | `commit_login` | Git author name for commits. | | `commit_email` | Git author email for commits. | +| `features` | Optional feature flags. Currently supports `model_selection` (boolean). | +| `selected_engine` | Engine family selected for this run (e.g. `claude` or `codex`). Present when `features.model_selection` is `true`. | +| `selected_model` | Model selected by the platform for this run. Present when `features.model_selection` is `true`. | +| `default_model` | Default model for the selected engine. Present when `features.model_selection` is `true`. | +| `available_models` | List of models the engine can choose from. Present when `features.model_selection` is `true`. | | `mcp_proxy_url` | Optional URL of the MCP proxy server. When present, use it to discover user-provided MCP servers. See [User-Provided MCP Servers](#user-provided-mcp-servers). | Use `GITHUB_INFERENCE_TOKEN` for model calls and `GITHUB_GIT_TOKEN` for git operations; those are bootstrap action inputs, not job response fields. @@ -825,7 +841,7 @@ flowchart LR ``` ```typescript -import { PlatformClient, cloneRepo, finalizeChanges } from "@github/copilot-engine-sdk"; +import { PlatformClient, cloneRepo, finalizeChanges, resolveSelectedModel } from "@github/copilot-engine-sdk"; import { CopilotClient } from "@github/copilot-sdk"; async function main() { @@ -862,6 +878,9 @@ async function main() { // 5. Build system message based on action type const systemMessage = buildSystemMessage(job.action, job); + const model = resolveSelectedModel(job, { + fallbackModel: "claude-sonnet-4.5", + }) ?? "claude-sonnet-4.5"; // 6. Run your agentic loop with your inference client const client = new CopilotClient({ @@ -872,7 +891,7 @@ async function main() { const mcpServerPath = require.resolve("@github/copilot-engine-sdk/mcp-server"); const session = await client.createSession({ - model: "claude-sonnet-4.5", + model, systemMessage: { content: systemMessage }, mcpServers: { "engine-tools": { diff --git a/src/client.ts b/src/client.ts index 79010fb..37e74cb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -485,6 +485,93 @@ export interface JobDetails { commit_login: string; commit_email: string; mcp_proxy_url?: string; + /** Engine family selected for this run (e.g., "claude" or "codex"). Present when model selection is enabled. */ + selected_engine?: string; + /** Model selected by the platform for this run. Present when model selection is enabled. */ + selected_model?: string; + /** Default model for the selected engine. Present when model selection is enabled. */ + default_model?: string; + /** Models the engine can choose from. Present when model selection is enabled. */ + available_models?: string[]; + /** Feature flags enabled for this job. */ + features?: { + /** Whether the platform has enabled model selection for this job. */ + model_selection?: boolean; + }; +} + +/** + * Check whether the platform has enabled model selection for this job. + * + * When this returns `false`, engines should use their own hardcoded model. + */ +export function isModelSelectionEnabled(job: Pick): boolean { + return job.features?.model_selection === true; +} + +/** + * Resolve which model an engine should use for a job. + * + * Returns `undefined` when model selection is not enabled for the job + * (i.e. `features.model_selection` is not `true`), allowing engines that + * do not support model selection to ignore it entirely. + * + * When enabled, the selection order is: + * 1) caller preferred model + * 2) model selected by platform (`selected_model`) + * 3) platform-provided engine default (`default_model`) + * 4) caller fallback model + * + * If `available_models` is present the resolved model must appear in that + * list. When no candidate matches, the first available model is returned + * and a warning is logged if `selected_model` was set but missing from the + * list (indicates a platform misconfiguration). + */ +/** Options for {@link resolveSelectedModel}. */ +export interface ResolveSelectedModelOptions { + /** Model the engine prefers to use, checked first. */ + preferredModel?: string; + /** Model to fall back to when no platform-provided candidate matches. */ + fallbackModel?: string; +} + +export function resolveSelectedModel( + job: Pick, + options?: ResolveSelectedModelOptions, +): string | undefined { + // Model selection must be explicitly enabled via feature flag + if (!job.features?.model_selection) { + return undefined; + } + + const availableModels = job.available_models?.filter((model) => model.trim().length > 0) ?? []; + + const candidates = [ + options?.preferredModel, + job.selected_model, + job.default_model, + options?.fallbackModel, + ].filter((model): model is string => Boolean(model && model.trim().length > 0)); + + if (availableModels.length === 0) { + return candidates[0]; + } + + for (const candidate of candidates) { + if (availableModels.includes(candidate)) { + return candidate; + } + } + + // Warn when the platform-selected model is not in the available list + if (job.selected_model && !availableModels.includes(job.selected_model)) { + console.warn( + `resolveSelectedModel: selected_model "${job.selected_model}" is not in available_models [${availableModels.join(", ")}]. ` + + `Falling back to "${availableModels[0]}".` + ); + } + + return availableModels[0]; } /** diff --git a/src/index.ts b/src/index.ts index d586e2e..f827fb8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -91,9 +91,9 @@ export type { // Platform Client // ============================================================================= -export { PlatformClient } from "./client.js"; +export { PlatformClient, resolveSelectedModel, isModelSelectionEnabled } from "./client.js"; -export type { PlatformClientConfig, ProgressPayload, ProgressRecord, ProgressResponse, SendResult, JobDetails, ProblemStatement } from "./client.js"; +export type { PlatformClientConfig, ProgressPayload, ProgressRecord, ProgressResponse, SendResult, JobDetails, ProblemStatement, ResolveSelectedModelOptions } from "./client.js"; // ============================================================================= // MCP Server From 13ba8b0afaa0cf5a77e34a07bae44557ba2afc78 Mon Sep 17 00:00:00 2001 From: Kinsey Durham Grace <74373246+kinseydurhamgrace@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:05:19 +0000 Subject: [PATCH 2/7] address PR comments --- cli/cmd/engine-cli/main.go | 25 ++++++++++++++----------- src/client.ts | 9 ++++++--- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/cli/cmd/engine-cli/main.go b/cli/cmd/engine-cli/main.go index b49ed84..c5b5f17 100644 --- a/cli/cmd/engine-cli/main.go +++ b/cli/cmd/engine-cli/main.go @@ -273,17 +273,20 @@ func runEngine(cmd *cobra.Command, args []string) error { } env := runner.Environment{ - JobID: jobID, - APIToken: githubToken, - APIURL: apiURL, - JobNonce: mockServer.Nonce(), - InferenceToken: githubToken, - InferenceURL: inferenceURL, - GitToken: githubToken, - SelectedEngine: selectedEngine, - SelectedModel: selectedModel, - DefaultModel: defaultModel, - AvailableModels: availableModels, + JobID: jobID, + APIToken: githubToken, + APIURL: apiURL, + JobNonce: mockServer.Nonce(), + InferenceToken: githubToken, + InferenceURL: inferenceURL, + GitToken: githubToken, + } + + if enableModelSelection { + env.SelectedEngine = selectedEngine + env.SelectedModel = selectedModel + env.DefaultModel = defaultModel + env.AvailableModels = availableModels } result := runner.Run(ctx, command, env, runner.Options{WorkingDir: workingDir}, runnerCallbacks) diff --git a/src/client.ts b/src/client.ts index 37e74cb..abf634c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -540,18 +540,21 @@ export function resolveSelectedModel( options?: ResolveSelectedModelOptions, ): string | undefined { // Model selection must be explicitly enabled via feature flag - if (!job.features?.model_selection) { + if (job.features?.model_selection !== true) { return undefined; } - const availableModels = job.available_models?.filter((model) => model.trim().length > 0) ?? []; + const availableModels = job.available_models + ?.map((model) => model.trim()) + .filter((model) => model.length > 0) ?? []; const candidates = [ options?.preferredModel, job.selected_model, job.default_model, options?.fallbackModel, - ].filter((model): model is string => Boolean(model && model.trim().length > 0)); + ].map((model) => model?.trim()) + .filter((model): model is string => Boolean(model && model.length > 0)); if (availableModels.length === 0) { return candidates[0]; From 7a64a2f157d76bc2ed039382a8647dbb1d5e8cc3 Mon Sep 17 00:00:00 2001 From: Kinsey Durham Grace <74373246+kinseydurhamgrace@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:09:21 +0000 Subject: [PATCH 3/7] support model vendor --- cli/cmd/engine-cli/main.go | 4 ++++ cli/internal/runner/runner.go | 5 +++++ cli/internal/server/server.go | 5 +++++ docs/integration-guide.md | 3 +++ src/client.ts | 2 ++ 5 files changed, 19 insertions(+) diff --git a/cli/cmd/engine-cli/main.go b/cli/cmd/engine-cli/main.go index c5b5f17..78490ef 100644 --- a/cli/cmd/engine-cli/main.go +++ b/cli/cmd/engine-cli/main.go @@ -38,6 +38,7 @@ var ( selectedModel string defaultModel string availableModels []string + modelVendor string ) func main() { @@ -98,6 +99,7 @@ func init() { runCmd.Flags().StringVar(&selectedModel, "selected-model", "", "Selected model for this job") runCmd.Flags().StringVar(&defaultModel, "default-model", "", "Default model for this engine") runCmd.Flags().StringSliceVar(&availableModels, "available-model", nil, "Available model for this engine (repeatable)") + runCmd.Flags().StringVar(&modelVendor, "model-vendor", "", "Model vendor for filtering (e.g. Anthropic, OpenAI)") _ = runCmd.MarkFlagRequired("repo") } @@ -164,6 +166,7 @@ func runEngine(cmd *cobra.Command, args []string) error { SelectedModel: selectedModel, DefaultModel: defaultModel, AvailableModels: availableModels, + ModelVendor: modelVendor, } prNumber := setup.PRNumber @@ -287,6 +290,7 @@ func runEngine(cmd *cobra.Command, args []string) error { env.SelectedModel = selectedModel env.DefaultModel = defaultModel env.AvailableModels = availableModels + env.ModelVendor = modelVendor } result := runner.Run(ctx, command, env, runner.Options{WorkingDir: workingDir}, runnerCallbacks) diff --git a/cli/internal/runner/runner.go b/cli/internal/runner/runner.go index 3a0af31..7ffeae9 100644 --- a/cli/internal/runner/runner.go +++ b/cli/internal/runner/runner.go @@ -27,6 +27,7 @@ type Environment struct { SelectedModel string DefaultModel string AvailableModels []string + ModelVendor string } // Callbacks contains optional callbacks for runner events. @@ -160,6 +161,10 @@ func buildEnv(env Environment, extra map[string]string) []string { } } + if env.ModelVendor != "" { + platformVars["GITHUB_MODEL_VENDOR"] = env.ModelVendor + } + for k, v := range platformVars { result = append(result, fmt.Sprintf("%s=%s", k, v)) } diff --git a/cli/internal/server/server.go b/cli/internal/server/server.go index 681eb88..b802806 100644 --- a/cli/internal/server/server.go +++ b/cli/internal/server/server.go @@ -36,6 +36,7 @@ type JobConfig struct { SelectedModel string DefaultModel string AvailableModels []string + ModelVendor string } // ProgressEvent represents a progress event received from an engine. @@ -231,6 +232,10 @@ func (s *MockPlatformServer) handleGetJob(w http.ResponseWriter, r *http.Request if len(s.jobConfig.AvailableModels) > 0 { response["available_models"] = s.jobConfig.AvailableModels } + + if s.jobConfig.ModelVendor != "" { + response["model_vendor"] = s.jobConfig.ModelVendor + } } if s.callbacks.OnJobFetched != nil { diff --git a/docs/integration-guide.md b/docs/integration-guide.md index fe68409..3ff8736 100644 --- a/docs/integration-guide.md +++ b/docs/integration-guide.md @@ -98,6 +98,7 @@ The platform injects these environment variables into the engine process at runt | `GITHUB_SELECTED_MODEL` | No | Model selected by the platform for this run. Only set when model selection is enabled. | | `GITHUB_DEFAULT_MODEL` | No | Default model for the selected engine. Only set when model selection is enabled. | | `GITHUB_AVAILABLE_MODELS` | No | JSON array of models the engine can choose from (e.g. `["claude-sonnet-4.5","claude-opus-4.1"]`). Only set when model selection is enabled. | +| `GITHUB_MODEL_VENDOR` | No | Model vendor for filtering (e.g. `Anthropic`, `OpenAI`). Only set when model selection is enabled. | ## Step 2: Fetch Job Details @@ -154,6 +155,7 @@ Headers: "selected_model": "claude-sonnet-4.5", "default_model": "claude-sonnet-4.5", "available_models": ["claude-sonnet-4.5", "claude-opus-4.1"], + "model_vendor": "Anthropic", "mcp_proxy_url": "http://127.0.0.1:2301" } ``` @@ -173,6 +175,7 @@ Headers: | `selected_model` | Model selected by the platform for this run. Present when `features.model_selection` is `true`. | | `default_model` | Default model for the selected engine. Present when `features.model_selection` is `true`. | | `available_models` | List of models the engine can choose from. Present when `features.model_selection` is `true`. | +| `model_vendor` | Model vendor for filtering (e.g. `Anthropic`, `OpenAI`). Present when `features.model_selection` is `true`. | | `mcp_proxy_url` | Optional URL of the MCP proxy server. When present, use it to discover user-provided MCP servers. See [User-Provided MCP Servers](#user-provided-mcp-servers). | Use `GITHUB_INFERENCE_TOKEN` for model calls and `GITHUB_GIT_TOKEN` for git operations; those are bootstrap action inputs, not job response fields. diff --git a/src/client.ts b/src/client.ts index abf634c..2ba14f0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -493,6 +493,8 @@ export interface JobDetails { default_model?: string; /** Models the engine can choose from. Present when model selection is enabled. */ available_models?: string[]; + /** Model vendor for filtering (e.g. "Anthropic", "OpenAI"). Present when model selection is enabled. */ + model_vendor?: string; /** Feature flags enabled for this job. */ features?: { /** Whether the platform has enabled model selection for this job. */ From 8726da3497bdd5b8573d64d736502531a0f1fcda Mon Sep 17 00:00:00 2001 From: Kinsey Durham Grace <74373246+kinseydurhamgrace@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:23:12 +0000 Subject: [PATCH 4/7] remove selectedEngine --- cli/cmd/engine-cli/main.go | 4 ---- cli/internal/runner/runner.go | 5 ----- cli/internal/server/server.go | 5 ----- docs/integration-guide.md | 3 --- src/client.ts | 2 -- 5 files changed, 19 deletions(-) diff --git a/cli/cmd/engine-cli/main.go b/cli/cmd/engine-cli/main.go index 78490ef..f27db4a 100644 --- a/cli/cmd/engine-cli/main.go +++ b/cli/cmd/engine-cli/main.go @@ -34,7 +34,6 @@ var ( commitEmail string assignmentID string enableModelSelection bool - selectedEngine string selectedModel string defaultModel string availableModels []string @@ -95,7 +94,6 @@ func init() { runCmd.Flags().StringVar(&commitEmail, "commit-email", "engine-cli@users.noreply.github.com", "Git author email for commits") runCmd.Flags().StringVar(&assignmentID, "assignment-id", "", "Assignment ID to enable cross-run history persistence") runCmd.Flags().BoolVar(&enableModelSelection, "enable-model-selection", false, "Enable the model selection feature flag in the job response") - runCmd.Flags().StringVar(&selectedEngine, "selected-engine", "", "Selected engine family for this job (for example: claude or codex)") runCmd.Flags().StringVar(&selectedModel, "selected-model", "", "Selected model for this job") runCmd.Flags().StringVar(&defaultModel, "default-model", "", "Default model for this engine") runCmd.Flags().StringSliceVar(&availableModels, "available-model", nil, "Available model for this engine (repeatable)") @@ -162,7 +160,6 @@ func runEngine(cmd *cobra.Command, args []string) error { CommitLogin: commitLogin, CommitEmail: commitEmail, EnableModelSelection: enableModelSelection, - SelectedEngine: selectedEngine, SelectedModel: selectedModel, DefaultModel: defaultModel, AvailableModels: availableModels, @@ -286,7 +283,6 @@ func runEngine(cmd *cobra.Command, args []string) error { } if enableModelSelection { - env.SelectedEngine = selectedEngine env.SelectedModel = selectedModel env.DefaultModel = defaultModel env.AvailableModels = availableModels diff --git a/cli/internal/runner/runner.go b/cli/internal/runner/runner.go index 7ffeae9..34a2988 100644 --- a/cli/internal/runner/runner.go +++ b/cli/internal/runner/runner.go @@ -23,7 +23,6 @@ type Environment struct { InferenceToken string InferenceURL string GitToken string - SelectedEngine string SelectedModel string DefaultModel string AvailableModels []string @@ -142,10 +141,6 @@ func buildEnv(env Environment, extra map[string]string) []string { platformVars["GITHUB_INFERENCE_URL"] = env.InferenceURL } - if env.SelectedEngine != "" { - platformVars["GITHUB_SELECTED_ENGINE"] = env.SelectedEngine - } - if env.SelectedModel != "" { platformVars["GITHUB_SELECTED_MODEL"] = env.SelectedModel } diff --git a/cli/internal/server/server.go b/cli/internal/server/server.go index b802806..c1d819d 100644 --- a/cli/internal/server/server.go +++ b/cli/internal/server/server.go @@ -32,7 +32,6 @@ type JobConfig struct { CommitEmail string MCPProxyURL string EnableModelSelection bool - SelectedEngine string SelectedModel string DefaultModel string AvailableModels []string @@ -217,10 +216,6 @@ func (s *MockPlatformServer) handleGetJob(w http.ResponseWriter, r *http.Request "model_selection": true, } - if s.jobConfig.SelectedEngine != "" { - response["selected_engine"] = s.jobConfig.SelectedEngine - } - if s.jobConfig.SelectedModel != "" { response["selected_model"] = s.jobConfig.SelectedModel } diff --git a/docs/integration-guide.md b/docs/integration-guide.md index 3ff8736..fcde600 100644 --- a/docs/integration-guide.md +++ b/docs/integration-guide.md @@ -94,7 +94,6 @@ The platform injects these environment variables into the engine process at runt | `GITHUB_INFERENCE_TOKEN` | Yes | Token used by your inference client / SDK for model calls. | | `GITHUB_INFERENCE_URL` | Yes | Base URL for the inference API (e.g. Copilot API). Use this along with `GITHUB_INFERENCE_TOKEN` to make LLM inference calls. | | `GITHUB_GIT_TOKEN` | Yes | Token used for authenticated `git clone` / `git push`. | -| `GITHUB_SELECTED_ENGINE` | No | Engine family selected for this run (e.g. `claude`, `codex`). Only set when model selection is enabled. | | `GITHUB_SELECTED_MODEL` | No | Model selected by the platform for this run. Only set when model selection is enabled. | | `GITHUB_DEFAULT_MODEL` | No | Default model for the selected engine. Only set when model selection is enabled. | | `GITHUB_AVAILABLE_MODELS` | No | JSON array of models the engine can choose from (e.g. `["claude-sonnet-4.5","claude-opus-4.1"]`). Only set when model selection is enabled. | @@ -151,7 +150,6 @@ Headers: "features": { "model_selection": true }, - "selected_engine": "claude", "selected_model": "claude-sonnet-4.5", "default_model": "claude-sonnet-4.5", "available_models": ["claude-sonnet-4.5", "claude-opus-4.1"], @@ -171,7 +169,6 @@ Headers: | `commit_login` | Git author name for commits. | | `commit_email` | Git author email for commits. | | `features` | Optional feature flags. Currently supports `model_selection` (boolean). | -| `selected_engine` | Engine family selected for this run (e.g. `claude` or `codex`). Present when `features.model_selection` is `true`. | | `selected_model` | Model selected by the platform for this run. Present when `features.model_selection` is `true`. | | `default_model` | Default model for the selected engine. Present when `features.model_selection` is `true`. | | `available_models` | List of models the engine can choose from. Present when `features.model_selection` is `true`. | diff --git a/src/client.ts b/src/client.ts index 2ba14f0..e798582 100644 --- a/src/client.ts +++ b/src/client.ts @@ -485,8 +485,6 @@ export interface JobDetails { commit_login: string; commit_email: string; mcp_proxy_url?: string; - /** Engine family selected for this run (e.g., "claude" or "codex"). Present when model selection is enabled. */ - selected_engine?: string; /** Model selected by the platform for this run. Present when model selection is enabled. */ selected_model?: string; /** Default model for the selected engine. Present when model selection is enabled. */ From 3f0265e93acf3f3195f5f7a433d0d4a2a16398df Mon Sep 17 00:00:00 2001 From: Kinsey Durham Grace <74373246+kinseydurhamgrace@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:37:53 +0000 Subject: [PATCH 5/7] Add documentation --- docs/integration-guide.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/integration-guide.md b/docs/integration-guide.md index fcde600..14faa92 100644 --- a/docs/integration-guide.md +++ b/docs/integration-guide.md @@ -77,6 +77,10 @@ author: 'Your Name' # The fully qualified command to run the engine. # The platform executes this command directly — no implicit runtime setup. entrypoint: 'node --enable-source-maps dist/index.js' + +# Optional: Specify the model vendor for model selection (e.g. 'Anthropic', 'OpenAI'). +# When set, the platform uses this to determine the available models for the engine. +vendor: 'Anthropic' ``` > **Note:** This is not a GitHub Action. The platform reads `entrypoint` from `engine.yaml` and runs it directly. All paths in the entrypoint are resolved relative to the engine's root directory. @@ -97,7 +101,7 @@ The platform injects these environment variables into the engine process at runt | `GITHUB_SELECTED_MODEL` | No | Model selected by the platform for this run. Only set when model selection is enabled. | | `GITHUB_DEFAULT_MODEL` | No | Default model for the selected engine. Only set when model selection is enabled. | | `GITHUB_AVAILABLE_MODELS` | No | JSON array of models the engine can choose from (e.g. `["claude-sonnet-4.5","claude-opus-4.1"]`). Only set when model selection is enabled. | -| `GITHUB_MODEL_VENDOR` | No | Model vendor for filtering (e.g. `Anthropic`, `OpenAI`). Only set when model selection is enabled. | +| `GITHUB_MODEL_VENDOR` | No | Model vendor as defined by the `vendor` field in `engine.yaml` (e.g. `Anthropic`, `OpenAI`). Only set when model selection is enabled. | ## Step 2: Fetch Job Details @@ -172,7 +176,7 @@ Headers: | `selected_model` | Model selected by the platform for this run. Present when `features.model_selection` is `true`. | | `default_model` | Default model for the selected engine. Present when `features.model_selection` is `true`. | | `available_models` | List of models the engine can choose from. Present when `features.model_selection` is `true`. | -| `model_vendor` | Model vendor for filtering (e.g. `Anthropic`, `OpenAI`). Present when `features.model_selection` is `true`. | +| `model_vendor` | Model vendor as defined by the `vendor` field in `engine.yaml` (e.g. `Anthropic`, `OpenAI`). Present when `features.model_selection` is `true`. | | `mcp_proxy_url` | Optional URL of the MCP proxy server. When present, use it to discover user-provided MCP servers. See [User-Provided MCP Servers](#user-provided-mcp-servers). | Use `GITHUB_INFERENCE_TOKEN` for model calls and `GITHUB_GIT_TOKEN` for git operations; those are bootstrap action inputs, not job response fields. From e72426b404da73a3024eed04a93b7b31d163776e Mon Sep 17 00:00:00 2001 From: Kinsey Durham Grace <74373246+kinseydurhamgrace@users.noreply.github.com> Date: Thu, 2 Apr 2026 21:06:38 +0000 Subject: [PATCH 6/7] change to support multiple vendors --- cli/cmd/engine-cli/main.go | 8 ++++---- cli/internal/runner/runner.go | 8 +++++--- cli/internal/server/server.go | 6 +++--- docs/integration-guide.md | 14 ++++++++------ src/client.ts | 4 ++-- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/cli/cmd/engine-cli/main.go b/cli/cmd/engine-cli/main.go index f27db4a..8eb5dde 100644 --- a/cli/cmd/engine-cli/main.go +++ b/cli/cmd/engine-cli/main.go @@ -37,7 +37,7 @@ var ( selectedModel string defaultModel string availableModels []string - modelVendor string + modelVendors []string ) func main() { @@ -97,7 +97,7 @@ func init() { runCmd.Flags().StringVar(&selectedModel, "selected-model", "", "Selected model for this job") runCmd.Flags().StringVar(&defaultModel, "default-model", "", "Default model for this engine") runCmd.Flags().StringSliceVar(&availableModels, "available-model", nil, "Available model for this engine (repeatable)") - runCmd.Flags().StringVar(&modelVendor, "model-vendor", "", "Model vendor for filtering (e.g. Anthropic, OpenAI)") + runCmd.Flags().StringSliceVar(&modelVendors, "model-vendor", nil, "Model vendor for filtering (repeatable, e.g. Anthropic, OpenAI)") _ = runCmd.MarkFlagRequired("repo") } @@ -163,7 +163,7 @@ func runEngine(cmd *cobra.Command, args []string) error { SelectedModel: selectedModel, DefaultModel: defaultModel, AvailableModels: availableModels, - ModelVendor: modelVendor, + ModelVendors: modelVendors, } prNumber := setup.PRNumber @@ -286,7 +286,7 @@ func runEngine(cmd *cobra.Command, args []string) error { env.SelectedModel = selectedModel env.DefaultModel = defaultModel env.AvailableModels = availableModels - env.ModelVendor = modelVendor + env.ModelVendors = modelVendors } result := runner.Run(ctx, command, env, runner.Options{WorkingDir: workingDir}, runnerCallbacks) diff --git a/cli/internal/runner/runner.go b/cli/internal/runner/runner.go index 34a2988..f7b85ef 100644 --- a/cli/internal/runner/runner.go +++ b/cli/internal/runner/runner.go @@ -26,7 +26,7 @@ type Environment struct { SelectedModel string DefaultModel string AvailableModels []string - ModelVendor string + ModelVendors []string } // Callbacks contains optional callbacks for runner events. @@ -156,8 +156,10 @@ func buildEnv(env Environment, extra map[string]string) []string { } } - if env.ModelVendor != "" { - platformVars["GITHUB_MODEL_VENDOR"] = env.ModelVendor + if len(env.ModelVendors) > 0 { + if encoded, err := json.Marshal(env.ModelVendors); err == nil { + platformVars["GITHUB_MODEL_VENDORS"] = string(encoded) + } } for k, v := range platformVars { diff --git a/cli/internal/server/server.go b/cli/internal/server/server.go index c1d819d..0ccc126 100644 --- a/cli/internal/server/server.go +++ b/cli/internal/server/server.go @@ -35,7 +35,7 @@ type JobConfig struct { SelectedModel string DefaultModel string AvailableModels []string - ModelVendor string + ModelVendors []string } // ProgressEvent represents a progress event received from an engine. @@ -228,8 +228,8 @@ func (s *MockPlatformServer) handleGetJob(w http.ResponseWriter, r *http.Request response["available_models"] = s.jobConfig.AvailableModels } - if s.jobConfig.ModelVendor != "" { - response["model_vendor"] = s.jobConfig.ModelVendor + if len(s.jobConfig.ModelVendors) > 0 { + response["model_vendors"] = s.jobConfig.ModelVendors } } diff --git a/docs/integration-guide.md b/docs/integration-guide.md index 14faa92..cc4d383 100644 --- a/docs/integration-guide.md +++ b/docs/integration-guide.md @@ -78,9 +78,11 @@ author: 'Your Name' # The platform executes this command directly — no implicit runtime setup. entrypoint: 'node --enable-source-maps dist/index.js' -# Optional: Specify the model vendor for model selection (e.g. 'Anthropic', 'OpenAI'). -# When set, the platform uses this to determine the available models for the engine. -vendor: 'Anthropic' +# Optional: Specify the model vendors for model selection (e.g. 'Anthropic', 'OpenAI'). +# When set, the platform uses these to determine the available models for the engine. +vendors: + - 'Anthropic' + - 'OpenAI' ``` > **Note:** This is not a GitHub Action. The platform reads `entrypoint` from `engine.yaml` and runs it directly. All paths in the entrypoint are resolved relative to the engine's root directory. @@ -101,7 +103,7 @@ The platform injects these environment variables into the engine process at runt | `GITHUB_SELECTED_MODEL` | No | Model selected by the platform for this run. Only set when model selection is enabled. | | `GITHUB_DEFAULT_MODEL` | No | Default model for the selected engine. Only set when model selection is enabled. | | `GITHUB_AVAILABLE_MODELS` | No | JSON array of models the engine can choose from (e.g. `["claude-sonnet-4.5","claude-opus-4.1"]`). Only set when model selection is enabled. | -| `GITHUB_MODEL_VENDOR` | No | Model vendor as defined by the `vendor` field in `engine.yaml` (e.g. `Anthropic`, `OpenAI`). Only set when model selection is enabled. | +| `GITHUB_MODEL_VENDORS` | No | JSON array of model vendors as defined by the `vendors` field in `engine.yaml` (e.g. `["Anthropic","OpenAI"]`). Only set when model selection is enabled. | ## Step 2: Fetch Job Details @@ -157,7 +159,7 @@ Headers: "selected_model": "claude-sonnet-4.5", "default_model": "claude-sonnet-4.5", "available_models": ["claude-sonnet-4.5", "claude-opus-4.1"], - "model_vendor": "Anthropic", + "model_vendors": ["Anthropic"], "mcp_proxy_url": "http://127.0.0.1:2301" } ``` @@ -176,7 +178,7 @@ Headers: | `selected_model` | Model selected by the platform for this run. Present when `features.model_selection` is `true`. | | `default_model` | Default model for the selected engine. Present when `features.model_selection` is `true`. | | `available_models` | List of models the engine can choose from. Present when `features.model_selection` is `true`. | -| `model_vendor` | Model vendor as defined by the `vendor` field in `engine.yaml` (e.g. `Anthropic`, `OpenAI`). Present when `features.model_selection` is `true`. | +| `model_vendors` | List of model vendors as defined by the `vendors` field in `engine.yaml` (e.g. `["Anthropic", "OpenAI"]`). Present when `features.model_selection` is `true`. | | `mcp_proxy_url` | Optional URL of the MCP proxy server. When present, use it to discover user-provided MCP servers. See [User-Provided MCP Servers](#user-provided-mcp-servers). | Use `GITHUB_INFERENCE_TOKEN` for model calls and `GITHUB_GIT_TOKEN` for git operations; those are bootstrap action inputs, not job response fields. diff --git a/src/client.ts b/src/client.ts index e798582..0401170 100644 --- a/src/client.ts +++ b/src/client.ts @@ -491,8 +491,8 @@ export interface JobDetails { default_model?: string; /** Models the engine can choose from. Present when model selection is enabled. */ available_models?: string[]; - /** Model vendor for filtering (e.g. "Anthropic", "OpenAI"). Present when model selection is enabled. */ - model_vendor?: string; + /** Model vendors for filtering (e.g. ["Anthropic", "OpenAI"]). Present when model selection is enabled. */ + model_vendors?: string[]; /** Feature flags enabled for this job. */ features?: { /** Whether the platform has enabled model selection for this job. */ From 7147ce35200728faa265d9efd0592bdb25c2fe99 Mon Sep 17 00:00:00 2001 From: Kinsey Durham Grace <74373246+kinseydurhamgrace@users.noreply.github.com> Date: Fri, 3 Apr 2026 04:03:18 +0000 Subject: [PATCH 7/7] fix trim bug --- src/client.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index 0401170..98a0642 100644 --- a/src/client.ts +++ b/src/client.ts @@ -567,9 +567,10 @@ export function resolveSelectedModel( } // Warn when the platform-selected model is not in the available list - if (job.selected_model && !availableModels.includes(job.selected_model)) { + const trimmedSelectedModel = job.selected_model?.trim(); + if (trimmedSelectedModel && !availableModels.includes(trimmedSelectedModel)) { console.warn( - `resolveSelectedModel: selected_model "${job.selected_model}" is not in available_models [${availableModels.join(", ")}]. ` + + `resolveSelectedModel: selected_model "${trimmedSelectedModel}" is not in available_models [${availableModels.join(", ")}]. ` + `Falling back to "${availableModels[0]}".` ); }