GetExecutionSteps returns the GitHub Actions steps for executing the Pi CLI. The prompt is piped to Pi via stdin; streaming JSON events are written to PiStreamingLogFile for post-run analysis and step summary rendering.
(workflowData *WorkflowData, logFile string)
| 288 | // The prompt is piped to Pi via stdin; streaming JSON events are written to |
| 289 | // PiStreamingLogFile for post-run analysis and step summary rendering. |
| 290 | func (e *PiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile string) []GitHubActionStep { |
| 291 | piLog.Printf("Generating execution steps for Pi engine: workflow=%s, firewall=%v", |
| 292 | workflowData.Name, isFirewallEnabled(workflowData)) |
| 293 | |
| 294 | commandName := "pi" |
| 295 | if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" { |
| 296 | commandName = workflowData.EngineConfig.Command |
| 297 | } |
| 298 | |
| 299 | // Resolve backend and profile early so we can use them when building piArgs. |
| 300 | modelConfigured := workflowData.EngineConfig != nil && workflowData.EngineConfig.Model != "" |
| 301 | backend := resolvePiBackend(workflowData) |
| 302 | profile := getUniversalLLMBackendProfile(backend, hasCopilotRequestsWritePermission(workflowData)) |
| 303 | firewallEnabled := isFirewallEnabled(workflowData) |
| 304 | |
| 305 | // When engine.driver is set, run the driver script directly instead of the pi CLI. |
| 306 | driverConfigured := workflowData.EngineConfig != nil && workflowData.EngineConfig.Driver != "" |
| 307 | |
| 308 | // Build the pi command. Pi v0.72+ uses flags-only syntax (no "run" subcommand). |
| 309 | // --print: non-interactive, process prompt from stdin and exit. |
| 310 | // --mode json: emit structured JSONL events to stdout. |
| 311 | // --no-session: don't persist a session file (appropriate for CI). |
| 312 | piArgs := []string{"--print", "--mode", "json", "--no-session"} |
| 313 | |
| 314 | // Append any user-supplied extra args from engine.args |
| 315 | if workflowData.EngineConfig != nil { |
| 316 | piArgs = append(piArgs, filterPiArgs(workflowData.EngineConfig.Args)...) |
| 317 | } |
| 318 | |
| 319 | // Pi v0.72+ does not support a PI_MODEL env var for CLI model selection; the model must be passed as |
| 320 | // the --model CLI flag. When the firewall is enabled we route LLM traffic |
| 321 | // through the AWF gateway sidecar by generating a temporary models.json that |
| 322 | // registers a custom "aw-gateway" provider pointing at the gateway port. When |
| 323 | // the firewall is disabled we use Pi's built-in provider directly. |
| 324 | var piModelsJSONSetup string // shell fragment prepended to piCommand when needed |
| 325 | if modelConfigured { |
| 326 | modelID := extractPiModelID(workflowData.EngineConfig.Model) |
| 327 | |
| 328 | // Determine the env var name to use as the "apiKey" in the models.json gateway |
| 329 | // config. Pi's resolveConfigValue() reads process.env[apiKey] at runtime to |
| 330 | // obtain the actual token value. |
| 331 | gatewaySecretEnvVar := resolvePiGatewaySecretEnvVar(profile, backend) |
| 332 | |
| 333 | if firewallEnabled { |
| 334 | // Pi + firewall must always route through aw-gateway/models.json. Native |
| 335 | // provider resolution bypasses the gateway and is incompatible with this mode. |
| 336 | if gatewaySecretEnvVar == "" { |
| 337 | piLog.Printf("Pi: no gateway apiKey env resolved for backend=%s; defaulting to COPILOT_GITHUB_TOKEN", backend) |
| 338 | gatewaySecretEnvVar = "COPILOT_GITHUB_TOKEN" |
| 339 | } |
| 340 | |
| 341 | // Firewall case: write a models.json that redirects Pi's LLM calls to the |
| 342 | // AWF gateway sidecar port. The "apiKey" field value is the name of the env |
| 343 | // var that holds the secret; Pi's resolveConfigValue() looks up |
| 344 | // process.env[apiKey] to obtain the actual token value at runtime. |
| 345 | // |
| 346 | // printf '%s\n' '<json>' is safe here because JSON uses only double quotes |
| 347 | // (never single quotes), so single-quoting via shellEscapeArg requires no |