@mono-agent/agent-runtime 0.4.0 → 0.4.1

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.
Files changed (119) hide show
  1. package/ARCHITECTURE.md +37 -16
  2. package/MIGRATION.md +185 -7
  3. package/README.md +24 -14
  4. package/package.json +103 -10
  5. package/src/agent/allowlists.js +3 -0
  6. package/src/agent/approval.js +3 -0
  7. package/src/agent/compaction.js +244 -1
  8. package/src/agent/prompt/skill-index.js +6 -0
  9. package/src/agent/sandbox-seam.js +227 -0
  10. package/src/agent/tool-bloat.js +8 -2
  11. package/src/agent/tools/bash.js +16 -8
  12. package/src/agent/tools/edit.js +7 -3
  13. package/src/agent/tools/glob.js +11 -5
  14. package/src/agent/tools/grep.js +11 -5
  15. package/src/agent/tools/pi-bridge.js +227 -45
  16. package/src/agent/tools/read.js +28 -3
  17. package/src/agent/tools/shared/output-truncation.js +8 -3
  18. package/src/agent/tools/shared/path-resolver.js +24 -18
  19. package/src/agent/tools/shared/ripgrep.js +33 -6
  20. package/src/agent/tools/shared/runtime-context.js +60 -50
  21. package/src/agent/tools/shared/tool-context.js +157 -0
  22. package/src/agent/tools/web-fetch.js +21 -10
  23. package/src/agent/tools/web-search.js +11 -4
  24. package/src/agent/tools/write.js +7 -3
  25. package/src/agent/transcript.js +16 -1
  26. package/src/ai/cost.js +128 -3
  27. package/src/ai/failure.js +96 -4
  28. package/src/ai/live-input-prompt.js +11 -1
  29. package/src/ai/providers/claude-cli.js +2 -2
  30. package/src/ai/providers/claude-sdk.js +55 -12
  31. package/src/ai/providers/codex-app.js +36 -23
  32. package/src/ai/providers/opencode-app.js +2 -2
  33. package/src/ai/providers/opencode-discovery.js +2 -2
  34. package/src/ai/providers/pi-events.js +5 -0
  35. package/src/ai/providers/pi-models.js +21 -4
  36. package/src/ai/providers/pi-native/compaction-driver.js +394 -0
  37. package/src/ai/providers/pi-native/result-builder.js +312 -0
  38. package/src/ai/providers/pi-native/session-lifecycle.js +438 -0
  39. package/src/ai/providers/pi-native/stream-subscriber.js +148 -0
  40. package/src/ai/providers/pi-native/structured-output.js +130 -0
  41. package/src/ai/providers/pi-native/turn-runner.js +278 -0
  42. package/src/ai/providers/pi-native.js +415 -1106
  43. package/src/ai/runtime/model-refs.js +30 -0
  44. package/src/ai/runtime/registry.js +29 -0
  45. package/src/ai/runtime/router.js +96 -12
  46. package/src/ai/runtime/session-liveness.js +110 -0
  47. package/src/ai/runtime/sessions.js +16 -0
  48. package/src/ai/types.js +252 -19
  49. package/src/index.js +1 -1
  50. package/src/pi-auth.js +147 -16
  51. package/src/runtime-brand.js +21 -1
  52. package/src/runtime.js +72 -8
  53. package/types/agent/allowlists.d.ts +25 -0
  54. package/types/agent/approval.d.ts +30 -0
  55. package/types/agent/compaction.d.ts +97 -0
  56. package/types/agent/index.d.ts +5 -0
  57. package/types/agent/prompt/skill-index.d.ts +19 -0
  58. package/types/agent/sandbox-seam.d.ts +148 -0
  59. package/types/agent/tool-bloat.d.ts +22 -0
  60. package/types/agent/tools/bash.d.ts +16 -0
  61. package/types/agent/tools/edit.d.ts +14 -0
  62. package/types/agent/tools/glob.d.ts +16 -0
  63. package/types/agent/tools/grep.d.ts +22 -0
  64. package/types/agent/tools/index.d.ts +10 -0
  65. package/types/agent/tools/pi-bridge.d.ts +144 -0
  66. package/types/agent/tools/read.d.ts +19 -0
  67. package/types/agent/tools/shared/constants.d.ts +13 -0
  68. package/types/agent/tools/shared/dedup.d.ts +8 -0
  69. package/types/agent/tools/shared/output-truncation.d.ts +22 -0
  70. package/types/agent/tools/shared/path-resolver.d.ts +6 -0
  71. package/types/agent/tools/shared/ripgrep.d.ts +38 -0
  72. package/types/agent/tools/shared/runtime-context.d.ts +27 -0
  73. package/types/agent/tools/shared/tool-context.d.ts +48 -0
  74. package/types/agent/tools/web-fetch.d.ts +13 -0
  75. package/types/agent/tools/web-search.d.ts +11 -0
  76. package/types/agent/tools/write.d.ts +12 -0
  77. package/types/agent/transcript.d.ts +45 -0
  78. package/types/ai/backend.d.ts +41 -0
  79. package/types/ai/cost.d.ts +96 -0
  80. package/types/ai/failure.d.ts +117 -0
  81. package/types/ai/file-change-stats.d.ts +75 -0
  82. package/types/ai/index.d.ts +7 -0
  83. package/types/ai/live-input-prompt.d.ts +6 -0
  84. package/types/ai/observer.d.ts +68 -0
  85. package/types/ai/providers/claude-cli.d.ts +211 -0
  86. package/types/ai/providers/claude-sdk.d.ts +66 -0
  87. package/types/ai/providers/claude-subagents.d.ts +2 -0
  88. package/types/ai/providers/codex-app.d.ts +151 -0
  89. package/types/ai/providers/opencode-app.d.ts +95 -0
  90. package/types/ai/providers/opencode-discovery.d.ts +4 -0
  91. package/types/ai/providers/pi-errors.d.ts +3 -0
  92. package/types/ai/providers/pi-events.d.ts +24 -0
  93. package/types/ai/providers/pi-messages.d.ts +5 -0
  94. package/types/ai/providers/pi-models.d.ts +57 -0
  95. package/types/ai/providers/pi-native/compaction-driver.d.ts +86 -0
  96. package/types/ai/providers/pi-native/result-builder.d.ts +168 -0
  97. package/types/ai/providers/pi-native/session-lifecycle.d.ts +62 -0
  98. package/types/ai/providers/pi-native/stream-subscriber.d.ts +50 -0
  99. package/types/ai/providers/pi-native/structured-output.d.ts +69 -0
  100. package/types/ai/providers/pi-native/turn-runner.d.ts +60 -0
  101. package/types/ai/providers/pi-native.d.ts +18 -0
  102. package/types/ai/registry.d.ts +1 -0
  103. package/types/ai/runtime/capabilities-used.d.ts +21 -0
  104. package/types/ai/runtime/capabilities.d.ts +33 -0
  105. package/types/ai/runtime/context-windows.d.ts +8 -0
  106. package/types/ai/runtime/fast-mode.d.ts +2 -0
  107. package/types/ai/runtime/model-refs.d.ts +35 -0
  108. package/types/ai/runtime/registry.d.ts +38 -0
  109. package/types/ai/runtime/router.d.ts +56 -0
  110. package/types/ai/runtime/session-liveness.d.ts +55 -0
  111. package/types/ai/runtime/sessions.d.ts +38 -0
  112. package/types/ai/streaming/codex-events.d.ts +30 -0
  113. package/types/ai/streaming/opencode-events.d.ts +41 -0
  114. package/types/ai/types.d.ts +702 -0
  115. package/types/index.d.ts +7 -0
  116. package/types/pi-auth.d.ts +28 -0
  117. package/types/runtime-brand.d.ts +30 -0
  118. package/types/runtime.d.ts +10 -0
  119. package/src/ai/providers/pi-sdk.js +0 -35
