(params, context)
| 92 | } |
| 93 | |
| 94 | export const runLocalPi: PiBackendRun<PiLocalRunParams> = async (params, context) => { |
| 95 | // Isolate Pi resource discovery: an empty cwd/agentDir keeps DefaultResourceLoader |
| 96 | // from loading the Sim server's own .agents/skills, AGENTS.md, extensions, or settings. |
| 97 | const isolatedDir = await mkdtemp(join(tmpdir(), 'sim-pi-')) |
| 98 | // Clean up the scratch dir if the SSH connection fails — the try/finally below |
| 99 | // is only entered once the session is open, so an early handshake failure would |
| 100 | // otherwise orphan the directory. |
| 101 | const session = await openSshSession(params.ssh).catch(async (error) => { |
| 102 | await rm(isolatedDir, { recursive: true, force: true }).catch(() => {}) |
| 103 | throw error |
| 104 | }) |
| 105 | |
| 106 | try { |
| 107 | const sdk = await loadPiSdk() |
| 108 | |
| 109 | const authStorage = sdk.AuthStorage.create() |
| 110 | authStorage.setRuntimeApiKey(params.providerId, params.apiKey) |
| 111 | |
| 112 | const modelRegistry = sdk.ModelRegistry.create(authStorage) |
| 113 | const thinkingLevel = mapThinkingLevel(params.thinkingLevel) |
| 114 | // Parity with cloud: when the model isn't in Pi's bundled catalog under the |
| 115 | // resolved provider, pass it through on that provider instead of failing. |
| 116 | const model = |
| 117 | modelRegistry.find(params.providerId, params.model) ?? |
| 118 | buildPiFallbackModel(modelRegistry, params.providerId, params.model, thinkingLevel) |
| 119 | if (!model) { |
| 120 | throw new Error( |
| 121 | `Pi has no models for provider "${params.providerId}" (cannot run ${params.model})` |
| 122 | ) |
| 123 | } |
| 124 | |
| 125 | const specs = [...buildSshToolSpecs(session, params.repoPath), ...params.tools] |
| 126 | const customTools = specs.map((spec) => toPiTool(sdk, spec)) |
| 127 | |
| 128 | const { session: agentSession } = await sdk.createAgentSession({ |
| 129 | cwd: isolatedDir, |
| 130 | agentDir: isolatedDir, |
| 131 | model, |
| 132 | thinkingLevel, |
| 133 | noTools: 'builtin', |
| 134 | customTools, |
| 135 | authStorage, |
| 136 | modelRegistry, |
| 137 | sessionManager: sdk.SessionManager.inMemory(isolatedDir), |
| 138 | }) |
| 139 | |
| 140 | const totals = createPiTotals() |
| 141 | const unsubscribe = agentSession.subscribe((raw) => { |
| 142 | const event = normalizePiEvent(raw) |
| 143 | if (!event) return |
| 144 | applyPiEvent(totals, event) |
| 145 | context.onEvent(event) |
| 146 | }) |
| 147 | |
| 148 | const onAbort = () => { |
| 149 | void agentSession.abort() |
| 150 | } |
| 151 | if (context.signal?.aborted) { |
nothing calls this directly
no test coverage detected