@archmax-ai/harness 0.0.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.
Files changed (351) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/dist/agent.d.ts +174 -0
  4. package/dist/agent.js +140 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/assembly/compose.d.ts +170 -0
  7. package/dist/assembly/compose.js +391 -0
  8. package/dist/assembly/compose.js.map +1 -0
  9. package/dist/assembly/delegation-registry.d.ts +5 -0
  10. package/dist/assembly/delegation-registry.js +118 -0
  11. package/dist/assembly/delegation-registry.js.map +1 -0
  12. package/dist/assembly/index.d.ts +192 -0
  13. package/dist/assembly/index.js +265 -0
  14. package/dist/assembly/index.js.map +1 -0
  15. package/dist/assembly/plain.d.ts +7 -0
  16. package/dist/assembly/plain.js +41 -0
  17. package/dist/assembly/plain.js.map +1 -0
  18. package/dist/authoring-skill/archmax-harness/README.md +76 -0
  19. package/dist/authoring-skill/archmax-harness/SKILL.md +241 -0
  20. package/dist/authoring-skill/archmax-harness/references/backend-integration.md +626 -0
  21. package/dist/authoring-skill/archmax-harness/references/cases.md +41 -0
  22. package/dist/authoring-skill/archmax-harness/references/diagnostics.md +51 -0
  23. package/dist/authoring-skill/archmax-harness/references/hook-and-test-scripts.md +1070 -0
  24. package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +82 -0
  25. package/dist/authoring-skill/archmax-harness/references/wiring.md +101 -0
  26. package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +1631 -0
  27. package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +241 -0
  28. package/dist/behaviour/support.d.ts +232 -0
  29. package/dist/behaviour/support.js +339 -0
  30. package/dist/behaviour/support.js.map +1 -0
  31. package/dist/cli/banner.d.ts +11 -0
  32. package/dist/cli/banner.js +29 -0
  33. package/dist/cli/banner.js.map +1 -0
  34. package/dist/cli/bootstrap.d.ts +33 -0
  35. package/dist/cli/bootstrap.js +153 -0
  36. package/dist/cli/bootstrap.js.map +1 -0
  37. package/dist/cli/command.d.ts +39 -0
  38. package/dist/cli/command.js +97 -0
  39. package/dist/cli/command.js.map +1 -0
  40. package/dist/cli/state-flow.d.ts +46 -0
  41. package/dist/cli/state-flow.js +268 -0
  42. package/dist/cli/state-flow.js.map +1 -0
  43. package/dist/cli/style.d.ts +41 -0
  44. package/dist/cli/style.js +64 -0
  45. package/dist/cli/style.js.map +1 -0
  46. package/dist/cli/turn.d.ts +41 -0
  47. package/dist/cli/turn.js +161 -0
  48. package/dist/cli/turn.js.map +1 -0
  49. package/dist/cli.d.ts +10 -0
  50. package/dist/cli.js +426 -0
  51. package/dist/cli.js.map +1 -0
  52. package/dist/core/code-description.d.ts +22 -0
  53. package/dist/core/code-description.js +38 -0
  54. package/dist/core/code-description.js.map +1 -0
  55. package/dist/core/deepagents.d.ts +183 -0
  56. package/dist/core/deepagents.js +128 -0
  57. package/dist/core/deepagents.js.map +1 -0
  58. package/dist/core/events.d.ts +350 -0
  59. package/dist/core/events.js +205 -0
  60. package/dist/core/events.js.map +1 -0
  61. package/dist/core/frontmatter.d.ts +15 -0
  62. package/dist/core/frontmatter.js +32 -0
  63. package/dist/core/frontmatter.js.map +1 -0
  64. package/dist/core/listing-redact.d.ts +37 -0
  65. package/dist/core/listing-redact.js +88 -0
  66. package/dist/core/listing-redact.js.map +1 -0
  67. package/dist/core/match.d.ts +26 -0
  68. package/dist/core/match.js +75 -0
  69. package/dist/core/match.js.map +1 -0
  70. package/dist/core/message-readers.d.ts +84 -0
  71. package/dist/core/message-readers.js +159 -0
  72. package/dist/core/message-readers.js.map +1 -0
  73. package/dist/core/messages.d.ts +49 -0
  74. package/dist/core/messages.js +85 -0
  75. package/dist/core/messages.js.map +1 -0
  76. package/dist/core/mount-prompt.d.ts +28 -0
  77. package/dist/core/mount-prompt.js +55 -0
  78. package/dist/core/mount-prompt.js.map +1 -0
  79. package/dist/core/mount-redact.d.ts +28 -0
  80. package/dist/core/mount-redact.js +32 -0
  81. package/dist/core/mount-redact.js.map +1 -0
  82. package/dist/core/mounts.d.ts +88 -0
  83. package/dist/core/mounts.js +147 -0
  84. package/dist/core/mounts.js.map +1 -0
  85. package/dist/core/path-mapping.d.ts +52 -0
  86. package/dist/core/path-mapping.js +113 -0
  87. package/dist/core/path-mapping.js.map +1 -0
  88. package/dist/core/platform-prompt.generated.d.ts +2 -0
  89. package/dist/core/platform-prompt.generated.js +4 -0
  90. package/dist/core/platform-prompt.generated.js.map +1 -0
  91. package/dist/core/prompt.d.ts +42 -0
  92. package/dist/core/prompt.js +75 -0
  93. package/dist/core/prompt.js.map +1 -0
  94. package/dist/core/session-id.d.ts +38 -0
  95. package/dist/core/session-id.js +61 -0
  96. package/dist/core/session-id.js.map +1 -0
  97. package/dist/core/session-store.d.ts +85 -0
  98. package/dist/core/session-store.js +149 -0
  99. package/dist/core/session-store.js.map +1 -0
  100. package/dist/core/session-zone.d.ts +54 -0
  101. package/dist/core/session-zone.js +82 -0
  102. package/dist/core/session-zone.js.map +1 -0
  103. package/dist/core/skill-prompt.d.ts +31 -0
  104. package/dist/core/skill-prompt.js +54 -0
  105. package/dist/core/skill-prompt.js.map +1 -0
  106. package/dist/core/skill-redact.d.ts +15 -0
  107. package/dist/core/skill-redact.js +27 -0
  108. package/dist/core/skill-redact.js.map +1 -0
  109. package/dist/core/skills.d.ts +172 -0
  110. package/dist/core/skills.js +214 -0
  111. package/dist/core/skills.js.map +1 -0
  112. package/dist/core/tool-mocks.d.ts +23 -0
  113. package/dist/core/tool-mocks.js +39 -0
  114. package/dist/core/tool-mocks.js.map +1 -0
  115. package/dist/core/tool-telemetry.d.ts +16 -0
  116. package/dist/core/tool-telemetry.js +40 -0
  117. package/dist/core/tool-telemetry.js.map +1 -0
  118. package/dist/core/usage.d.ts +114 -0
  119. package/dist/core/usage.js +226 -0
  120. package/dist/core/usage.js.map +1 -0
  121. package/dist/core/workspace-context.d.ts +136 -0
  122. package/dist/core/workspace-context.js +178 -0
  123. package/dist/core/workspace-context.js.map +1 -0
  124. package/dist/core/workspace-prompt.d.ts +7 -0
  125. package/dist/core/workspace-prompt.js +102 -0
  126. package/dist/core/workspace-prompt.js.map +1 -0
  127. package/dist/core/workspace-router.d.ts +61 -0
  128. package/dist/core/workspace-router.js +145 -0
  129. package/dist/core/workspace-router.js.map +1 -0
  130. package/dist/core/workspace.d.ts +85 -0
  131. package/dist/core/workspace.js +193 -0
  132. package/dist/core/workspace.js.map +1 -0
  133. package/dist/core/zones.d.ts +181 -0
  134. package/dist/core/zones.js +202 -0
  135. package/dist/core/zones.js.map +1 -0
  136. package/dist/env.d.ts +99 -0
  137. package/dist/env.js +271 -0
  138. package/dist/env.js.map +1 -0
  139. package/dist/index.d.ts +265 -0
  140. package/dist/index.js +197 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/kernel/kernel.d.ts +192 -0
  143. package/dist/kernel/kernel.js +751 -0
  144. package/dist/kernel/kernel.js.map +1 -0
  145. package/dist/lifecycle/hook-shape.d.ts +44 -0
  146. package/dist/lifecycle/hook-shape.js +66 -0
  147. package/dist/lifecycle/hook-shape.js.map +1 -0
  148. package/dist/lifecycle/runner.d.ts +177 -0
  149. package/dist/lifecycle/runner.js +372 -0
  150. package/dist/lifecycle/runner.js.map +1 -0
  151. package/dist/lifecycle/transcript.d.ts +27 -0
  152. package/dist/lifecycle/transcript.js +43 -0
  153. package/dist/lifecycle/transcript.js.map +1 -0
  154. package/dist/machine/allow.d.ts +65 -0
  155. package/dist/machine/allow.js +107 -0
  156. package/dist/machine/allow.js.map +1 -0
  157. package/dist/machine/delegation.d.ts +9 -0
  158. package/dist/machine/delegation.js +10 -0
  159. package/dist/machine/delegation.js.map +1 -0
  160. package/dist/machine/diagnostic.d.ts +14 -0
  161. package/dist/machine/diagnostic.js +2 -0
  162. package/dist/machine/diagnostic.js.map +1 -0
  163. package/dist/machine/lint-spec.d.ts +4 -0
  164. package/dist/machine/lint-spec.js +266 -0
  165. package/dist/machine/lint-spec.js.map +1 -0
  166. package/dist/machine/load-spec.d.ts +90 -0
  167. package/dist/machine/load-spec.js +120 -0
  168. package/dist/machine/load-spec.js.map +1 -0
  169. package/dist/machine/machine.d.ts +273 -0
  170. package/dist/machine/machine.js +551 -0
  171. package/dist/machine/machine.js.map +1 -0
  172. package/dist/machine/mount-grants.d.ts +25 -0
  173. package/dist/machine/mount-grants.js +24 -0
  174. package/dist/machine/mount-grants.js.map +1 -0
  175. package/dist/machine/slug.d.ts +14 -0
  176. package/dist/machine/slug.js +17 -0
  177. package/dist/machine/slug.js.map +1 -0
  178. package/dist/machine/spec-schema.d.ts +662 -0
  179. package/dist/machine/spec-schema.js +717 -0
  180. package/dist/machine/spec-schema.js.map +1 -0
  181. package/dist/machine/tool-names.d.ts +98 -0
  182. package/dist/machine/tool-names.js +144 -0
  183. package/dist/machine/tool-names.js.map +1 -0
  184. package/dist/machine/triggers.d.ts +114 -0
  185. package/dist/machine/triggers.js +165 -0
  186. package/dist/machine/triggers.js.map +1 -0
  187. package/dist/machine/types.d.ts +186 -0
  188. package/dist/machine/types.js +12 -0
  189. package/dist/machine/types.js.map +1 -0
  190. package/dist/machine/validate-spec.d.ts +42 -0
  191. package/dist/machine/validate-spec.js +29 -0
  192. package/dist/machine/validate-spec.js.map +1 -0
  193. package/dist/machine/variables.d.ts +196 -0
  194. package/dist/machine/variables.js +415 -0
  195. package/dist/machine/variables.js.map +1 -0
  196. package/dist/public/cli.d.ts +15 -0
  197. package/dist/public/cli.js +14 -0
  198. package/dist/public/cli.js.map +1 -0
  199. package/dist/public/messages.d.ts +33 -0
  200. package/dist/public/messages.js +33 -0
  201. package/dist/public/messages.js.map +1 -0
  202. package/dist/public/sandbox.d.ts +99 -0
  203. package/dist/public/sandbox.js +32 -0
  204. package/dist/public/sandbox.js.map +1 -0
  205. package/dist/public/spec.d.ts +147 -0
  206. package/dist/public/spec.js +149 -0
  207. package/dist/public/spec.js.map +1 -0
  208. package/dist/public/testing.d.ts +71 -0
  209. package/dist/public/testing.js +39 -0
  210. package/dist/public/testing.js.map +1 -0
  211. package/dist/rubrics/rubrics.d.ts +135 -0
  212. package/dist/rubrics/rubrics.js +145 -0
  213. package/dist/rubrics/rubrics.js.map +1 -0
  214. package/dist/runtime/contract.d.ts +73 -0
  215. package/dist/runtime/contract.js +96 -0
  216. package/dist/runtime/contract.js.map +1 -0
  217. package/dist/sandbox/assets/parts/core.js +22 -0
  218. package/dist/sandbox/assets/parts/lifecycle-hook.js +47 -0
  219. package/dist/sandbox/bridge.d.ts +16 -0
  220. package/dist/sandbox/bridge.js +95 -0
  221. package/dist/sandbox/bridge.js.map +1 -0
  222. package/dist/sandbox/executor.d.ts +46 -0
  223. package/dist/sandbox/executor.js +119 -0
  224. package/dist/sandbox/executor.js.map +1 -0
  225. package/dist/sandbox/imports.d.ts +33 -0
  226. package/dist/sandbox/imports.js +67 -0
  227. package/dist/sandbox/imports.js.map +1 -0
  228. package/dist/sandbox/prelude.d.ts +12 -0
  229. package/dist/sandbox/prelude.js +51 -0
  230. package/dist/sandbox/prelude.js.map +1 -0
  231. package/dist/sandbox/ptc-gateway.d.ts +104 -0
  232. package/dist/sandbox/ptc-gateway.js +167 -0
  233. package/dist/sandbox/ptc-gateway.js.map +1 -0
  234. package/dist/sandbox/runtime.d.ts +59 -0
  235. package/dist/sandbox/runtime.js +51 -0
  236. package/dist/sandbox/runtime.js.map +1 -0
  237. package/dist/sandbox/tools.d.ts +34 -0
  238. package/dist/sandbox/tools.js +147 -0
  239. package/dist/sandbox/tools.js.map +1 -0
  240. package/dist/sessions/operations.d.ts +79 -0
  241. package/dist/sessions/operations.js +121 -0
  242. package/dist/sessions/operations.js.map +1 -0
  243. package/dist/sessions/resolve.d.ts +97 -0
  244. package/dist/sessions/resolve.js +145 -0
  245. package/dist/sessions/resolve.js.map +1 -0
  246. package/dist/sessions/resume.d.ts +242 -0
  247. package/dist/sessions/resume.js +288 -0
  248. package/dist/sessions/resume.js.map +1 -0
  249. package/dist/sessions/scope.d.ts +99 -0
  250. package/dist/sessions/scope.js +145 -0
  251. package/dist/sessions/scope.js.map +1 -0
  252. package/dist/sessions/summary.d.ts +65 -0
  253. package/dist/sessions/summary.js +56 -0
  254. package/dist/sessions/summary.js.map +1 -0
  255. package/dist/testing/assertions.d.ts +30 -0
  256. package/dist/testing/assertions.js +231 -0
  257. package/dist/testing/assertions.js.map +1 -0
  258. package/dist/testing/case-document.d.ts +141 -0
  259. package/dist/testing/case-document.js +51 -0
  260. package/dist/testing/case-document.js.map +1 -0
  261. package/dist/testing/case-schema.d.ts +29 -0
  262. package/dist/testing/case-schema.js +519 -0
  263. package/dist/testing/case-schema.js.map +1 -0
  264. package/dist/testing/discovery.d.ts +10 -0
  265. package/dist/testing/discovery.js +24 -0
  266. package/dist/testing/discovery.js.map +1 -0
  267. package/dist/testing/driver.d.ts +78 -0
  268. package/dist/testing/driver.js +117 -0
  269. package/dist/testing/driver.js.map +1 -0
  270. package/dist/testing/grade.d.ts +116 -0
  271. package/dist/testing/grade.js +261 -0
  272. package/dist/testing/grade.js.map +1 -0
  273. package/dist/testing/interpreter.d.ts +55 -0
  274. package/dist/testing/interpreter.js +103 -0
  275. package/dist/testing/interpreter.js.map +1 -0
  276. package/dist/testing/mock-middleware.d.ts +5 -0
  277. package/dist/testing/mock-middleware.js +33 -0
  278. package/dist/testing/mock-middleware.js.map +1 -0
  279. package/dist/testing/runner.d.ts +205 -0
  280. package/dist/testing/runner.js +266 -0
  281. package/dist/testing/runner.js.map +1 -0
  282. package/dist/testing/target.d.ts +27 -0
  283. package/dist/testing/target.js +21 -0
  284. package/dist/testing/target.js.map +1 -0
  285. package/dist/validate/validate.d.ts +41 -0
  286. package/dist/validate/validate.js +669 -0
  287. package/dist/validate/validate.js.map +1 -0
  288. package/dist/workflow/agent-tools.d.ts +10 -0
  289. package/dist/workflow/agent-tools.js +10 -0
  290. package/dist/workflow/agent-tools.js.map +1 -0
  291. package/dist/workflow/checkpointer.d.ts +52 -0
  292. package/dist/workflow/checkpointer.js +200 -0
  293. package/dist/workflow/checkpointer.js.map +1 -0
  294. package/dist/workflow/control-tools.d.ts +174 -0
  295. package/dist/workflow/control-tools.js +468 -0
  296. package/dist/workflow/control-tools.js.map +1 -0
  297. package/dist/workflow/governance.d.ts +104 -0
  298. package/dist/workflow/governance.js +546 -0
  299. package/dist/workflow/governance.js.map +1 -0
  300. package/dist/workflow/middleware.d.ts +85 -0
  301. package/dist/workflow/middleware.js +283 -0
  302. package/dist/workflow/middleware.js.map +1 -0
  303. package/dist/workflow/on-error.d.ts +25 -0
  304. package/dist/workflow/on-error.js +58 -0
  305. package/dist/workflow/on-error.js.map +1 -0
  306. package/dist/workflow/parks.d.ts +46 -0
  307. package/dist/workflow/parks.js +513 -0
  308. package/dist/workflow/parks.js.map +1 -0
  309. package/dist/workflow/paths.d.ts +67 -0
  310. package/dist/workflow/paths.js +108 -0
  311. package/dist/workflow/paths.js.map +1 -0
  312. package/dist/workflow/prompt-cache.d.ts +70 -0
  313. package/dist/workflow/prompt-cache.js +103 -0
  314. package/dist/workflow/prompt-cache.js.map +1 -0
  315. package/dist/workflow/prompt-pruning.d.ts +20 -0
  316. package/dist/workflow/prompt-pruning.js +83 -0
  317. package/dist/workflow/prompt-pruning.js.map +1 -0
  318. package/dist/workflow/render-prompt.d.ts +45 -0
  319. package/dist/workflow/render-prompt.js +140 -0
  320. package/dist/workflow/render-prompt.js.map +1 -0
  321. package/dist/workflow/runtime-config.d.ts +7 -0
  322. package/dist/workflow/runtime-config.js +9 -0
  323. package/dist/workflow/runtime-config.js.map +1 -0
  324. package/dist/workflow/session-artifacts.d.ts +90 -0
  325. package/dist/workflow/session-artifacts.js +134 -0
  326. package/dist/workflow/session-artifacts.js.map +1 -0
  327. package/dist/workflow/session-view.d.ts +31 -0
  328. package/dist/workflow/session-view.js +94 -0
  329. package/dist/workflow/session-view.js.map +1 -0
  330. package/dist/workflow/snapshot.d.ts +14 -0
  331. package/dist/workflow/snapshot.js +97 -0
  332. package/dist/workflow/snapshot.js.map +1 -0
  333. package/dist/workflow/state.d.ts +347 -0
  334. package/dist/workflow/state.js +322 -0
  335. package/dist/workflow/state.js.map +1 -0
  336. package/dist/workflow/sub-workflow.d.ts +174 -0
  337. package/dist/workflow/sub-workflow.js +392 -0
  338. package/dist/workflow/sub-workflow.js.map +1 -0
  339. package/dist/workflow/tool-service.d.ts +48 -0
  340. package/dist/workflow/tool-service.js +291 -0
  341. package/dist/workflow/tool-service.js.map +1 -0
  342. package/dist/workflow/turn-boundary.d.ts +39 -0
  343. package/dist/workflow/turn-boundary.js +128 -0
  344. package/dist/workflow/turn-boundary.js.map +1 -0
  345. package/dist/workflow/turn-runner.d.ts +40 -0
  346. package/dist/workflow/turn-runner.js +122 -0
  347. package/dist/workflow/turn-runner.js.map +1 -0
  348. package/dist/workflow/workflow-tools.d.ts +46 -0
  349. package/dist/workflow/workflow-tools.js +90 -0
  350. package/dist/workflow/workflow-tools.js.map +1 -0
  351. package/package.json +119 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Token accounting: what a session cost, in tokens and (when priced) in money.