package/src/ai/types.js CHANGED
@@ -1,40 +1,155 @@
1
1
  // Runtime contracts. These are JSDoc-only; the codebase is JS
2
2
  // (no TypeScript), so the types document the bridge surface that active
3
3
  // runtimes implement through src/ai/runtime/registry.js.
4
+ //
5
+ // `tsc -p tsconfig.types.json` (allowJs + declaration + emitDeclarationOnly)
6
+ // compiles these typedefs into packages/agent-runtime/types/ai/types.d.ts;
7
+ // downstream TS consumers (currently the runtime-adapter package) import them
8
+ // via `import('./types.js').X` JSDoc syntax from the other seam files, or
9
+ // transitively through the package root's generated declarations.
10
+ //
11
+ // A few fields below use the `"literal" | (string & {})` shape (a "branded
12
+ // string union"). This keeps the known literal values available for
13
+ // IDE/editor autocomplete while still accepting any plain `string`, since
14
+ // hosts validate the real vocabulary at runtime (parseRuntimeModelReference,
15
+ // resolveRuntimeBridge) rather than at the type level — the type only
16
+ // documents the values active runtimes currently produce.
17
+
18
+ /**
19
+ * @typedef {"claude" | "pi" | "codex" | "opencode" | (string & {})} RuntimeSdkId
20
+ * Canonical active runtime id. See ACTIVE_RUNTIME_KINDS (model-refs.js) for
21
+ * the enforced-at-runtime vocabulary.
22
+ */
23
+
24
+ /**
25
+ * @typedef {"claude" | "claude-code" | "codex-app" | "opencode-app" | "pi" | (string & {})} RuntimeBridgeId
26
+ * Registry bridge id (distinct from RuntimeSdkId: a single sdk can be served
27
+ * by more than one bridge, e.g. sdk "claude" is served by both the "claude"
28
+ * SDK bridge and the "claude-code" CLI bridge). See
29
+ * src/ai/runtime/registry.js's builtinBridgeSpecs.
30
+ */
4
31
 
