(params, context)
| 181 | } |
| 182 | |
| 183 | export const runCloudPi: PiBackendRun<PiCloudRunParams> = async (params, context) => { |
| 184 | if (!params.isBYOK) { |
| 185 | throw new Error( |
| 186 | 'Cloud mode requires your own provider API key (BYOK). Set one in Settings > BYOK.' |
| 187 | ) |
| 188 | } |
| 189 | const keyEnvVar = providerApiKeyEnvVar(params.providerId) |
| 190 | if (!keyEnvVar) { |
| 191 | throw new Error( |
| 192 | `Provider "${params.providerId}" is not supported in cloud mode. Use a key-based provider or run in local mode.` |
| 193 | ) |
| 194 | } |
| 195 | |
| 196 | const branch = params.branchName?.trim() || `pi/${generateShortId(8)}` |
| 197 | const commitMessage = defaultTitle(params) |
| 198 | const prompt = buildPiPrompt({ |
| 199 | skills: params.skills, |
| 200 | initialMessages: params.initialMessages, |
| 201 | task: params.task, |
| 202 | guidance: CLOUD_GUIDANCE, |
| 203 | }) |
| 204 | const totals = createPiTotals() |
| 205 | const thinking = mapThinkingLevel(params.thinkingLevel) ?? 'medium' |
| 206 | |
| 207 | return withPiSandbox(async (runner) => { |
| 208 | try { |
| 209 | const clone = await raceAbort( |
| 210 | runner.run(CLONE_SCRIPT, { |
| 211 | envs: { |
| 212 | GITHUB_TOKEN: params.githubToken, |
| 213 | REPO_OWNER: params.owner, |
| 214 | REPO_NAME: params.repo, |
| 215 | BASE_BRANCH: params.baseBranch?.trim() ?? '', |
| 216 | BRANCH: branch, |
| 217 | }, |
| 218 | timeoutMs: CLONE_TIMEOUT_MS, |
| 219 | }), |
| 220 | context.signal |
| 221 | ) |
| 222 | if (clone.exitCode !== 0) { |
| 223 | throw new Error( |
| 224 | `git clone failed: ${scrubGitSecrets(clone.stderr || clone.stdout || 'unknown error', params.githubToken)}` |
| 225 | ) |
| 226 | } |
| 227 | const baseSha = extractMarkerValues(clone.stdout, '__BASE_SHA__=')[0] |
| 228 | if (!baseSha) { |
| 229 | throw new Error('Clone did not report a base commit') |
| 230 | } |
| 231 | const detectedBase = extractMarkerValues(clone.stdout, '__DEFAULT_BRANCH__=')[0] |
| 232 | |
| 233 | // Deliver the prompt as a file (read back on Pi's stdin), not a CLI |
| 234 | // arg/env, so its skill/memory content can't be parsed by the shell that |
| 235 | // launches the Pi loop. |
| 236 | await runner.writeFile(PROMPT_PATH, prompt) |
| 237 | |
| 238 | let buffer = '' |
| 239 | const handleChunk = (chunk: string) => { |
| 240 | buffer += chunk |
no test coverage detected