@defrex/autobuild 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/** AgentRunner adapter for the operator's locally installed Pi CLI. */
|
|
2
|
+
import semver from 'semver'
|
|
3
|
+
import type { StreamPart } from '../../store/streams/types'
|
|
4
|
+
import {
|
|
5
|
+
agentInvocation,
|
|
6
|
+
type AgentContinueOpts,
|
|
7
|
+
type AgentRunner,
|
|
8
|
+
type AgentSessionHandle,
|
|
9
|
+
type AgentStartOpts,
|
|
10
|
+
type AgentTurnFailure,
|
|
11
|
+
type AgentTurnResult,
|
|
12
|
+
type SessionStreamEmitter,
|
|
13
|
+
type Transcript,
|
|
14
|
+
} from '../types'
|
|
15
|
+
import type { OneShotCompletion, OneShotCompletionInput, OneShotCompletionResult } from './one-shot'
|
|
16
|
+
import { classifyProviderError, configurationFailure, credentialFailure } from './provider-error'
|
|
17
|
+
import { createPiRpcSession, PI_MODEL_ARG, PI_RPC_MODE_ARG } from './pi-rpc'
|
|
18
|
+
import {
|
|
19
|
+
abortPart,
|
|
20
|
+
errorPart,
|
|
21
|
+
finishPart,
|
|
22
|
+
finishStepPart,
|
|
23
|
+
promptPart,
|
|
24
|
+
reasoningDeltaPart,
|
|
25
|
+
reasoningEndPart,
|
|
26
|
+
reasoningStartPart,
|
|
27
|
+
startPart,
|
|
28
|
+
startStepPart,
|
|
29
|
+
textDeltaPart,
|
|
30
|
+
textEndPart,
|
|
31
|
+
textStartPart,
|
|
32
|
+
toolInputPart,
|
|
33
|
+
toolOutputPart,
|
|
34
|
+
} from './stream-parts'
|
|
35
|
+
import type { RuntimeUsabilityInput, RuntimeUsabilityResult } from './runtime'
|
|
36
|
+
import { sessionEnv } from './session-env'
|
|
37
|
+
|
|
38
|
+
export const MINIMUM_PI_VERSION = '0.84.3'
|
|
39
|
+
const PI_TOOLS = ['read', 'bash', 'edit', 'write', 'grep', 'find', 'ls'] as const
|
|
40
|
+
|
|
41
|
+
export interface PiModelRef {
|
|
42
|
+
provider: string
|
|
43
|
+
id: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parsePiModel(model: string): PiModelRef {
|
|
47
|
+
const slash = model.indexOf('/')
|
|
48
|
+
if (slash <= 0 || slash === model.length - 1) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`pi runtime: model "${model}" is not provider-qualified — expected "<provider>/<id>". ` +
|
|
51
|
+
'Run `ab models <query>` to look up an id.',
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
return { provider: model.slice(0, slash), id: model.slice(slash + 1) }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parsePiModelCatalog(output: string): PiModelRef[] {
|
|
58
|
+
const lines = output
|
|
59
|
+
.split(/\r?\n/)
|
|
60
|
+
.map((line) => line.trim())
|
|
61
|
+
.filter((line) => line.length > 0)
|
|
62
|
+
const header = lines.shift()?.split(/\s{2,}/)
|
|
63
|
+
if (
|
|
64
|
+
header === undefined ||
|
|
65
|
+
header.length < 3 ||
|
|
66
|
+
header[0] !== 'provider' ||
|
|
67
|
+
header[1] !== 'model'
|
|
68
|
+
) {
|
|
69
|
+
throw new Error('expected a header beginning with "provider" and "model" columns')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return lines.map((line, index) => {
|
|
73
|
+
const columns = line.split(/\s{2,}/)
|
|
74
|
+
if (columns.length !== header.length || columns[0] === '' || columns[1] === '') {
|
|
75
|
+
throw new Error(`row ${index + 1} does not match the catalog header`)
|
|
76
|
+
}
|
|
77
|
+
return { provider: columns[0]!, id: columns[1]! }
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface PiCliInvocation {
|
|
82
|
+
args: string[]
|
|
83
|
+
cwd: string
|
|
84
|
+
env: Record<string, string>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PiCliResult {
|
|
88
|
+
stdout: string
|
|
89
|
+
stderr: string
|
|
90
|
+
exitCode: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type PiCliRunFn = (invocation: PiCliInvocation) => Promise<PiCliResult>
|
|
94
|
+
|
|
95
|
+
export const runPiCli: PiCliRunFn = async (invocation) => {
|
|
96
|
+
const proc = Bun.spawn(['pi', ...invocation.args], {
|
|
97
|
+
cwd: invocation.cwd,
|
|
98
|
+
env: invocation.env,
|
|
99
|
+
stdin: 'ignore',
|
|
100
|
+
stdout: 'pipe',
|
|
101
|
+
stderr: 'pipe',
|
|
102
|
+
})
|
|
103
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
104
|
+
new Response(proc.stdout).text(),
|
|
105
|
+
new Response(proc.stderr).text(),
|
|
106
|
+
proc.exited,
|
|
107
|
+
])
|
|
108
|
+
return { stdout, stderr, exitCode }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface PiPrerequisite {
|
|
112
|
+
version: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function checkLocalPi(
|
|
116
|
+
cwd: string,
|
|
117
|
+
env: Record<string, string>,
|
|
118
|
+
runCli: PiCliRunFn = runPiCli,
|
|
119
|
+
): Promise<PiPrerequisite> {
|
|
120
|
+
let result: PiCliResult
|
|
121
|
+
try {
|
|
122
|
+
result = await runCli({ args: ['--version'], cwd, env })
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (isEnoent(error)) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
'pi runtime: Pi CLI executable "pi" was not found. Install Pi, run `pi` and log in, then retry.',
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
throw new Error(`pi runtime: failed to launch Pi CLI executable "pi": ${errorText(error)}`)
|
|
130
|
+
}
|
|
131
|
+
const detected = result.stdout.trim()
|
|
132
|
+
if (result.exitCode !== 0) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`pi runtime: "pi --version" exited with code ${result.exitCode}: ${result.stderr.trim() || detected || 'no diagnostic'}`,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
const version = semver.valid(detected)
|
|
138
|
+
if (version === null) {
|
|
139
|
+
throw new Error(`pi runtime: could not parse detected Pi version "${detected || '(empty)'}"`)
|
|
140
|
+
}
|
|
141
|
+
if (semver.lt(version, MINIMUM_PI_VERSION)) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`pi runtime: detected Pi ${version}, but Autobuild requires Pi ${MINIMUM_PI_VERSION} or newer. Upgrade the local "pi" executable.`,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
return { version }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function isPiRuntimeUsable(
|
|
150
|
+
input: RuntimeUsabilityInput,
|
|
151
|
+
runCli: PiCliRunFn = runPiCli,
|
|
152
|
+
): Promise<RuntimeUsabilityResult> {
|
|
153
|
+
if (input.models.length === 0) {
|
|
154
|
+
return { usable: false, reason: 'Pi has no default model configured to probe' }
|
|
155
|
+
}
|
|
156
|
+
const env = cleanEnv(input.env)
|
|
157
|
+
try {
|
|
158
|
+
await checkLocalPi(input.cwd, env, runCli)
|
|
159
|
+
let catalog: PiModelRef[] | undefined
|
|
160
|
+
for (const model of new Set(input.models)) {
|
|
161
|
+
const modelRef = parsePiModel(model)
|
|
162
|
+
const result = await runCli({
|
|
163
|
+
args: ['auth', 'check', '--model', model, '--json'],
|
|
164
|
+
cwd: input.cwd,
|
|
165
|
+
env,
|
|
166
|
+
})
|
|
167
|
+
let payload: unknown
|
|
168
|
+
try {
|
|
169
|
+
payload = JSON.parse(result.stdout)
|
|
170
|
+
} catch {
|
|
171
|
+
return {
|
|
172
|
+
usable: false,
|
|
173
|
+
reason: `Pi authentication check for model "${model}" returned malformed JSON`,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const status = isRecord(payload) ? payload.status : undefined
|
|
177
|
+
if (result.exitCode !== 0 || status !== 'ready') {
|
|
178
|
+
const reason =
|
|
179
|
+
isRecord(payload) && typeof payload.reason === 'string' ? ` (${payload.reason})` : ''
|
|
180
|
+
return {
|
|
181
|
+
usable: false,
|
|
182
|
+
reason: `Pi model "${model}" is not ready with the configured provider credentials${reason}`,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (catalog === undefined) {
|
|
187
|
+
let catalogResult: PiCliResult
|
|
188
|
+
try {
|
|
189
|
+
catalogResult = await runCli({ args: ['--list-models'], cwd: input.cwd, env })
|
|
190
|
+
} catch (error) {
|
|
191
|
+
return {
|
|
192
|
+
usable: false,
|
|
193
|
+
reason: `Pi local model catalog probe failed to launch: ${errorText(error)}`,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (catalogResult.exitCode !== 0) {
|
|
197
|
+
return {
|
|
198
|
+
usable: false,
|
|
199
|
+
reason: `Pi local model catalog probe failed: "pi --list-models" exited with code ${catalogResult.exitCode}: ${catalogResult.stderr.trim() || catalogResult.stdout.trim() || 'no diagnostic'}`,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
catalog = parsePiModelCatalog(catalogResult.stdout)
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return {
|
|
206
|
+
usable: false,
|
|
207
|
+
reason: `Pi local model catalog probe returned a malformed table: ${errorText(error)}`,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (
|
|
212
|
+
!catalog.some(
|
|
213
|
+
(candidate) => candidate.provider === modelRef.provider && candidate.id === modelRef.id,
|
|
214
|
+
)
|
|
215
|
+
) {
|
|
216
|
+
return {
|
|
217
|
+
usable: false,
|
|
218
|
+
reason: `Pi model "${model}" is unavailable from the local Pi model catalog`,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { usable: true, reason: 'Local Pi CLI, model catalog, and authentication are available' }
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return { usable: false, reason: errorText(error) }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Options that select the model or the RPC protocol parsed by the adapter. */
|
|
229
|
+
export const PI_OWNED_ARGS = [PI_RPC_MODE_ARG, PI_MODEL_ARG] as const
|
|
230
|
+
|
|
231
|
+
export interface PiTurn {
|
|
232
|
+
text: string
|
|
233
|
+
usage: { inputTokens: number; outputTokens: number }
|
|
234
|
+
failure?: AgentTurnFailure
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface PiSession {
|
|
238
|
+
readonly sessionId: string
|
|
239
|
+
prompt(text: string, env: Record<string, string>, signal?: AbortSignal): Promise<PiTurn>
|
|
240
|
+
dispose(): Promise<void> | void
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export type PiCreateSessionFn = (opts: {
|
|
244
|
+
cwd: string
|
|
245
|
+
model?: PiModelRef
|
|
246
|
+
tools: readonly string[]
|
|
247
|
+
args: readonly string[]
|
|
248
|
+
skill?: string
|
|
249
|
+
env: Record<string, string>
|
|
250
|
+
/** Session-event observer for stream translation (SPEC §9); fakes may
|
|
251
|
+
* invoke it to deliver native session events during a prompt. */
|
|
252
|
+
onEvent?: (event: JsonRecord) => void
|
|
253
|
+
}) => Promise<PiSession>
|
|
254
|
+
|
|
255
|
+
const createLocalPiSession: PiCreateSessionFn = async (opts) => {
|
|
256
|
+
await checkLocalPi(opts.cwd, opts.env)
|
|
257
|
+
return createPiRpcSession(opts)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface TurnRecord {
|
|
261
|
+
turn: number
|
|
262
|
+
prompt: string
|
|
263
|
+
text: string
|
|
264
|
+
usage: { inputTokens: number; outputTokens: number }
|
|
265
|
+
failure?: AgentTurnFailure
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
type JsonRecord = Record<string, unknown>
|
|
269
|
+
|
|
270
|
+
/** Per-session event hook: the current turn's translator, or none between
|
|
271
|
+
* turns. Prompts are sequential per session, so one slot is safe. */
|
|
272
|
+
interface PiTurnEventHook {
|
|
273
|
+
translator?: PiTurnTranslator
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
interface SessionState {
|
|
277
|
+
opts: AgentStartOpts
|
|
278
|
+
model?: string
|
|
279
|
+
session?: PiSession
|
|
280
|
+
/** Set whenever the session was created through a path that could deliver
|
|
281
|
+
* native events; a turn without a stream leaves it unused. */
|
|
282
|
+
turnEvents?: PiTurnEventHook
|
|
283
|
+
turns: TurnRecord[]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Maps Pi's native session events onto the stream part vocabulary (SPEC §9).
|
|
288
|
+
* Unrecognized events produce no parts (fail open); the harness outcome is
|
|
289
|
+
* remembered so a turn ending in a provider abort emits an `abort` part
|
|
290
|
+
* rather than `error`.
|
|
291
|
+
*/
|
|
292
|
+
interface PiTurnTranslator {
|
|
293
|
+
onEvent(event: JsonRecord): void
|
|
294
|
+
/** Set when `message_end` carried stopReason "aborted". */
|
|
295
|
+
aborted(): boolean
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function createPiTurnTranslator(emit: (parts: StreamPart[]) => void): PiTurnTranslator {
|
|
299
|
+
let nextId = 0
|
|
300
|
+
let textId: string | undefined
|
|
301
|
+
let thinkingId: string | undefined
|
|
302
|
+
let aborted = false
|
|
303
|
+
|
|
304
|
+
const blockId = (event: JsonRecord, current: string | undefined): string => {
|
|
305
|
+
const existing = event.id
|
|
306
|
+
if (typeof existing === 'string' && existing.length > 0) return existing
|
|
307
|
+
return current ?? `pi-${++nextId}`
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
onEvent(event) {
|
|
312
|
+
switch (event.type) {
|
|
313
|
+
case 'agent_start':
|
|
314
|
+
case 'turn_start':
|
|
315
|
+
emit([startStepPart()])
|
|
316
|
+
break
|
|
317
|
+
case 'message_update': {
|
|
318
|
+
const update = isRecord(event.assistantMessageEvent) ? event.assistantMessageEvent : {}
|
|
319
|
+
switch (update.type) {
|
|
320
|
+
case 'text_start':
|
|
321
|
+
textId = blockId(update, textId ?? `pi-${++nextId}`)
|
|
322
|
+
emit([textStartPart(textId)])
|
|
323
|
+
break
|
|
324
|
+
case 'text_delta':
|
|
325
|
+
textId ??= `pi-${++nextId}`
|
|
326
|
+
if (typeof update.delta === 'string') emit([textDeltaPart(textId, update.delta)])
|
|
327
|
+
break
|
|
328
|
+
case 'text_end':
|
|
329
|
+
emit([textEndPart(blockId(update, textId ?? `pi-${++nextId}`))])
|
|
330
|
+
textId = undefined
|
|
331
|
+
break
|
|
332
|
+
case 'thinking_start':
|
|
333
|
+
thinkingId = blockId(update, thinkingId ?? `pi-${++nextId}`)
|
|
334
|
+
emit([reasoningStartPart(thinkingId)])
|
|
335
|
+
break
|
|
336
|
+
case 'thinking_delta':
|
|
337
|
+
thinkingId ??= `pi-${++nextId}`
|
|
338
|
+
if (typeof update.delta === 'string') {
|
|
339
|
+
emit([reasoningDeltaPart(thinkingId, update.delta)])
|
|
340
|
+
}
|
|
341
|
+
break
|
|
342
|
+
case 'thinking_end':
|
|
343
|
+
emit([reasoningEndPart(blockId(update, thinkingId ?? `pi-${++nextId}`))])
|
|
344
|
+
thinkingId = undefined
|
|
345
|
+
break
|
|
346
|
+
default:
|
|
347
|
+
break
|
|
348
|
+
}
|
|
349
|
+
break
|
|
350
|
+
}
|
|
351
|
+
case 'tool_execution_start': {
|
|
352
|
+
const toolCallId =
|
|
353
|
+
typeof event.toolCallId === 'string' ? event.toolCallId : `pi-${++nextId}`
|
|
354
|
+
const toolName = typeof event.toolName === 'string' ? event.toolName : 'tool'
|
|
355
|
+
emit([toolInputPart(toolCallId, toolName, event.args ?? {})])
|
|
356
|
+
break
|
|
357
|
+
}
|
|
358
|
+
case 'tool_execution_end': {
|
|
359
|
+
const toolCallId =
|
|
360
|
+
typeof event.toolCallId === 'string' ? event.toolCallId : `pi-${++nextId}`
|
|
361
|
+
emit([toolOutputPart(toolCallId, event.result ?? null)])
|
|
362
|
+
break
|
|
363
|
+
}
|
|
364
|
+
case 'message_end': {
|
|
365
|
+
const message = isRecord(event.message) ? event.message : {}
|
|
366
|
+
if (message.stopReason === 'aborted') aborted = true
|
|
367
|
+
break
|
|
368
|
+
}
|
|
369
|
+
case 'turn_end':
|
|
370
|
+
emit([finishStepPart()])
|
|
371
|
+
break
|
|
372
|
+
default:
|
|
373
|
+
break
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
aborted() {
|
|
377
|
+
return aborted
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export class PiAgentRunner implements AgentRunner, OneShotCompletion {
|
|
383
|
+
readonly name = 'pi'
|
|
384
|
+
private readonly createSessionFn: PiCreateSessionFn
|
|
385
|
+
private readonly createSessionId: () => string
|
|
386
|
+
private readonly sessions = new Map<string, SessionState>()
|
|
387
|
+
|
|
388
|
+
constructor(opts: { createSessionFn?: PiCreateSessionFn; createSessionId?: () => string } = {}) {
|
|
389
|
+
this.createSessionFn = opts.createSessionFn ?? createLocalPiSession
|
|
390
|
+
this.createSessionId = opts.createSessionId ?? (() => crypto.randomUUID())
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async complete(input: OneShotCompletionInput): Promise<OneShotCompletionResult> {
|
|
394
|
+
const model = input.model !== undefined ? parsePiModel(input.model) : undefined
|
|
395
|
+
const session = await this.createSessionFn({
|
|
396
|
+
cwd: input.cwd,
|
|
397
|
+
...(model !== undefined ? { model } : {}),
|
|
398
|
+
tools: [],
|
|
399
|
+
args: input.args ?? [],
|
|
400
|
+
env: sessionEnv(input.env),
|
|
401
|
+
})
|
|
402
|
+
try {
|
|
403
|
+
const turn = await session.prompt(input.prompt, sessionEnv(input.env), input.signal)
|
|
404
|
+
if (turn.failure !== undefined) throw new Error(turn.failure.message)
|
|
405
|
+
return { text: turn.text }
|
|
406
|
+
} finally {
|
|
407
|
+
await session.dispose()
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async start(
|
|
412
|
+
opts: AgentStartOpts,
|
|
413
|
+
): Promise<{ session: AgentSessionHandle; result: AgentTurnResult }> {
|
|
414
|
+
const model = opts.model !== undefined ? parsePiModel(opts.model) : undefined
|
|
415
|
+
const prompt = `/skill:${opts.skill} ${agentInvocation(opts)}`
|
|
416
|
+
let native: PiSession | undefined
|
|
417
|
+
let turn: PiTurn
|
|
418
|
+
const turnEvents: PiTurnEventHook = {}
|
|
419
|
+
try {
|
|
420
|
+
native = await this.createSessionFn({
|
|
421
|
+
cwd: opts.workspacePath,
|
|
422
|
+
...(model !== undefined ? { model } : {}),
|
|
423
|
+
tools: PI_TOOLS,
|
|
424
|
+
args: opts.args ?? [],
|
|
425
|
+
skill: opts.skill,
|
|
426
|
+
env: sessionEnv(opts.env),
|
|
427
|
+
// Stream translation is per turn; the hook stays live for later
|
|
428
|
+
// continued turns even when turn 1 streams nothing.
|
|
429
|
+
onEvent: (event) => turnEvents.translator?.onEvent(event),
|
|
430
|
+
})
|
|
431
|
+
turn = await this.runPrompt(
|
|
432
|
+
native,
|
|
433
|
+
prompt,
|
|
434
|
+
this.turnEnv(opts.env),
|
|
435
|
+
opts.signal,
|
|
436
|
+
opts.stream,
|
|
437
|
+
turnEvents,
|
|
438
|
+
)
|
|
439
|
+
} catch (error) {
|
|
440
|
+
turn = {
|
|
441
|
+
text: '',
|
|
442
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
443
|
+
failure: localPiFailure(error),
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const id = native?.sessionId ?? this.createSessionId()
|
|
447
|
+
const handle: AgentSessionHandle = {
|
|
448
|
+
id,
|
|
449
|
+
runner: this.name,
|
|
450
|
+
...(opts.model !== undefined ? { model: opts.model } : {}),
|
|
451
|
+
}
|
|
452
|
+
this.sessions.set(id, {
|
|
453
|
+
opts,
|
|
454
|
+
...(opts.model !== undefined ? { model: opts.model } : {}),
|
|
455
|
+
...(native !== undefined ? { session: native } : {}),
|
|
456
|
+
...(native !== undefined ? { turnEvents } : {}),
|
|
457
|
+
turns: [this.turnRecord(1, prompt, turn)],
|
|
458
|
+
})
|
|
459
|
+
return { session: handle, result: this.toResult(turn) }
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async continue(
|
|
463
|
+
handle: AgentSessionHandle,
|
|
464
|
+
message: string,
|
|
465
|
+
opts?: AgentContinueOpts,
|
|
466
|
+
): Promise<AgentTurnResult> {
|
|
467
|
+
const state = this.liveState(handle, 'continue')
|
|
468
|
+
if (state.session === undefined) {
|
|
469
|
+
throw new Error(`pi: cannot continue session "${handle.id}" because local Pi failed to start`)
|
|
470
|
+
}
|
|
471
|
+
const scoped = opts?.env !== undefined ? { ...state.opts.env, ...opts.env } : state.opts.env
|
|
472
|
+
const turn = await this.runPrompt(
|
|
473
|
+
state.session,
|
|
474
|
+
message,
|
|
475
|
+
this.turnEnv(scoped),
|
|
476
|
+
opts?.signal,
|
|
477
|
+
opts?.stream,
|
|
478
|
+
state.turnEvents,
|
|
479
|
+
)
|
|
480
|
+
state.turns.push(this.turnRecord(state.turns.length + 1, message, turn))
|
|
481
|
+
return this.toResult(turn)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async end(handle: AgentSessionHandle): Promise<Transcript> {
|
|
485
|
+
const state = this.liveState(handle, 'end')
|
|
486
|
+
this.sessions.delete(handle.id)
|
|
487
|
+
await state.session?.dispose()
|
|
488
|
+
const usage = { inputTokens: 0, outputTokens: 0, turns: 0 }
|
|
489
|
+
for (const turn of state.turns) {
|
|
490
|
+
usage.inputTokens += turn.usage.inputTokens
|
|
491
|
+
usage.outputTokens += turn.usage.outputTokens
|
|
492
|
+
usage.turns += 1
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
content: JSON.stringify(
|
|
496
|
+
{
|
|
497
|
+
session: handle.id,
|
|
498
|
+
skill: state.opts.skill,
|
|
499
|
+
invocation: agentInvocation(state.opts),
|
|
500
|
+
...(state.opts.buildSlug !== undefined ? { buildSlug: state.opts.buildSlug } : {}),
|
|
501
|
+
turns: state.turns,
|
|
502
|
+
},
|
|
503
|
+
null,
|
|
504
|
+
2,
|
|
505
|
+
),
|
|
506
|
+
metadata: {
|
|
507
|
+
runner: this.name,
|
|
508
|
+
...(state.model !== undefined ? { model: state.model } : {}),
|
|
509
|
+
usage,
|
|
510
|
+
},
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
private async runPrompt(
|
|
515
|
+
session: PiSession,
|
|
516
|
+
prompt: string,
|
|
517
|
+
env: Record<string, string>,
|
|
518
|
+
signal?: AbortSignal,
|
|
519
|
+
stream?: SessionStreamEmitter,
|
|
520
|
+
turnEvents?: PiTurnEventHook,
|
|
521
|
+
): Promise<PiTurn> {
|
|
522
|
+
if (stream !== undefined) stream.append([promptPart(prompt), startPart(crypto.randomUUID())])
|
|
523
|
+
const translator =
|
|
524
|
+
stream !== undefined ? createPiTurnTranslator((parts) => stream.append(parts)) : undefined
|
|
525
|
+
if (translator !== undefined && turnEvents !== undefined) turnEvents.translator = translator
|
|
526
|
+
try {
|
|
527
|
+
const turn = await session.prompt(prompt, env, signal)
|
|
528
|
+
if (stream !== undefined) {
|
|
529
|
+
if (turn.failure === undefined) {
|
|
530
|
+
stream.append([finishPart()])
|
|
531
|
+
} else if (translator?.aborted() === true || signal?.aborted === true) {
|
|
532
|
+
stream.append([abortPart(turn.failure.message)])
|
|
533
|
+
} else {
|
|
534
|
+
stream.append([errorPart(turn.failure.message)])
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return turn
|
|
538
|
+
} catch (error) {
|
|
539
|
+
const failure = localPiFailure(error)
|
|
540
|
+
if (stream !== undefined) {
|
|
541
|
+
if (signal?.aborted) {
|
|
542
|
+
stream.append([abortPart(error instanceof Error ? error.message : String(error))])
|
|
543
|
+
} else if (translator?.aborted() === true) {
|
|
544
|
+
stream.append([abortPart(failure.message)])
|
|
545
|
+
} else {
|
|
546
|
+
stream.append([errorPart(failure.message)])
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
text: '',
|
|
551
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
552
|
+
failure,
|
|
553
|
+
}
|
|
554
|
+
} finally {
|
|
555
|
+
if (translator !== undefined && turnEvents !== undefined) turnEvents.translator = undefined
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private liveState(handle: AgentSessionHandle, operation: 'continue' | 'end'): SessionState {
|
|
560
|
+
const state = this.sessions.get(handle.id)
|
|
561
|
+
if (state === undefined) throw new Error(`pi: ${operation} on unknown session "${handle.id}"`)
|
|
562
|
+
return state
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private turnRecord(number: number, prompt: string, turn: PiTurn): TurnRecord {
|
|
566
|
+
return {
|
|
567
|
+
turn: number,
|
|
568
|
+
prompt,
|
|
569
|
+
text: turn.text,
|
|
570
|
+
usage: turn.usage,
|
|
571
|
+
...(turn.failure !== undefined ? { failure: turn.failure } : {}),
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private toResult(turn: PiTurn): AgentTurnResult {
|
|
576
|
+
const base = { text: turn.text, usage: { ...turn.usage, turns: 1 } }
|
|
577
|
+
return turn.failure === undefined
|
|
578
|
+
? { kind: 'completed', ...base }
|
|
579
|
+
: { kind: 'failed', ...base, failure: turn.failure }
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private turnEnv(scoped: Record<string, string>): Record<string, string> {
|
|
583
|
+
return sessionEnv(scoped)
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function localPiFailure(error: unknown): AgentTurnFailure {
|
|
588
|
+
const message = errorText(error)
|
|
589
|
+
if (
|
|
590
|
+
/executable "pi" was not found|requires Pi .* or newer|could not parse detected Pi version/.test(
|
|
591
|
+
message,
|
|
592
|
+
)
|
|
593
|
+
) {
|
|
594
|
+
return configurationFailure(message)
|
|
595
|
+
}
|
|
596
|
+
if (/not ready|authentication|logged in|credentials/i.test(message))
|
|
597
|
+
return credentialFailure(message)
|
|
598
|
+
return classifyProviderError(message)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function cleanEnv(env: Readonly<Record<string, string | undefined>>): Record<string, string> {
|
|
602
|
+
return Object.fromEntries(
|
|
603
|
+
Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
|
604
|
+
)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function isEnoent(error: unknown): boolean {
|
|
608
|
+
return isRecord(error) && error.code === 'ENOENT'
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
612
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function errorText(error: unknown): string {
|
|
616
|
+
return error instanceof Error ? error.message : String(error)
|
|
617
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production runtime registrations shared by every sessionless CLI path that
|
|
3
|
+
* needs agent judgment. Keeping the shipped adapters and their model families
|
|
4
|
+
* here prevents dispatch and other non-phase one-shots from drifting apart.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
CLAUDE_OWNED_ARGS,
|
|
8
|
+
CLAUDE_PROMPT_BOUNDARY,
|
|
9
|
+
ClaudeAgentRunner,
|
|
10
|
+
isClaudeRuntimeUsable,
|
|
11
|
+
} from './claude'
|
|
12
|
+
import {
|
|
13
|
+
CODEX_OWNED_ARGS,
|
|
14
|
+
CODEX_PROMPT_BOUNDARY,
|
|
15
|
+
CodexAgentRunner,
|
|
16
|
+
isCodexRuntimeUsable,
|
|
17
|
+
} from './codex'
|
|
18
|
+
import { isPiRuntimeUsable, PI_OWNED_ARGS, PiAgentRunner } from './pi'
|
|
19
|
+
import type { SessionStreamInfo, RuntimeRegistry } from './runtime'
|
|
20
|
+
import type { SessionStreamSink } from '../types'
|
|
21
|
+
|
|
22
|
+
export interface ProductionRuntimes {
|
|
23
|
+
runtimes: RuntimeRegistry
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createProductionRuntimes(): ProductionRuntimes {
|
|
27
|
+
// Each adapter carries both the resumable AgentRunner contract and its
|
|
28
|
+
// optional tool-free OneShotCompletion capability.
|
|
29
|
+
const claude = new ClaudeAgentRunner()
|
|
30
|
+
const codex = new CodexAgentRunner()
|
|
31
|
+
const pi = new PiAgentRunner()
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
runtimes: {
|
|
35
|
+
claude: {
|
|
36
|
+
runner: claude,
|
|
37
|
+
oneShot: claude,
|
|
38
|
+
initUsable: isClaudeRuntimeUsable,
|
|
39
|
+
servesModels: ['claude-'],
|
|
40
|
+
ownedArgs: CLAUDE_OWNED_ARGS,
|
|
41
|
+
promptBoundary: CLAUDE_PROMPT_BOUNDARY,
|
|
42
|
+
openSessionStream: openSessionStreamLabel,
|
|
43
|
+
},
|
|
44
|
+
codex: {
|
|
45
|
+
runner: codex,
|
|
46
|
+
oneShot: codex,
|
|
47
|
+
initUsable: isCodexRuntimeUsable,
|
|
48
|
+
// Codex CLI model ids are unqualified; an omitted model delegates to
|
|
49
|
+
// the operator's configured Codex default.
|
|
50
|
+
servesModels: ['gpt-'],
|
|
51
|
+
ownedArgs: CODEX_OWNED_ARGS,
|
|
52
|
+
promptBoundary: CODEX_PROMPT_BOUNDARY,
|
|
53
|
+
openSessionStream: openSessionStreamLabel,
|
|
54
|
+
},
|
|
55
|
+
pi: {
|
|
56
|
+
runner: pi,
|
|
57
|
+
oneShot: pi,
|
|
58
|
+
initUsable: isPiRuntimeUsable,
|
|
59
|
+
// The local Pi installation owns the provider catalog. Any
|
|
60
|
+
// provider-qualified model it resolves is valid here.
|
|
61
|
+
servesModels: ['*/*'],
|
|
62
|
+
defaultModel: 'kimi-coding/k3',
|
|
63
|
+
ownedArgs: PI_OWNED_ARGS,
|
|
64
|
+
openSessionStream: openSessionStreamLabel,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The one shared opener: a session bracket's stream is labeled by its
|
|
71
|
+
* Autobuild session id, never a harness-native session/thread id. */
|
|
72
|
+
function openSessionStreamLabel(sink: SessionStreamSink, info: SessionStreamInfo): Promise<string> {
|
|
73
|
+
return sink.open(`session:${info.session}`)
|
|
74
|
+
}
|