5
32
  /**
6
33
  * @typedef {Object} RuntimeModelRef
7
- * @property {"claude" | "pi" | "codex"} sdk Canonical active runtime id.
8
- * @property {string} model Provider model id.
9
- * @property {string} reference Original canonical model reference.
10
- * @property {string} [provider] Pi provider id when sdk === "pi".
34
+ * @property {RuntimeSdkId} sdk Canonical active runtime id.
35
+ * @property {string} model Provider model id.
36
+ * @property {string} [reference] Original canonical model reference; always set by
37
+ * parseRuntimeModelReference, but router.js's chain
38
+ * shorthand accepts bare {sdk, model} refs without one.
39
+ * @property {string} [provider] Pi/OpenCode provider id when sdk === "pi" | "opencode".
40
+ */
41
+
42
+ /**
43
+ * @typedef {{type: string, [key: string]: *}} RuntimeEvent
44
+ * Structured runtime/telemetry event. `type` is the only required field;
45
+ * every event kind (tool_approval_pending, provider_failover_started,
46
+ * context_compaction_applied, ...) adds its own extra fields.
47
+ */
48
+
49
+ /**
50
+ * @typedef {Object} RuntimeObserver
51
+ * Per-call or host-level observer merged by createObserverHub (ai/observer.js).
52
+ * Loose on purpose: observer.js is not a kernel seam file.
53
+ * @property {(event: RuntimeEvent) => (void|Promise<void>)} [onEvent]
54
+ * @property {() => (void|Promise<void>)} [flush]
11
55
  */
12
56
 