3
+ *
4
+ * Providers report usage on each AI message as LangChain `usage_metadata`. This
5
+ * module is the single place that reads those shapes (including the cached-prefix
6
+ * detail that makes prompt caching observable), sums them, and turns them into a
7
+ * cost using a caller-supplied price table. Cost is **omitted, never guessed**:
8
+ * an unpriced model reports tokens only, so a number on screen is always a
9
+ * number someone configured.
10
+ */
11
+ import type { WorkflowEventHandler } from "./events.js";
12
+ /** Tokens consumed by one model call, one turn, or a whole session. */
13
+ export interface TokenUsage {
14
+ /** Total input tokens; the two cache counts below are a breakdown of it, not a peer. */
15
+ inputTokens: number;
16
+ outputTokens: number;
17
+ /** Input tokens served from the provider's prompt cache (billed at a discount). */
18
+ cacheReadTokens: number;
19
+ /** Input tokens written to the provider's prompt cache (billed at a premium). */
20
+ cacheCreationTokens: number;
21
+ }
22
+ /** A run's usage plus its cost when the model is priced. */
23
+ export interface UsageSummary extends TokenUsage {
24
+ costUsd?: number;
25
+ }
26
+ /** Prices in USD per 1,000,000 tokens. Omitted rates contribute nothing. */
27
+ export interface ModelPricing {
28
+ input?: number;
29
+ output?: number;
30
+ /** Cache-read rate (Anthropic bills ~10% of input). */
31
+ cacheRead?: number;
32
+ /** Cache-write rate (Anthropic bills ~125% of input). */
33
+ cacheWrite?: number;
34
+ }
35
+ /**
36
+ * Price table keyed by model id. Lookup is: exact id, then the longest key that
37
+ * is a substring of the id (so `claude-sonnet` prices
38
+ * `anthropic/claude-sonnet-4.6`), then the `default` key.
39
+ */
40
+ export type PricingTable = Record<string, ModelPricing>;
41
+ /** The key used when a price applies to whatever model is configured. */
42
+ export declare const DEFAULT_PRICING_KEY = "default";
43
+ /** A zeroed usage record. */
44
+ export declare function emptyUsage(): TokenUsage;
45
+ /** Sum two usage records into a new one. */
46
+ export declare function addUsage(a: TokenUsage, b: TokenUsage): TokenUsage;
47
+ /** Whether any counter is non-zero. */
48
+ export declare function hasUsage(usage: TokenUsage): boolean;
49
+ /**
50
+ * The provider shapes this reads, in order of preference:
51
+ * - LangChain normalized: `usage_metadata.input_token_details.{cache_read,cache_creation}`
52
+ * - OpenAI-compatible raw: `response_metadata.usage.prompt_tokens_details.cached_tokens`
53
+ * - Anthropic raw: `…usage.{cache_read_input_tokens,cache_creation_input_tokens}`
54
+ *
55
+ * `inputTokens` is the **total** input count, of which `cacheReadTokens` and
56
+ * `cacheCreationTokens` are a breakdown — LangChain's own `usage_metadata`
57
+ * contract (`input_tokens` is the "sum of all input token types"), and what the
58
+ * OpenAI-compatible raw shape reports too (`prompt_tokens` includes
59
+ * `prompt_tokens_details.cached_tokens`). Anthropic's raw shape is the one
60
+ * exception: its `usage.input_tokens` counts only what was neither read from nor
61
+ * written to cache, so the cache counts are folded back in. One invariant then
62
+ * holds downstream whatever the provider sent, which is what lets
63
+ * {@link computeCostUsd} price without a per-provider branch.
64
+ */
65
+ export declare function extractUsage(message: unknown): {
66
+ usage: TokenUsage;
67
+ model?: string;
68
+ } | null;
69
+ /**
70
+ * Resolve the price entry for `model`: exact id, then the longest substring key,
71
+ * then `default`. Returns `undefined` when nothing matches — the caller must
72
+ * then omit cost rather than assume free.
73
+ */
74
+ export declare function resolveModelPricing(table: PricingTable | undefined, model: string | undefined): ModelPricing | undefined;
75
+ /**
76
+ * Cost in USD for `usage` under `pricing`, or `undefined` when no rate applies.
77
+ *
78
+ * The cache counts are a breakdown of `inputTokens`, not a peer of it (see
79
+ * {@link extractUsage}), so the input rate applies to the **remainder** — what
80
+ * was neither served from nor written to cache — and each cache rate to its own
81
+ * count. Charging the whole input count *and* the cache counts on top would bill
82
+ * a cached token twice, which on a warm prompt-caching workload is most of the
83
+ * bill. A missing cache rate falls back to the input rate for reads and writes,
84
+ * since every provider prices cached tokens relative to input.
85
+ */
86
+ export declare function computeCostUsd(usage: TokenUsage, pricing: ModelPricing | undefined): number | undefined;
87
+ /** Build a summary: usage plus cost when the model is priced. */
88
+ export declare function summarizeUsage(usage: TokenUsage, table: PricingTable | undefined, model: string | undefined): UsageSummary;
89
+ /**
90
+ * Sum two summaries, tokens and cost together. Cost is present only when at
91
+ * least one side has it — summing an unpriced summary must not invent a `0`.
92
+ * This is the single place summaries are combined (the tracker's cross-session
93
+ * totals and the CLI's per-test-case totals both use it).
94
+ */
95
+ export declare function addSummaries(a: UsageSummary, b: UsageSummary): UsageSummary;
96
+ /** Human-readable one-liner, e.g. `in 12,004 · out 318 · cache r 9,801/w 0 · $0.0421`. */
97
+ export declare function formatUsage(summary: UsageSummary): string;
98
+ export interface UsageTracker {
99
+ /**
100
+ * Event handler to pass as the runtime `onEvent` (it forwards to the
101
+ * `onEvent` given at construction, when any).
102
+ */
103
+ handler: WorkflowEventHandler;
104
+ /** Totals for one session, or across every observed session when omitted. */
105
+ totals(sessionId?: string): UsageSummary;
106
+ /** Per-session totals, keyed by session id. */
107
+ bySession(): Map<string, UsageSummary>;
108
+ /** Drop accumulated totals for one session, or for all of them. */
109
+ reset(sessionId?: string): void;
110
+ }
111
+ /** Create a tracker that accumulates usage and cost from the event stream. */
112
+ export declare function createUsageTracker(options?: {
113
+ onEvent?: WorkflowEventHandler;
114
+ }): UsageTracker;
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Token accounting: what a session cost, in tokens and (when priced) in money.
3
+ *
4
+ * Providers report usage on each AI message as LangChain `usage_metadata`. This
5
+ * module is the single place that reads those shapes (including the cached-prefix
6
+ * detail that makes prompt caching observable), sums them, and turns them into a
7
+ * cost using a caller-supplied price table. Cost is **omitted, never guessed**:
8
+ * an unpriced model reports tokens only, so a number on screen is always a
9
+ * number someone configured.
10
+ */
11
+ /** The key used when a price applies to whatever model is configured. */
12
+ export const DEFAULT_PRICING_KEY = "default";
13
+ /** A zeroed usage record. */
14
+ export function emptyUsage() {
15
+ return { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };
16
+ }
17
+ /** Sum two usage records into a new one. */
18
+ export function addUsage(a, b) {
19
+ return {
20
+ inputTokens: a.inputTokens + b.inputTokens,
21
+ outputTokens: a.outputTokens + b.outputTokens,
22
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
23
+ cacheCreationTokens: a.cacheCreationTokens + b.cacheCreationTokens,
24
+ };
25
+ }
26
+ /** Whether any counter is non-zero. */
27
+ export function hasUsage(usage) {
28
+ return (usage.inputTokens > 0 ||
29
+ usage.outputTokens > 0 ||
30
+ usage.cacheReadTokens > 0 ||
31
+ usage.cacheCreationTokens > 0);
32
+ }
33
+ function num(value) {
34
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
35
+ }
36
+ /**
37
+ * The provider shapes this reads, in order of preference:
38
+ * - LangChain normalized: `usage_metadata.input_token_details.{cache_read,cache_creation}`
39
+ * - OpenAI-compatible raw: `response_metadata.usage.prompt_tokens_details.cached_tokens`
40
+ * - Anthropic raw: `…usage.{cache_read_input_tokens,cache_creation_input_tokens}`
41
+ *
42
+ * `inputTokens` is the **total** input count, of which `cacheReadTokens` and
43
+ * `cacheCreationTokens` are a breakdown — LangChain's own `usage_metadata`
44
+ * contract (`input_tokens` is the "sum of all input token types"), and what the
45
+ * OpenAI-compatible raw shape reports too (`prompt_tokens` includes
46
+ * `prompt_tokens_details.cached_tokens`). Anthropic's raw shape is the one
47
+ * exception: its `usage.input_tokens` counts only what was neither read from nor
48
+ * written to cache, so the cache counts are folded back in. One invariant then
49
+ * holds downstream whatever the provider sent, which is what lets
50
+ * {@link computeCostUsd} price without a per-provider branch.
51
+ */
52
+ export function extractUsage(message) {
53
+ if (typeof message !== "object" || message === null)
54
+ return null;
55
+ const meta = message.usage_metadata;
56
+ const response = message.response_metadata;
57
+ const raw = (response?.usage ?? response?.token_usage);
58
+ if (!meta && !raw)
59
+ return null;
60
+ const details = (meta?.input_token_details ?? {});
61
+ const promptDetails = (raw?.prompt_tokens_details ?? {});
62
+ const cacheReadTokens = num(details.cache_read) ||
63
+ num(promptDetails.cached_tokens) ||
64
+ num(raw?.cache_read_input_tokens);
65
+ const cacheCreationTokens = num(details.cache_creation) || num(raw?.cache_creation_input_tokens);
66
+ // Which shape reported the input count decides whether the cache counts are
67
+ // already inside it. Read from the keys rather than from a zero, so a prompt
68
+ // served entirely from cache is classified the same way as any other.
69
+ const reportsNumber = (record, key) => typeof record?.[key] === "number";
70
+ const isAnthropicRaw = !reportsNumber(meta, "input_tokens") &&
71
+ !reportsNumber(raw, "prompt_tokens") &&
72
+ reportsNumber(raw, "input_tokens");
73
+ const reportedInput = num(meta?.input_tokens) || num(raw?.prompt_tokens) || num(raw?.input_tokens);
74
+ const usage = {
75
+ inputTokens: isAnthropicRaw
76
+ ? reportedInput + cacheReadTokens + cacheCreationTokens
77
+ : reportedInput,
78
+ outputTokens: num(meta?.output_tokens) || num(raw?.completion_tokens) || num(raw?.output_tokens),
79
+ cacheReadTokens,
80
+ cacheCreationTokens,
81
+ };
82
+ const model = typeof response?.model_name === "string"
83
+ ? response.model_name
84
+ : typeof response?.model === "string"
85
+ ? response.model
86
+ : undefined;
87
+ return { usage, ...(model ? { model } : {}) };
88
+ }
89
+ /**
90
+ * Resolve the price entry for `model`: exact id, then the longest substring key,
91
+ * then `default`. Returns `undefined` when nothing matches — the caller must
92
+ * then omit cost rather than assume free.
93
+ */
94
+ export function resolveModelPricing(table, model) {
95
+ if (!table)
96
+ return undefined;
97
+ if (model) {
98
+ if (table[model])
99
+ return table[model];
100
+ const keys = Object.keys(table)
101
+ .filter((key) => key !== DEFAULT_PRICING_KEY && model.includes(key))
102
+ .sort((a, b) => b.length - a.length);
103
+ if (keys.length > 0)
104
+ return table[keys[0]];
105
+ }
106
+ return table[DEFAULT_PRICING_KEY];
107
+ }
108
+ const PER_MILLION = 1_000_000;
109
+ /**
110
+ * Cost in USD for `usage` under `pricing`, or `undefined` when no rate applies.
111
+ *
112
+ * The cache counts are a breakdown of `inputTokens`, not a peer of it (see
113
+ * {@link extractUsage}), so the input rate applies to the **remainder** — what
114
+ * was neither served from nor written to cache — and each cache rate to its own
115
+ * count. Charging the whole input count *and* the cache counts on top would bill
116
+ * a cached token twice, which on a warm prompt-caching workload is most of the
117
+ * bill. A missing cache rate falls back to the input rate for reads and writes,
118
+ * since every provider prices cached tokens relative to input.
119
+ */
120
+ export function computeCostUsd(usage, pricing) {
121
+ if (!pricing)
122
+ return undefined;
123
+ const { input, output, cacheRead, cacheWrite } = pricing;
124
+ if (input === undefined && output === undefined)
125
+ return undefined;
126
+ // Clamped: a provider's breakdown is not obliged to fit inside its own total,
127
+ // and a negative remainder would credit cost back rather than fail loudly.
128
+ const fullRateInput = Math.max(0, usage.inputTokens - usage.cacheReadTokens - usage.cacheCreationTokens);
129
+ const cost = (num(input) * fullRateInput +
130
+ num(output) * usage.outputTokens +
131
+ num(cacheRead ?? input) * usage.cacheReadTokens +
132
+ num(cacheWrite ?? input) * usage.cacheCreationTokens) /
133
+ PER_MILLION;
134
+ // Sub-cent runs are common; keep six decimals so a cost is never rounded to 0.
135
+ return Math.round(cost * 1e6) / 1e6;
136
+ }
137
+ /** Build a summary: usage plus cost when the model is priced. */
138
+ export function summarizeUsage(usage, table, model) {
139
+ const costUsd = computeCostUsd(usage, resolveModelPricing(table, model));
140
+ return { ...usage, ...(costUsd !== undefined ? { costUsd } : {}) };
141
+ }
142
+ /**
143
+ * Sum two summaries, tokens and cost together. Cost is present only when at
144
+ * least one side has it — summing an unpriced summary must not invent a `0`.
145
+ * This is the single place summaries are combined (the tracker's cross-session
146
+ * totals and the CLI's per-test-case totals both use it).
147
+ */
148
+ export function addSummaries(a, b) {
149
+ const costUsd = a.costUsd !== undefined || b.costUsd !== undefined
150
+ ? Math.round(((a.costUsd ?? 0) + (b.costUsd ?? 0)) * 1e6) / 1e6
151
+ : undefined;
152
+ return { ...addUsage(a, b), ...(costUsd !== undefined ? { costUsd } : {}) };
153
+ }
154
+ /** Human-readable one-liner, e.g. `in 12,004 · out 318 · cache r 9,801/w 0 · $0.0421`. */
155
+ export function formatUsage(summary) {
156
+ const n = (value) => value.toLocaleString("en-US");
157
+ const parts = [
158
+ `in ${n(summary.inputTokens)}`,
159
+ `out ${n(summary.outputTokens)}`,
160
+ `cache r ${n(summary.cacheReadTokens)}/w ${n(summary.cacheCreationTokens)}`,
161
+ ];
162
+ if (summary.costUsd !== undefined) {
163
+ parts.push(`$${summary.costUsd < 0.01 ? summary.costUsd.toFixed(6) : summary.costUsd.toFixed(4)}`);
164
+ }
165
+ return parts.join(" · ");
166
+ }
167
+ /**
168
+ * `createUsageTracker()` — the supported way to read what a session cost.
169
+ *
170
+ * Usage arrives as one `model-usage` event per model call. A host could sum
171
+ * those by hand; this tracker exists so nobody has to, and so per-session
172
+ * demultiplexing (concurrent runs on one assembled agent) is done once, from the
173
+ * event envelope's `sessionId`.
174
+ *
175
+ * For the consumer holding `invoke`, there is deliberately nothing to wait for:
176
+ * `invoke` returning *is* the end of the turn, and that is where `totals()` is
177
+ * read. The runtime keeps one of these internally so a session's usage can be
178
+ * written into its artifacts.
179
+ *
180
+ * A consumer that only sees the **event stream** has no `invoke` to observe, and
181
+ * for a durable session "`invoke` returned" means the turn ended, not the run — a
182
+ * parked session returns with the run very much alive. Those consumers get the
183
+ * `run-metrics` event instead, which reports the session's cumulative ledger
184
+ * (usage included) whenever a turn settles. See `core/metrics.ts`.
185
+ *
186
+ * ```ts
187
+ * const usage = createUsageTracker();
188
+ * const runtime = await createAgent({ onEvent: usage.handler });
189
+ * await runtime.graph.invoke(input, { configurable: { thread_id: "t1" } });
190
+ * usage.totals("t1"); // { inputTokens, cacheReadTokens, …, costUsd? }
191
+ * ```
192
+ *
193
+ * To keep your own subscriber, pass it in: `createUsageTracker({ onEvent: mine })`.
194
+ */
195
+ /** Key used for events that arrive without a bound session id. */
196
+ const UNSCOPED = "__unscoped__";
197
+ /** Create a tracker that accumulates usage and cost from the event stream. */
198
+ export function createUsageTracker(options = {}) {
199
+ const sessions = new Map();
200
+ return {
201
+ handler: (event) => {
202
+ if (event.type === "model-usage") {
203
+ const key = event.sessionId ?? UNSCOPED;
204
+ sessions.set(key, addSummaries(sessions.get(key) ?? emptyUsage(), {
205
+ inputTokens: event.inputTokens,
206
+ outputTokens: event.outputTokens,
207
+ cacheReadTokens: event.cacheReadTokens ?? 0,
208
+ cacheCreationTokens: event.cacheCreationTokens ?? 0,
209
+ ...(event.costUsd !== undefined ? { costUsd: event.costUsd } : {}),
210
+ }));
211
+ }
212
+ options.onEvent?.(event);
213
+ },
214
+ totals: (sessionId) => sessionId !== undefined
215
+ ? (sessions.get(sessionId) ?? emptyUsage())
216
+ : [...sessions.values()].reduce(addSummaries, emptyUsage()),
217
+ bySession: () => new Map(sessions),
218
+ reset: (sessionId) => {
219
+ if (sessionId === undefined)
220
+ sessions.clear();
221
+ else
222
+ sessions.delete(sessionId);
223
+ },
224
+ };
225
+ }
226
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/core/usage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ import { type BackendProtocolV2 } from "deepagents";
2
+ import { type SessionStore } from "./session-store.js";
3
+ import { SessionZoneRouter } from "./session-zone.js";
4
+ import { Workspace } from "./workspace.js";
5
+ import { type MountPrefixes, type MountSpec } from "./mounts.js";
6
+ export interface WorkspaceContextOptions {
7
+ /**
8
+ * Agent workspace root: where the zero-config filesystem defaults — the
9
+ * conventional mount table, the `sessions/` store, the authoring backend —
10
+ * are built. With **none** of `backend`, `mounts`, `sessionStore` and
11
+ * `authoring` supplied it defaults to the consumer's current working
12
+ * directory, as documented. Once any of them is supplied, a default that still
13
+ * needs a root is refused with {@link WorkspaceRootRequiredError} rather than
14
+ * built over the cwd; and when nothing needs one, none is resolved
15
+ * ({@link WorkspaceContext.rootDir} is absent).
16
+ */
17
+ rootDir?: string;
18
+ /**
19
+ * Declares that authored content comes from somewhere other than the local
20
+ * filesystem root — a store, sandbox, or remote Deep Agents backend.
21
+ *
22
+ * Supplying it does **not** by itself serve anything: this backend reaches the
23
+ * workspace only through the `mounts` table below, and its presence is what
24
+ * withdraws every zero-config default (the conventional mount table, the local
25
+ * `<rootDir>/sessions` store). Omit it for a filesystem
26
+ * workspace, where those defaults apply.
27
+ */
28
+ backend?: BackendProtocolV2;
29
+ /**
30
+ * Physical storage for the writable session zone — the agent's workspace root
31
+ * (checkpoints, artifacts, output, scratchpad, offloaded context). With the
32
+ * default authored backend this defaults to a filesystem store at
33
+ * `<rootDir>/sessions`; with a custom authored `backend` it is required — assembly
34
+ * throws {@link SessionStoreRequiredError} rather than silently running without
35
+ * session-scoped run routing or writing to local disk beside a virtual
36
+ * workspace.
37
+ */
38
+ sessionStore?: SessionStore;
39
+ /**
40
+ * Authored mounts as a {@link CompositeBackend} route table: key → backend, or
41
+ * `{ backend, readOnly }`. A key ending in `/` is a directory mount; a key
42
+ * without one (`"/AGENTS.md"`) is an exact-path file mount. Mounts are
43
+ * read-only unless a spec declares otherwise.
44
+ *
45
+ * Omitted on the default filesystem backend, {@link defaultMounts} is applied
46
+ * over the workspace root. Omitted with a custom `backend`, nothing authored is
47
+ * served — exposing that backend is an explicit act (compose it with
48
+ * `mountSubtree(backend, "skills")`).
49
+ */
50
+ mounts?: Record<string, MountSpec>;
51
+ /**
52
+ * Backend serving the **authored governance plane** — `workflows/<slug>/`:
53
+ * machine specs, prose addenda, lifecycle hook scripts, and offline test
54
+ * cases. Read by the runtime, never routed into the agent's workspace
55
+ * composite, so no agent tool and no PTC call can address it.
56
+ *
57
+ * Defaults to the authored `backend` when one is supplied, and otherwise to a
58
+ * filesystem backend over the resolved root. Declaring it separately is what
59
+ * lets governance live somewhere the agent's workspace does not: a store, a
60
+ * service, a signed bundle.
61
+ */
62
+ authoring?: BackendProtocolV2;
63
+ }
64
+ /**
65
+ * Thrown when the authoring backend is also served by a **writable** mount. A
66
+ * writable route onto the authoring backend would let a session edit the machine
67
+ * that governs it, which no downstream rule can undo — so it is refused at
68
+ * assembly rather than diagnosed later.
69
+ */
70
+ export declare class AuthoringBackendExposedError extends Error {
71
+ readonly key: string;
72
+ constructor(key: string);
73
+ }
74
+ /** @deprecated Older spelling of {@link AuthoringBackendExposedError}; kept for one release. Same class. */
75
+ export declare const AuthoringPlaneExposedError: typeof AuthoringBackendExposedError;
76
+ /**
77
+ * Thrown when a workspace is partly composed — some of `mounts`, `sessionStore`
78
+ * and `authoring` supplied, no `backend` — and a filesystem default for the rest
79
+ * would have to be built over the process's working directory because no
80
+ * `rootDir` was given. A service's cwd is not an agent root; the option that
81
+ * needed one is named so the caller supplies it, or the source it stands for.
82
+ */
83
+ export declare class WorkspaceRootRequiredError extends Error {
84
+ readonly option: "mounts" | "sessionStore" | "authoring";
85
+ constructor(option: "mounts" | "sessionStore" | "authoring");
86
+ }
87
+ /**
88
+ * Resolved workspace context shared by assembly, scaffold validation,
89
+ * and offline test execution. Centralizing this keeps root resolution, backend
90
+ * selection, platform-prompt directory choice, and run-store resolution
91
+ * consistent across every entrypoint.
92
+ */
93
+ export interface WorkspaceContext {
94
+ /**
95
+ * Absolute resolved workspace root — present when one was given or when a
96
+ * filesystem default was built over it; absent when every source was supplied
97
+ * and nothing was read from local disk to compose this context.
98
+ */
99
+ rootDir?: string;
100
+ /** Backend serving workspace file access. */
101
+ backend: BackendProtocolV2;
102
+ /** Workspace wrapper over {@link backend}. */
103
+ workspace: Workspace;
104
+ /**
105
+ * Workspace over the authoring backend — the runtime's read path for
106
+ * `workflows/**`. Deliberately *not* the agent's {@link workspace}: nothing
107
+ * reachable from here is reachable from a tool call.
108
+ */
109
+ authoring: Workspace;
110
+ /** The resolved session store (explicit, or the zero-config filesystem default). */
111
+ sessionStore: SessionStore;
112
+ /** Whether the authored source is the default filesystem backend over `rootDir`. */
113
+ usingDefaultBackend: boolean;
114
+ /**
115
+ * This workspace's resolved mount keys — the single source every consumer
116
+ * classifies against: mount routing, agent-visible root shaping, the decision
117
+ * kernel's zone rules, and static validation. Derived from the very keys the
118
+ * composite routes on, so the two cannot drift.
119
+ */
120
+ mountPrefixes: MountPrefixes;
121
+ /**
122
+ * Router serving the workspace root's session-scoped, id-free addressing. Bind
123
+ * it to a session id (`sessionScoped`) around a turn's `agent.invoke` so
124
+ * agent-visible `scratchpad/…` and offload paths for that turn
125
+ * resolve against that session without the session id embedded.
126
+ */
127
+ sessionZone: SessionZoneRouter;
128
+ }
129
+ /**
130
+ * Build a {@link WorkspaceContext} from caller options. With no custom backend,
131
+ * a filesystem backend is created over the resolved root, the platform prompt
132
+ * directory defaults to the bundled platform assets, and the session store
133
+ * defaults to `<rootDir>/sessions` on the local filesystem. With a custom backend,
134
+ * an explicit {@link SessionStore} is required.
135
+ */
136
+ export declare function createWorkspaceContext(options?: WorkspaceContextOptions): WorkspaceContext;
@@ -0,0 +1,178 @@
1
+ import { join } from "node:path";
2
+ import { CompositeBackend, FilesystemBackend } from "deepagents";
3
+ import { resolveWorkspaceRoot } from "../env.js";
4
+ import { mountSubtree } from "./path-mapping.js";
5
+ import { createFilesystemSessionStore, DEFAULT_SESSIONS_DIR, SessionStoreRequiredError, } from "./session-store.js";
6
+ import { SessionZoneRouter } from "./session-zone.js";
7
+ import { Workspace } from "./workspace.js";
8
+ import { createWorkspaceRouter } from "./workspace-router.js";
9
+ import { defaultMounts, resolveMounts, } from "./mounts.js";
10
+ /**
11
+ * Thrown when the authoring backend is also served by a **writable** mount. A
12
+ * writable route onto the authoring backend would let a session edit the machine
13
+ * that governs it, which no downstream rule can undo — so it is refused at
14
+ * assembly rather than diagnosed later.
15
+ */
16
+ export class AuthoringBackendExposedError extends Error {
17
+ key;
18
+ constructor(key) {
19
+ super(`Mount '${key}' is writable and is served by the same backend as the authoring backend. ` +
20
+ `The authoring backend must not be agent-writable — mount a different backend, or declare ` +
21
+ `the mount read-only.`);
22
+ this.key = key;
23
+ this.name = "AuthoringBackendExposedError";
24
+ }
25
+ }
26
+ /** @deprecated Older spelling of {@link AuthoringBackendExposedError}; kept for one release. Same class. */
27
+ export const AuthoringPlaneExposedError = AuthoringBackendExposedError;
28
+ /**
29
+ * Thrown when a workspace is partly composed — some of `mounts`, `sessionStore`
30
+ * and `authoring` supplied, no `backend` — and a filesystem default for the rest
31
+ * would have to be built over the process's working directory because no
32
+ * `rootDir` was given. A service's cwd is not an agent root; the option that
33
+ * needed one is named so the caller supplies it, or the source it stands for.
34
+ */
35
+ export class WorkspaceRootRequiredError extends Error {
36
+ option;
37
+ constructor(option) {
38
+ super(`No 'rootDir' was given, and the filesystem default for '${option}' needs one. ` +
39
+ `The working directory is not used as an agent root once the workspace is composed ` +
40
+ `explicitly: pass 'rootDir', or supply '${option}' yourself.`);
41
+ this.option = option;
42
+ this.name = "WorkspaceRootRequiredError";
43
+ }
44
+ }
45
+ /**
46
+ * Build a {@link WorkspaceContext} from caller options. With no custom backend,
47
+ * a filesystem backend is created over the resolved root, the platform prompt
48
+ * directory defaults to the bundled platform assets, and the session store
49
+ * defaults to `<rootDir>/sessions` on the local filesystem. With a custom backend,
50
+ * an explicit {@link SessionStore} is required.
51
+ */
52
+ export function createWorkspaceContext(options = {}) {
53
+ // Nothing serves `options.backend` directly: a custom authored backend reaches
54
+ // the workspace only where the consumer mounts it (see `mounts`). What its
55
+ // presence decides here is that this is not the zero-config filesystem
56
+ // workspace — so neither the conventional mount table, the local session store,
57
+ // nor the bundled platform directory may be assumed.
58
+ const usingDefaultBackend = options.backend === undefined;
59
+ // Which filesystem defaults this call would build over a root. The cwd stands
60
+ // in for a root only in the fully zero-config case; a workspace the caller has
61
+ // started composing gets no silent root, and one composed entirely gets none at all.
62
+ const defaults = usingDefaultBackend
63
+ ? ["mounts", "sessionStore", "authoring"].filter((option) => options[option] === undefined)
64
+ : [];
65
+ const composed = options.backend !== undefined ||
66
+ options.mounts !== undefined ||
67
+ options.sessionStore !== undefined ||
68
+ options.authoring !== undefined;
69
+ let rootDir;
70
+ if (options.rootDir !== undefined)
71
+ rootDir = resolveWorkspaceRoot(options.rootDir);
72
+ else if (defaults.length > 0) {
73
+ if (composed)
74
+ throw new WorkspaceRootRequiredError(defaults[0]);
75
+ rootDir = resolveWorkspaceRoot();
76
+ }
77
+ /** The root a filesystem default is built over; only reached when one was resolved above. */
78
+ const rootFor = (option) => {
79
+ if (rootDir === undefined)
80
+ throw new WorkspaceRootRequiredError(option);
81
+ return rootDir;
82
+ };
83
+ // The consumer owns physical run-state storage; the SDK owns the logical
84
+ // namespace. Zero-config (default backend, no store) keeps the documented
85
+ // local default; a custom backend must name its store explicitly — never
86
+ // infer storage or drop session routing based on how the authored backend
87
+ // was supplied.
88
+ if (!usingDefaultBackend && options.sessionStore === undefined) {
89
+ throw new SessionStoreRequiredError();
90
+ }
91
+ const sessionStore = options.sessionStore ??
92
+ createFilesystemSessionStore({ dir: join(rootFor("sessionStore"), DEFAULT_SESSIONS_DIR) });
93
+ // The session zone is the workspace root: checkpoints, artifacts, governed
94
+ // the agent's `scratchpad/…` working area and the runtime's context-offload
95
+ // areas all resolve per session through one router. Authored content is
96
+ // mounted read-only beside it — each mount maps paths in both directions so
97
+ // the authored backend sees authored paths and results come back in
98
+ // workspace form.
99
+ const sessionZone = new SessionZoneRouter(sessionStore.backend);
100
+ // Which directories are authored is workspace shape, not framework knowledge:
101
+ // the consumer composes a route table of backends. The conventional table is a
102
+ // default value applied only on the zero-config filesystem path.
103
+ const table = options.mounts ?? (usingDefaultBackend ? defaultMounts(rootFor("mounts")) : {});
104
+ const { mounts, prefixes: mountPrefixes } = resolveMounts(table);
105
+ // Directory mounts become composite routes; file mounts are matched exactly by
106
+ // the router, since a prefix route can neither address a single file nor
107
+ // distinguish `AGENTS.md` from `AGENTS.md.bak`.
108
+ const dirMounts = mounts.filter((m) => m.isDir);
109
+ const routes = Object.fromEntries(dirMounts.map((m) => [m.key, guard(m)]));
110
+ const fileMounts = new Map(mounts.filter((m) => !m.isDir).map((m) => [m.name, guard(m)]));
111
+ const composite = new CompositeBackend(sessionZone, routes);
112
+ // A root-wide search fans out to every route the composite has, and one
113
+ // route's `{ error }` is fatal to the whole search. A mount declared
114
+ // `searchable: false` is therefore left out of the composite that serves
115
+ // searches — the same default route and the same guarded route objects, so
116
+ // nothing else about routing differs — and reached only by a search
117
+ // addressed at it, which the router delegates to the route directly. With
118
+ // no such mount the search composite *is* the routing composite, so an
119
+ // existing table behaves byte-identically.
120
+ const unsearchable = dirMounts.filter((m) => !m.searchable);
121
+ const searchComposite = unsearchable.length === 0
122
+ ? composite
123
+ : new CompositeBackend(sessionZone, Object.fromEntries(dirMounts.filter((m) => m.searchable).map((m) => [m.key, routes[m.key]])));
124
+ const unsearchableRoutes = new Map(unsearchable.map((m) => [m.name, routes[m.key]]));
125
+ const backend = createWorkspaceRouter({
126
+ composite,
127
+ searchComposite,
128
+ unsearchableRoutes,
129
+ fileMounts,
130
+ mountPrefixes,
131
+ boundSessionId: () => sessionZone.boundSessionId(),
132
+ });
133
+ const workspace = new Workspace(backend);
134
+ // The governance plane is served beside the agent's workspace, never inside
135
+ // it. With a custom authored backend that backend is the natural source (it
136
+ // already holds the authored tree); the agent still cannot reach `workflows/**`
137
+ // through it, because the composite has no route there and `resolveMounts`
138
+ // refuses one.
139
+ const authoringBackend = options.authoring ??
140
+ options.backend ??
141
+ new FilesystemBackend({ rootDir: rootFor("authoring"), virtualMode: true });
142
+ for (const mount of mounts) {
143
+ if (!mount.readOnly && mount.backend === authoringBackend) {
144
+ throw new AuthoringBackendExposedError(mount.key);
145
+ }
146
+ }
147
+ const authoring = new Workspace(authoringBackend);
148
+ return {
149
+ ...(rootDir !== undefined ? { rootDir } : {}),
150
+ backend,
151
+ workspace,
152
+ authoring,
153
+ sessionStore,
154
+ usingDefaultBackend,
155
+ sessionZone,
156
+ mountPrefixes,
157
+ };
158
+ }
159
+ /**
160
+ * A read-only mount refuses writes at the mount itself, so authored content
161
+ * cannot be modified even by a caller that bypasses governance; the kernel's
162
+ * `zone.read-only` rule remains the agent-facing diagnostic. A mount the consumer
163
+ * declared writable is delegated as-is.
164
+ *
165
+ * A directory mount is addressed with its route prefix already stripped by the
166
+ * composite, so the refusal is told that prefix (`data`) to re-apply when naming
167
+ * the rejected path; a file mount is addressed by its exact key, which needs no
168
+ * re-application.
169
+ */
170
+ function guard(mount) {
171
+ return mount.readOnly
172
+ ? mountSubtree(mount.backend, "", {
173
+ readOnly: true,
174
+ ...(mount.isDir ? { displayPrefix: mount.name } : {}),
175
+ })
176
+ : mount.backend;
177
+ }
178
+ //# sourceMappingURL=workspace-context.js.map