13
57
  /**
14
- * @typedef {Object} RuntimeRequest
15
- * @property {string} systemPrompt
16
- * @property {Array<Object>} messages
17
- * @property {RuntimeModelRef} model
58
+ * @typedef {Object} RuntimeToolLimits
59
+ * Typed per-run tool-output limits (the supported replacement for the
60
+ * `agent_tool_*` / `agent_mcp_*` keys of the deprecated `settings` bag). Every
61
+ * field is optional; an omitted field falls back to the kernel default (see
62
+ * resolveAgentCompactionPolicy, agent/compaction.js).
63
+ * @property {number} [toolTextLimitChars] Max chars of a builtin tool's text result.
64
+ * @property {number} [bashOutputLimitChars] Max chars of bash stdout/stderr.
65
+ * @property {number} [mcpTextLimitChars] Max chars of an MCP tool's text result.
66
+ * @property {number} [searchResultLimit] Max Grep/search hits returned.
67
+ * @property {number} [imageInlineMaxBytes] Max bytes of an inlined image result.
68
+ * @property {number} [toolPayloadMaxBytes] Hard cap on a single tool_result payload.
69
+ * @property {number} [mcpCallTimeoutMs] Per-MCP-call inactivity timeout.
70
+ * @property {number} [mcpCallMaxTotalTimeoutMs] Hard wall-clock cap for one MCP call.
71
+ * @property {number} [bashTimeoutMs] Documented for forward-compat; NOT wired to
72
+ * any tool today (no `agent_bash_*_timeout` mechanism exists — bash reads its per-call
73
+ * `timeout` argument), so setting it has no effect until a run-level default is introduced.
74
+ */
75
+
76
+ /**
77
+ * @typedef {Object} RuntimeCompactionPolicy
78
+ * Typed per-run context-compaction policy (the supported replacement for the
79
+ * `agent_compaction_*` keys of the deprecated `settings` bag). Every field is
80
+ * optional; an omitted field falls back to the kernel default.
81
+ * @property {boolean} [enabled] Whether auto-compaction runs at all.
82
+ * @property {number} [triggerRatio] Fraction of the context window that arms the proactive trigger.
83
+ * @property {number} [keepRecentTokens] Recent-token budget preserved across a compaction.
84
+ * @property {number} [summaryMaxTokens] Max tokens of the generated summary.
85
+ * @property {number} [minSavingsTokens] Minimum token savings required to keep a compaction.
86
+ * @property {boolean} [fixedOverheadEnabled] Whether the system-prompt + tool-schema overhead correction is folded into the trigger.
87
+ * @property {number} [contextWindowOverride] Forces the compaction window instead of the live-model-recognized one (applied at resolveLiveCompactionPolicy; has no legacy settings equivalent).
88
+ */
89
+
90
+ /**
91
+ * @typedef {Object} RuntimePromptOverrides
92
+ * Optional overrides for the kernel's built-in prompt fragments. Precedence is
93
+ * run over host over the kernel default: an absent field leaves the built-in
94
+ * string in place. Supplied on both AgentRuntimeHostOptions (host default) and
95
+ * RuntimeRunOptions (per-run).
96
+ * @property {(systemPrompt: string) => string} [structuredOutputInstruction]
97
+ * Replaces the StructuredOutput system-prompt instruction (receives the raw
98
+ * system prompt, returns the augmented one). Only applied when an outputSchema is active.
99
+ * @property {() => string} [structuredOutputFinalization]
100
+ * Replaces the structured-output finalization re-prompt.
101
+ * @property {(body: string) => string} [liveInputGuidance]
102
+ * Replaces the live-input steering wrapper (receives the raw guidance body).
103
+ */
104
+
105
+ /**
106
+ * @typedef {Object} RuntimeRunOptions
107
+ * The options object a host passes to `createRuntime(host).run(systemPrompt, options)`.
108
+ * @property {RuntimeModelRef} model Resolved model reference; see parseRuntimeModelReference.
109
+ * @property {string} [executionMode] "sdk" (default) or "cli"; selects which bridge variant handles the model.
110
+ * @property {boolean} [liveInput] Whether this run expects a live/streaming input channel.
111
+ * @property {ReadonlyArray<*>} [observers] Per-call observers (see RuntimeObserver) merged with host-level (createRuntime) observers.
112
+ * @property {(event: RuntimeEvent) => void} [onEvent]
113
+ * @property {ReadonlyArray<Object>} [messages]
18
114
  * @property {string} [effort]
19
115
  * @property {boolean} [fastMode]
20
116
  * @property {string} [cwd]
21
117
  * @property {Object<string, Object>} [mcpServers]
22
- * @property {Array<string>} [allowedTools]
23
- * @property {Array<string>} [disallowedTools]
118
+ * @property {ReadonlyArray<string>} [allowedTools]
119
+ * @property {ReadonlyArray<string>} [disallowedTools]
24
120
  * @property {string} [permissionMode]
25
121
  * @property {number} [maxTurns]
26
122
  * @property {Object} [outputSchema]
27
123
  * @property {string} [runArtifactDir]
28
124
  * @property {AbortSignal} [abortSignal]
29
- * @property {Object} [liveInput]
30
- * @property {Object} [settings]
125
+ * @property {import('../agent/sandbox-seam.js').SandboxPolicy} [sandboxPolicy] Per-run sandbox policy; merged monotonically with the host policy (see resolveSandboxPolicy, agent/tools/shared/tool-context.js).
126
+ * @property {import('../agent/sandbox-seam.js').RuntimeSandboxEngine} [sandboxEngine] Per-run concrete sandbox engine handed to the active sandbox implementation.
127
+ * @property {import('../agent/sandbox-seam.js').RuntimeSandbox} [sandbox] Per-run sandbox IMPLEMENTATION override; when set it enforces this run's tools instead of the host/ToolContext impl (precedence run > host > passthrough). Policy DATA still merges monotonically (I13); this overrides only the enforcing code.
128
+ * @property {RuntimeToolLimits} [toolLimits] Typed per-run tool-output limits (supported replacement for the deprecated `settings` tool keys).
129
+ * @property {RuntimeCompactionPolicy} [compaction] Typed per-run compaction policy (supported replacement for the deprecated `settings` compaction keys).
130
+ * @property {RuntimePromptOverrides} [prompts] Per-run prompt-fragment overrides (run wins over the host default).
131
+ * @property {Object} [settings] DEPRECATED. Legacy flat settings bag; consumed only as a per-group FALLBACK when the corresponding typed object (`toolLimits` / `compaction`) is absent. Consuming any key emits one `deprecated_settings_option` runtime_warning per run. Migrate via resolveRuntimePolicies (@mono-agent/runtime-adapter).
31
132
  * @property {Object} [nativeSubagents] Same-runtime teammate helpers exposed through native provider subagent surfaces.
32
- * @property {(event: RuntimeEvent) => void} [onEvent]
133
+ * @property {Object} [diagnosticsSeed] Set by createRouterRuntime (ai/runtime/router.js) with a `resume_snapshot` when
134
+ * failing over mid-chain; a host-level coordinator may relay it forward (see agent/transcript.js), not read by any
135
+ * bridge in this package today.
136
+ * @property {string} [systemPromptPrefix] Set by createRouterRuntime alongside diagnosticsSeed; the router also
137
+ * prepends the same text to the systemPrompt argument directly, so bridges that ignore this field still continue
138
+ * correctly.
33
139
  */
34
140
 
35
141
  /**
36
- * @typedef {Object} RuntimeEvent
37
- * @property {string} type
142
+ * @typedef {RuntimeRunOptions
143
+ * & Pick<AgentRuntimeHostOptions, "resolveCustomPricing" | "resolvePiApiKey" | "persistArtifact" | "onCompactionRecorded" | "onToolApprovalRequest" | "toolRiskTiers" | "approvalDefaultRiskTier" | "approvalTimeoutMs" | "approvalAlwaysAllowTools">
144
+ * & {runtimeBrand: import('../runtime-brand.js').RuntimeBrand, toolContext?: import('../agent/tools/shared/tool-context.js').ToolContext, observerHub: {emit: (event: RuntimeEvent) => void, flush: () => Promise<void>}}
145
+ * } RuntimeRequest
146
+ * The request shape a bridge's `execute(systemPrompt, req)` receives as its
147
+ * second (options) argument: the host-supplied RuntimeRunOptions, merged by
148
+ * createRuntime (runtime.js) with the bound HOST_KEYS host-integration
149
+ * callbacks, the resolved runtimeBrand, the per-instance toolContext (read by
150
+ * internal tool helpers; absent when a host drives a bridge directly without
151
+ * createRuntime), and the per-run observerHub (onEvent is overridden to the
152
+ * hub's emit). `systemPrompt` is passed positionally, not folded into this object.
38
153
  */
39
154
 
40
155
  /**
@@ -48,22 +163,140 @@
48
163
  * @property {number} [numTurns]
49
164
  * @property {string} [model]
50
165
  * @property {string} [effort]
51
- * @property {"claude" | "pi" | "codex"} [sdk]
166
+ * @property {RuntimeSdkId} [sdk]
52
167
  * @property {boolean} [cancelled]
53
168
  * @property {string|null} [error]
54
169
  * @property {Object|null} [errorDetails]
55
170
  * @property {string|null} [failureKind]
56
171
  * @property {string|null} [providerSessionId]
172
+ * @property {string|null} [stderrTail] Bounded stderr tail from a CLI-backed bridge; see createStderrTail (ai/failure.js).
57
173
  * @property {Array<Object>} [runtimeWarnings]
58
174
  * @property {Object} [diagnostics]
175
+ * @property {Object} [capabilitiesUsed]
176
+ * @property {Array<{model: RuntimeModelRef, failureKind: (string|null), requestId?: (string|null), retryableSubkind?: (string|null), requirements?: Object}>} [failoverHistory] Set by createRouterRuntime (ai/runtime/router.js) on every attempt after the first.
177
+ */
178
+
179
+ /**
180
+ * @typedef {Object} RuntimeCapabilities
181
+ * Capability flags a bridge reports for a given model reference. Structural,
182
+ * not sealed: bridges may report extra provider-specific flags (e.g.
183
+ * supports_fast_mode) alongside the common ones from COMMON_CAPABILITIES
184
+ * (ai/runtime/capabilities.js).
185
+ * @property {string} [kind]
186
+ * @property {string} [runtime]
187
+ * @property {boolean} [streaming]
188
+ * @property {boolean} [structured_output]
189
+ * @property {boolean} [supports_session_resume]
190
+ * @property {*} [native_runtime_config]
191
+ * @property {boolean} [supports_mcp]
192
+ * @property {boolean} [supports_skills]
193
+ * @property {boolean} [supports_builtin_tools]
194
+ * @property {boolean} [supports_live_input]
195
+ * @property {boolean} [supports_native_subagents]
196
+ * @property {boolean} [supports_fast_mode]
59
197
  */
60
198
 
61
199
  /**
62
200
  * @typedef {Object} RuntimeBridge
63
- * @property {"claude" | "pi" | "codex"} id
64
- * @property {(ref: RuntimeModelRef) => boolean} supports
65
- * @property {(ref?: RuntimeModelRef) => Object} capabilities
201
+ * A fully loaded bridge (registry.js's resolveRuntimeBridge result): the
202
+ * executable provider implementation. Unlike RuntimeBridgeDescriptor,
203
+ * `capabilities` here is the bridge module's own static value (computed once
204
+ * at module load, e.g. `runtimeCapabilities("pi")`), not a callable.
205
+ * @property {RuntimeBridgeId} id
206
+ * @property {(ref: RuntimeModelRef, options?: Object) => boolean} supports
207
+ * @property {RuntimeCapabilities} capabilities
66
208
  * @property {(systemPrompt: string, req: RuntimeRequest) => Promise<RuntimeResult>} execute
67
209
  */
68
210
 
211
+ /**
212
+ * @typedef {Object} RuntimeBridgeDescriptor
213
+ * The introspection-only projection of a RuntimeBridge (registry.js's
214
+ * listRuntimeBridges result): id/supports/capabilities without execute, so
215
+ * hosts can describe backend support without loading provider modules.
216
+ * @property {RuntimeBridgeId} id
217
+ * @property {(ref: RuntimeModelRef, options?: Object) => boolean} supports
218
+ * @property {(ref?: RuntimeModelRef) => RuntimeCapabilities} capabilities
219
+ */
220
+
221
+ /**
222
+ * @typedef {Object} ApprovalRequestPayload
223
+ * Payload passed to a host's `onToolApprovalRequest` callback (agent/approval.js).
224
+ * @property {string} requestId
225
+ * @property {string} toolName
226
+ * @property {string|null} toolUseId
227
+ * @property {string} argumentsSummary Secret-redacted, length-capped JSON summary of the tool call arguments.
228
+ * @property {"low"|"medium"|"high"} riskTier
229
+ * @property {string|null} model
230
+ */
231
+
232
+ /**
233
+ * @typedef {Object} ApprovalDecision
234
+ * A host's response to an ApprovalRequestPayload.
235
+ * @property {"approve"|"deny"|"always"} decision
236
+ * @property {string} [reason]
237
+ */
238
+
239
+ /**
240
+ * @typedef {Object} CompactionRecordedPayload
241
+ * Payload passed to a host's `onCompactionRecorded` callback (ai/providers/pi-native.js)
242
+ * after a successful context compaction, so the host can persist the row.
243
+ * @property {string|null} task_run_id
244
+ * @property {string} trigger
245
+ * @property {string} provider_kind
246
+ * @property {string|null} model
247
+ * @property {number|null} tokens_before
248
+ * @property {string} summary
249
+ * @property {string|null} first_kept_entry_id
250
+ * @property {"succeeded"} status
251
+ * @property {number} created_at
252
+ */
253
+
254
+ /**
255
+ * @typedef {Object} AgentRuntimeHostOptions
256
+ * The `host` object passed to `createRuntime(host)` / `createRouterRuntime({host, chain})`.
257
+ * Combines the tool-runtime keys (forwarded to configureToolRuntime) and the
258
+ * host-integration callbacks (bound once, applied to every run via hostDefaults).
259
+ * @property {string} [workspace]
260
+ * @property {string} [repoRoot]
261
+ * @property {string} [ripgrepPath]
262
+ * @property {string} [qaOutputDir]
263
+ * @property {import('../agent/sandbox-seam.js').SandboxPolicy} [sandboxPolicy]
264
+ * @property {import('../agent/sandbox-seam.js').RuntimeSandboxEngine} [sandboxEngine]
265
+ * @property {import('../agent/sandbox-seam.js').RuntimeSandbox} [sandbox] Sandbox seam implementation (see agent/sandbox-seam.js); defaults to the zero-dependency passthroughSandbox. Real hosts inject runtime-adapter's implementation from packages/runtime-adapter/src/sandbox.ts.
266
+ * @property {RuntimePromptOverrides} [prompts] Host-level prompt-fragment override defaults; a per-run `options.prompts` field wins over these (see resolvePrompts, runtime.js).
267
+ * @property {ReadonlyArray<*>} [observers] Observer instances (see RuntimeObserver); loose because observer.js is not a kernel seam file.
268
+ * @property {*} [runtimeBrand] See resolveRuntimeBrand (runtime-brand.js); accepts a partial RuntimeBrand.
269
+ * @property {(parsed: {sdk: (string|null), provider?: string, model: string}) => (import('./cost.js').NormalizedPricing|null)} [resolveCustomPricing] See resolvePricing (ai/cost.js).
270
+ * @property {import('../pi-auth.js').PiApiKeyResolver} [resolvePiApiKey] See createPiOAuthApiKeyResolver (pi-auth.js) for a ready-made implementation.
271
+ * @property {(artifact: {filename: string, buffer: Buffer, toolName: string, toolUseId: (string|null)}) => (string|null)} [persistArtifact]
272
+ * @property {(record: CompactionRecordedPayload) => void} [onCompactionRecorded]
273
+ * @property {(payload: ApprovalRequestPayload) => Promise<ApprovalDecision>} [onToolApprovalRequest]
274
+ * @property {Object<string, ("low"|"medium"|"high")>} [toolRiskTiers]
275
+ * @property {"low"|"medium"|"high"} [approvalDefaultRiskTier]
276
+ * @property {number} [approvalTimeoutMs]
277
+ * @property {ReadonlyArray<string>} [approvalAlwaysAllowTools]
278
+ */
279
+
280
+ /**
281
+ * @typedef {Object} AgentRuntimeToolOptions
282
+ * The subset of AgentRuntimeHostOptions accepted by `runtime.configureTools(next)`
283
+ * (createRuntime's TOOL_RUNTIME_KEYS pick).
284
+ * @property {string} [workspace]
285
+ * @property {string} [repoRoot]
286
+ * @property {string} [ripgrepPath]
287
+ * @property {string} [qaOutputDir]
288
+ * @property {import('../agent/sandbox-seam.js').SandboxPolicy} [sandboxPolicy]
289
+ * @property {import('../agent/sandbox-seam.js').RuntimeSandboxEngine} [sandboxEngine]
290
+ * @property {import('../agent/sandbox-seam.js').RuntimeSandbox} [sandbox]
291
+ */
292
+
293
+ /**
294
+ * @typedef {Object} AgentRuntimeInstance
295
+ * The object `createRuntime`/`createRouterRuntime` return.
296
+ * @property {(systemPrompt: string, options: RuntimeRunOptions) => Promise<RuntimeResult>} run
297
+ * @property {(next?: AgentRuntimeToolOptions) => void} configureTools
298
+ * @property {(providerSessionId: string) => Promise<boolean|void>} disposeSession
299
+ * @property {() => Promise<void>} disposeAllSessions
300
+ */
301
+
69
302
  export const PROVIDER_KIND_VALUES = ["claude", "pi", "codex"];
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // Public entry for @mono-agent/agent-runtime.
1
+ // Public entry for the agent-runtime package.
2
2
  //
3
3
  // Most consumers should reach for `createRuntime` (see runtime.js) — it
4
4
  // binds the host integration callbacks once and returns a `.run()` method.
package/src/pi-auth.js CHANGED
@@ -1,48 +1,174 @@
1
+ // @ts-check
2
+
3
+ import { realpathSync } from "node:fs";
1
4
  import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
- import { dirname } from "node:path";
5
+ import { basename, dirname, join, resolve } from "node:path";
3
6
 
4
7
  import { getOAuthApiKey } from "@earendil-works/pi-ai/oauth";
5
8
 
9
+ /**
10
+ * @typedef {{type: "api_key", key?: string, env?: Object<string, *>}} PiApiKeyCredential
11
+ * @typedef {{type: "oauth", access?: string, refresh?: string, expires: number, [key: string]: *}} PiOAuthCredential
12
+ * @typedef {PiApiKeyCredential|PiOAuthCredential} PiCredential
13
+ * @typedef {((provider: string) => Promise<string|undefined>) & {
14
+ * readCredential?: (provider: string) => Promise<PiCredential|undefined>,
15
+ * modifyCredential?: (provider: string, fn: (current: PiCredential|undefined) => Promise<PiCredential|undefined>) => Promise<PiCredential|undefined>,
16
+ * deleteCredential?: (provider: string) => Promise<void>
17
+ * }} PiApiKeyResolver
18
+ */
19
+
20
+ const authFileChains = new Map();
21
+
22
+ /**
23
+ * @param {Object} [options]
24
+ * @param {string} [options.path] Path to the pi auth.json credentials file.
25
+ * @returns {PiApiKeyResolver}
26
+ */
6
27
  export function createPiOAuthApiKeyResolver(options = {}) {
7
- const authPath = typeof options.path === "string" && options.path.trim().length > 0
28
+ const configuredAuthPath = typeof options.path === "string" && options.path.trim().length > 0
8
29
  ? options.path
9
30
  : undefined;
31
+ const authPath = configuredAuthPath ? canonicalizeAuthPath(configuredAuthPath) : undefined;
10
32
 
11
- return async function resolvePiOAuthApiKey(provider) {
33
+ async function resolvePiOAuthApiKey(provider) {
12
34
  if (!authPath || typeof provider !== "string" || provider.trim().length === 0) {
13
35
  return undefined;
14
36
  }
15
37
 
16
- const auth = await readAuthFile(authPath);
17
- if (auth === undefined || auth[provider] === undefined) {
38
+ return enqueueAuthFile(authPath, async () => {
39
+ const auth = await readAuthFile(authPath);
40
+ if (auth === undefined || auth[provider] === undefined) {
41
+ return undefined;
42
+ }
43
+
44
+ const result = await getOAuthApiKey(provider, cloneAuth(auth));
45
+ if (result === null || result === undefined || typeof result.apiKey !== "string" || result.apiKey.length === 0) {
46
+ return undefined;
47
+ }
48
+
49
+ auth[provider] = {
50
+ type: "oauth",
51
+ ...result.newCredentials,
52
+ };
53
+ await writeAuthFile(authPath, auth);
54
+ return result.apiKey;
55
+ });
56
+ }
57
+
58
+ resolvePiOAuthApiKey.readCredential = async function readCredential(provider) {
59
+ if (!authPath || typeof provider !== "string" || provider.trim().length === 0) {
18
60
  return undefined;
19
61
  }
62
+ const auth = await readAuthFile(authPath);
63
+ return cloneCredential(auth?.[provider]);
64
+ };
20
65
 
21
- const result = await getOAuthApiKey(provider, cloneAuth(auth));
22
- if (result === null || result === undefined || typeof result.apiKey !== "string" || result.apiKey.length === 0) {
66
+ resolvePiOAuthApiKey.modifyCredential = async function modifyCredential(provider, fn) {
67
+ if (!authPath || typeof provider !== "string" || provider.trim().length === 0) {
23
68
  return undefined;
24
69
  }
70
+ if (typeof fn !== "function") {
71
+ throw new TypeError("modifyCredential requires a function");
72
+ }
73
+ return enqueueAuthFile(authPath, async () => {
74
+ const auth = (await readAuthFile(authPath)) || {};
75
+ const current = cloneCredential(auth[provider]);
76
+ const next = await fn(current);
77
+ if (next !== undefined) {
78
+ auth[provider] = cloneCredential(next) || next;
79
+ await writeAuthFile(authPath, auth);
80
+ return cloneCredential(auth[provider]);
81
+ }
82
+ return current;
83
+ });
84
+ };
25
85
 
26
- auth[provider] = {
27
- type: "oauth",
28
- ...result.newCredentials,
29
- };
30
- await writeAuthFile(authPath, auth);
31
- return result.apiKey;
86
+ resolvePiOAuthApiKey.deleteCredential = async function deleteCredential(provider) {
87
+ if (!authPath || typeof provider !== "string" || provider.trim().length === 0) {
88
+ return;
89
+ }
90
+ await enqueueAuthFile(authPath, async () => {
91
+ const auth = await readAuthFile(authPath);
92
+ if (!auth || !Object.hasOwn(auth, provider)) return;
93
+ delete auth[provider];
94
+ await writeAuthFile(authPath, auth);
95
+ });
32
96
  };
97
+
98
+ return resolvePiOAuthApiKey;
99
+ }
100
+
101
+ /**
102
+ * @param {string} path
103
+ * @param {() => Promise<*>} task
104
+ * @returns {Promise<*>}
105
+ */
106
+ function enqueueAuthFile(path, task) {
107
+ const previous = authFileChains.get(path) ?? Promise.resolve();
108
+ const next = (async () => {
109
+ await previous.catch(() => {});
110
+ return task();
111
+ })();
112
+ authFileChains.set(path, next.catch(() => {}));
113
+ return next;
114
+ }
115
+
116
+ /**
117
+ * @param {string} path
118
+ * @returns {string}
119
+ */
120
+ function canonicalizeAuthPath(path) {
121
+ const resolved = resolve(path);
122
+ try {
123
+ return realpathSync.native(resolved);
124
+ } catch (error) {
125
+ if (!isMissingPathError(error)) {
126
+ return resolved;
127
+ }
128
+ }
129
+
130
+ try {
131
+ return join(realpathSync.native(dirname(resolved)), basename(resolved));
132
+ } catch {
133
+ return resolved;
134
+ }
33
135
  }
34
136
 
137
+ /**
138
+ * @param {*} error
139
+ * @returns {boolean}
140
+ */
141
+ function isMissingPathError(error) {
142
+ return error && (error.code === "ENOENT" || error.code === "ENOTDIR");
143
+ }
144
+
145
+ /**
146
+ * @param {Object<string, *>} auth
147
+ * @returns {Object<string, *>}
148
+ */
35
149
  function cloneAuth(auth) {
36
150
  return Object.fromEntries(
37
151
  Object.entries(auth).map(([provider, credentials]) => [
38
152
  provider,
39
- credentials && typeof credentials === "object" && !Array.isArray(credentials)
40
- ? { ...credentials }
41
- : credentials,
153
+ cloneCredential(credentials),
42
154
  ]),
43
155
  );
44
156
  }
45
157
 
158
+ /**
159
+ * @param {*} credential
160
+ * @returns {*}
161
+ */
162
+ function cloneCredential(credential) {
163
+ return credential && typeof credential === "object" && !Array.isArray(credential)
164
+ ? { ...credential }
165
+ : credential;
166
+ }
167
+
168
+ /**
169
+ * @param {string} path
170
+ * @returns {Promise<Object<string, *>|undefined>}
171
+ */
46
172
  async function readAuthFile(path) {
47
173
  let raw;
48
174
  try {
@@ -69,6 +195,11 @@ async function readAuthFile(path) {
69
195
  // writers in the same millisecond never collide on the temp path.
70
196
  let atomicWriteSequence = 0;
71
197
 
198
+ /**
199
+ * @param {string} path
200
+ * @param {Object<string, *>} auth
201
+ * @returns {Promise<void>}
202
+ */
72
203
  async function writeAuthFile(path, auth) {
73
204
  const dir = dirname(path);
74
205
  await mkdir(dir, { recursive: true, mode: 0o700 });
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  // Host-customisable identity strings used by the runtime when it has to
2
4
  // stamp a name onto something that leaves the process: MCP client name,
3
5
  // transcript-snapshot schema id, temp-directory prefix, the doctor command
@@ -7,6 +9,20 @@
7
9
  // to `createRuntime` to make the package look like theirs without forking
8
10
  // string-by-string.
9
11
 
12
+ /**
13
+ * @typedef {Object} RuntimeBrand
14
+ * @property {string} schemaPrefix
15
+ * @property {string} mcpClientName
16
+ * @property {string} mcpClientVersion
17
+ * @property {string} tempdirPrefix
18
+ * @property {string} providerModelPrefix
19
+ * @property {string} doctorCommand
20
+ * @property {string} serviceName
21
+ * @property {string} clientInfoName
22
+ * @property {string} clientInfoTitle
23
+ */
24
+
25
+ /** @type {RuntimeBrand} */
10
26
  export const DEFAULT_RUNTIME_BRAND = Object.freeze({
11
27
  schemaPrefix: "agent_runtime",
12
28
  mcpClientName: "agent-runtime",
@@ -21,10 +37,14 @@ export const DEFAULT_RUNTIME_BRAND = Object.freeze({
21
37
  clientInfoTitle: "Agent Runtime",
22
38
  });
23
39
 
40
+ /**
41
+ * @param {Partial<RuntimeBrand>} [input]
42
+ * @returns {RuntimeBrand}
43
+ */
24
44
  export function resolveRuntimeBrand(input) {
25
45
  if (!input || typeof input !== "object") return { ...DEFAULT_RUNTIME_BRAND };
26
46
  const out = { ...DEFAULT_RUNTIME_BRAND };
27
- for (const key of Object.keys(DEFAULT_RUNTIME_BRAND)) {
47
+ for (const key of /** @type {Array<keyof RuntimeBrand>} */ (Object.keys(DEFAULT_RUNTIME_BRAND))) {
28
48
  const value = input[key];
29
49
  if (typeof value === "string" && value.trim()) out[key] = value.trim();
30
50
  }