@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.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/agent.d.ts +174 -0
- package/dist/agent.js +140 -0
- package/dist/agent.js.map +1 -0
- package/dist/assembly/compose.d.ts +170 -0
- package/dist/assembly/compose.js +391 -0
- package/dist/assembly/compose.js.map +1 -0
- package/dist/assembly/delegation-registry.d.ts +5 -0
- package/dist/assembly/delegation-registry.js +118 -0
- package/dist/assembly/delegation-registry.js.map +1 -0
- package/dist/assembly/index.d.ts +192 -0
- package/dist/assembly/index.js +265 -0
- package/dist/assembly/index.js.map +1 -0
- package/dist/assembly/plain.d.ts +7 -0
- package/dist/assembly/plain.js +41 -0
- package/dist/assembly/plain.js.map +1 -0
- package/dist/authoring-skill/archmax-harness/README.md +76 -0
- package/dist/authoring-skill/archmax-harness/SKILL.md +241 -0
- package/dist/authoring-skill/archmax-harness/references/backend-integration.md +626 -0
- package/dist/authoring-skill/archmax-harness/references/cases.md +41 -0
- package/dist/authoring-skill/archmax-harness/references/diagnostics.md +51 -0
- package/dist/authoring-skill/archmax-harness/references/hook-and-test-scripts.md +1070 -0
- package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +82 -0
- package/dist/authoring-skill/archmax-harness/references/wiring.md +101 -0
- package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +1631 -0
- package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +241 -0
- package/dist/behaviour/support.d.ts +232 -0
- package/dist/behaviour/support.js +339 -0
- package/dist/behaviour/support.js.map +1 -0
- package/dist/cli/banner.d.ts +11 -0
- package/dist/cli/banner.js +29 -0
- package/dist/cli/banner.js.map +1 -0
- package/dist/cli/bootstrap.d.ts +33 -0
- package/dist/cli/bootstrap.js +153 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/command.d.ts +39 -0
- package/dist/cli/command.js +97 -0
- package/dist/cli/command.js.map +1 -0
- package/dist/cli/state-flow.d.ts +46 -0
- package/dist/cli/state-flow.js +268 -0
- package/dist/cli/state-flow.js.map +1 -0
- package/dist/cli/style.d.ts +41 -0
- package/dist/cli/style.js +64 -0
- package/dist/cli/style.js.map +1 -0
- package/dist/cli/turn.d.ts +41 -0
- package/dist/cli/turn.js +161 -0
- package/dist/cli/turn.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +426 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/code-description.d.ts +22 -0
- package/dist/core/code-description.js +38 -0
- package/dist/core/code-description.js.map +1 -0
- package/dist/core/deepagents.d.ts +183 -0
- package/dist/core/deepagents.js +128 -0
- package/dist/core/deepagents.js.map +1 -0
- package/dist/core/events.d.ts +350 -0
- package/dist/core/events.js +205 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/frontmatter.d.ts +15 -0
- package/dist/core/frontmatter.js +32 -0
- package/dist/core/frontmatter.js.map +1 -0
- package/dist/core/listing-redact.d.ts +37 -0
- package/dist/core/listing-redact.js +88 -0
- package/dist/core/listing-redact.js.map +1 -0
- package/dist/core/match.d.ts +26 -0
- package/dist/core/match.js +75 -0
- package/dist/core/match.js.map +1 -0
- package/dist/core/message-readers.d.ts +84 -0
- package/dist/core/message-readers.js +159 -0
- package/dist/core/message-readers.js.map +1 -0
- package/dist/core/messages.d.ts +49 -0
- package/dist/core/messages.js +85 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/mount-prompt.d.ts +28 -0
- package/dist/core/mount-prompt.js +55 -0
- package/dist/core/mount-prompt.js.map +1 -0
- package/dist/core/mount-redact.d.ts +28 -0
- package/dist/core/mount-redact.js +32 -0
- package/dist/core/mount-redact.js.map +1 -0
- package/dist/core/mounts.d.ts +88 -0
- package/dist/core/mounts.js +147 -0
- package/dist/core/mounts.js.map +1 -0
- package/dist/core/path-mapping.d.ts +52 -0
- package/dist/core/path-mapping.js +113 -0
- package/dist/core/path-mapping.js.map +1 -0
- package/dist/core/platform-prompt.generated.d.ts +2 -0
- package/dist/core/platform-prompt.generated.js +4 -0
- package/dist/core/platform-prompt.generated.js.map +1 -0
- package/dist/core/prompt.d.ts +42 -0
- package/dist/core/prompt.js +75 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/session-id.d.ts +38 -0
- package/dist/core/session-id.js +61 -0
- package/dist/core/session-id.js.map +1 -0
- package/dist/core/session-store.d.ts +85 -0
- package/dist/core/session-store.js +149 -0
- package/dist/core/session-store.js.map +1 -0
- package/dist/core/session-zone.d.ts +54 -0
- package/dist/core/session-zone.js +82 -0
- package/dist/core/session-zone.js.map +1 -0
- package/dist/core/skill-prompt.d.ts +31 -0
- package/dist/core/skill-prompt.js +54 -0
- package/dist/core/skill-prompt.js.map +1 -0
- package/dist/core/skill-redact.d.ts +15 -0
- package/dist/core/skill-redact.js +27 -0
- package/dist/core/skill-redact.js.map +1 -0
- package/dist/core/skills.d.ts +172 -0
- package/dist/core/skills.js +214 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/tool-mocks.d.ts +23 -0
- package/dist/core/tool-mocks.js +39 -0
- package/dist/core/tool-mocks.js.map +1 -0
- package/dist/core/tool-telemetry.d.ts +16 -0
- package/dist/core/tool-telemetry.js +40 -0
- package/dist/core/tool-telemetry.js.map +1 -0
- package/dist/core/usage.d.ts +114 -0
- package/dist/core/usage.js +226 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/core/workspace-context.d.ts +136 -0
- package/dist/core/workspace-context.js +178 -0
- package/dist/core/workspace-context.js.map +1 -0
- package/dist/core/workspace-prompt.d.ts +7 -0
- package/dist/core/workspace-prompt.js +102 -0
- package/dist/core/workspace-prompt.js.map +1 -0
- package/dist/core/workspace-router.d.ts +61 -0
- package/dist/core/workspace-router.js +145 -0
- package/dist/core/workspace-router.js.map +1 -0
- package/dist/core/workspace.d.ts +85 -0
- package/dist/core/workspace.js +193 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/core/zones.d.ts +181 -0
- package/dist/core/zones.js +202 -0
- package/dist/core/zones.js.map +1 -0
- package/dist/env.d.ts +99 -0
- package/dist/env.js +271 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +265 -0
- package/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/kernel.d.ts +192 -0
- package/dist/kernel/kernel.js +751 -0
- package/dist/kernel/kernel.js.map +1 -0
- package/dist/lifecycle/hook-shape.d.ts +44 -0
- package/dist/lifecycle/hook-shape.js +66 -0
- package/dist/lifecycle/hook-shape.js.map +1 -0
- package/dist/lifecycle/runner.d.ts +177 -0
- package/dist/lifecycle/runner.js +372 -0
- package/dist/lifecycle/runner.js.map +1 -0
- package/dist/lifecycle/transcript.d.ts +27 -0
- package/dist/lifecycle/transcript.js +43 -0
- package/dist/lifecycle/transcript.js.map +1 -0
- package/dist/machine/allow.d.ts +65 -0
- package/dist/machine/allow.js +107 -0
- package/dist/machine/allow.js.map +1 -0
- package/dist/machine/delegation.d.ts +9 -0
- package/dist/machine/delegation.js +10 -0
- package/dist/machine/delegation.js.map +1 -0
- package/dist/machine/diagnostic.d.ts +14 -0
- package/dist/machine/diagnostic.js +2 -0
- package/dist/machine/diagnostic.js.map +1 -0
- package/dist/machine/lint-spec.d.ts +4 -0
- package/dist/machine/lint-spec.js +266 -0
- package/dist/machine/lint-spec.js.map +1 -0
- package/dist/machine/load-spec.d.ts +90 -0
- package/dist/machine/load-spec.js +120 -0
- package/dist/machine/load-spec.js.map +1 -0
- package/dist/machine/machine.d.ts +273 -0
- package/dist/machine/machine.js +551 -0
- package/dist/machine/machine.js.map +1 -0
- package/dist/machine/mount-grants.d.ts +25 -0
- package/dist/machine/mount-grants.js +24 -0
- package/dist/machine/mount-grants.js.map +1 -0
- package/dist/machine/slug.d.ts +14 -0
- package/dist/machine/slug.js +17 -0
- package/dist/machine/slug.js.map +1 -0
- package/dist/machine/spec-schema.d.ts +662 -0
- package/dist/machine/spec-schema.js +717 -0
- package/dist/machine/spec-schema.js.map +1 -0
- package/dist/machine/tool-names.d.ts +98 -0
- package/dist/machine/tool-names.js +144 -0
- package/dist/machine/tool-names.js.map +1 -0
- package/dist/machine/triggers.d.ts +114 -0
- package/dist/machine/triggers.js +165 -0
- package/dist/machine/triggers.js.map +1 -0
- package/dist/machine/types.d.ts +186 -0
- package/dist/machine/types.js +12 -0
- package/dist/machine/types.js.map +1 -0
- package/dist/machine/validate-spec.d.ts +42 -0
- package/dist/machine/validate-spec.js +29 -0
- package/dist/machine/validate-spec.js.map +1 -0
- package/dist/machine/variables.d.ts +196 -0
- package/dist/machine/variables.js +415 -0
- package/dist/machine/variables.js.map +1 -0
- package/dist/public/cli.d.ts +15 -0
- package/dist/public/cli.js +14 -0
- package/dist/public/cli.js.map +1 -0
- package/dist/public/messages.d.ts +33 -0
- package/dist/public/messages.js +33 -0
- package/dist/public/messages.js.map +1 -0
- package/dist/public/sandbox.d.ts +99 -0
- package/dist/public/sandbox.js +32 -0
- package/dist/public/sandbox.js.map +1 -0
- package/dist/public/spec.d.ts +147 -0
- package/dist/public/spec.js +149 -0
- package/dist/public/spec.js.map +1 -0
- package/dist/public/testing.d.ts +71 -0
- package/dist/public/testing.js +39 -0
- package/dist/public/testing.js.map +1 -0
- package/dist/rubrics/rubrics.d.ts +135 -0
- package/dist/rubrics/rubrics.js +145 -0
- package/dist/rubrics/rubrics.js.map +1 -0
- package/dist/runtime/contract.d.ts +73 -0
- package/dist/runtime/contract.js +96 -0
- package/dist/runtime/contract.js.map +1 -0
- package/dist/sandbox/assets/parts/core.js +22 -0
- package/dist/sandbox/assets/parts/lifecycle-hook.js +47 -0
- package/dist/sandbox/bridge.d.ts +16 -0
- package/dist/sandbox/bridge.js +95 -0
- package/dist/sandbox/bridge.js.map +1 -0
- package/dist/sandbox/executor.d.ts +46 -0
- package/dist/sandbox/executor.js +119 -0
- package/dist/sandbox/executor.js.map +1 -0
- package/dist/sandbox/imports.d.ts +33 -0
- package/dist/sandbox/imports.js +67 -0
- package/dist/sandbox/imports.js.map +1 -0
- package/dist/sandbox/prelude.d.ts +12 -0
- package/dist/sandbox/prelude.js +51 -0
- package/dist/sandbox/prelude.js.map +1 -0
- package/dist/sandbox/ptc-gateway.d.ts +104 -0
- package/dist/sandbox/ptc-gateway.js +167 -0
- package/dist/sandbox/ptc-gateway.js.map +1 -0
- package/dist/sandbox/runtime.d.ts +59 -0
- package/dist/sandbox/runtime.js +51 -0
- package/dist/sandbox/runtime.js.map +1 -0
- package/dist/sandbox/tools.d.ts +34 -0
- package/dist/sandbox/tools.js +147 -0
- package/dist/sandbox/tools.js.map +1 -0
- package/dist/sessions/operations.d.ts +79 -0
- package/dist/sessions/operations.js +121 -0
- package/dist/sessions/operations.js.map +1 -0
- package/dist/sessions/resolve.d.ts +97 -0
- package/dist/sessions/resolve.js +145 -0
- package/dist/sessions/resolve.js.map +1 -0
- package/dist/sessions/resume.d.ts +242 -0
- package/dist/sessions/resume.js +288 -0
- package/dist/sessions/resume.js.map +1 -0
- package/dist/sessions/scope.d.ts +99 -0
- package/dist/sessions/scope.js +145 -0
- package/dist/sessions/scope.js.map +1 -0
- package/dist/sessions/summary.d.ts +65 -0
- package/dist/sessions/summary.js +56 -0
- package/dist/sessions/summary.js.map +1 -0
- package/dist/testing/assertions.d.ts +30 -0
- package/dist/testing/assertions.js +231 -0
- package/dist/testing/assertions.js.map +1 -0
- package/dist/testing/case-document.d.ts +141 -0
- package/dist/testing/case-document.js +51 -0
- package/dist/testing/case-document.js.map +1 -0
- package/dist/testing/case-schema.d.ts +29 -0
- package/dist/testing/case-schema.js +519 -0
- package/dist/testing/case-schema.js.map +1 -0
- package/dist/testing/discovery.d.ts +10 -0
- package/dist/testing/discovery.js +24 -0
- package/dist/testing/discovery.js.map +1 -0
- package/dist/testing/driver.d.ts +78 -0
- package/dist/testing/driver.js +117 -0
- package/dist/testing/driver.js.map +1 -0
- package/dist/testing/grade.d.ts +116 -0
- package/dist/testing/grade.js +261 -0
- package/dist/testing/grade.js.map +1 -0
- package/dist/testing/interpreter.d.ts +55 -0
- package/dist/testing/interpreter.js +103 -0
- package/dist/testing/interpreter.js.map +1 -0
- package/dist/testing/mock-middleware.d.ts +5 -0
- package/dist/testing/mock-middleware.js +33 -0
- package/dist/testing/mock-middleware.js.map +1 -0
- package/dist/testing/runner.d.ts +205 -0
- package/dist/testing/runner.js +266 -0
- package/dist/testing/runner.js.map +1 -0
- package/dist/testing/target.d.ts +27 -0
- package/dist/testing/target.js +21 -0
- package/dist/testing/target.js.map +1 -0
- package/dist/validate/validate.d.ts +41 -0
- package/dist/validate/validate.js +669 -0
- package/dist/validate/validate.js.map +1 -0
- package/dist/workflow/agent-tools.d.ts +10 -0
- package/dist/workflow/agent-tools.js +10 -0
- package/dist/workflow/agent-tools.js.map +1 -0
- package/dist/workflow/checkpointer.d.ts +52 -0
- package/dist/workflow/checkpointer.js +200 -0
- package/dist/workflow/checkpointer.js.map +1 -0
- package/dist/workflow/control-tools.d.ts +174 -0
- package/dist/workflow/control-tools.js +468 -0
- package/dist/workflow/control-tools.js.map +1 -0
- package/dist/workflow/governance.d.ts +104 -0
- package/dist/workflow/governance.js +546 -0
- package/dist/workflow/governance.js.map +1 -0
- package/dist/workflow/middleware.d.ts +85 -0
- package/dist/workflow/middleware.js +283 -0
- package/dist/workflow/middleware.js.map +1 -0
- package/dist/workflow/on-error.d.ts +25 -0
- package/dist/workflow/on-error.js +58 -0
- package/dist/workflow/on-error.js.map +1 -0
- package/dist/workflow/parks.d.ts +46 -0
- package/dist/workflow/parks.js +513 -0
- package/dist/workflow/parks.js.map +1 -0
- package/dist/workflow/paths.d.ts +67 -0
- package/dist/workflow/paths.js +108 -0
- package/dist/workflow/paths.js.map +1 -0
- package/dist/workflow/prompt-cache.d.ts +70 -0
- package/dist/workflow/prompt-cache.js +103 -0
- package/dist/workflow/prompt-cache.js.map +1 -0
- package/dist/workflow/prompt-pruning.d.ts +20 -0
- package/dist/workflow/prompt-pruning.js +83 -0
- package/dist/workflow/prompt-pruning.js.map +1 -0
- package/dist/workflow/render-prompt.d.ts +45 -0
- package/dist/workflow/render-prompt.js +140 -0
- package/dist/workflow/render-prompt.js.map +1 -0
- package/dist/workflow/runtime-config.d.ts +7 -0
- package/dist/workflow/runtime-config.js +9 -0
- package/dist/workflow/runtime-config.js.map +1 -0
- package/dist/workflow/session-artifacts.d.ts +90 -0
- package/dist/workflow/session-artifacts.js +134 -0
- package/dist/workflow/session-artifacts.js.map +1 -0
- package/dist/workflow/session-view.d.ts +31 -0
- package/dist/workflow/session-view.js +94 -0
- package/dist/workflow/session-view.js.map +1 -0
- package/dist/workflow/snapshot.d.ts +14 -0
- package/dist/workflow/snapshot.js +97 -0
- package/dist/workflow/snapshot.js.map +1 -0
- package/dist/workflow/state.d.ts +347 -0
- package/dist/workflow/state.js +322 -0
- package/dist/workflow/state.js.map +1 -0
- package/dist/workflow/sub-workflow.d.ts +174 -0
- package/dist/workflow/sub-workflow.js +392 -0
- package/dist/workflow/sub-workflow.js.map +1 -0
- package/dist/workflow/tool-service.d.ts +48 -0
- package/dist/workflow/tool-service.js +291 -0
- package/dist/workflow/tool-service.js.map +1 -0
- package/dist/workflow/turn-boundary.d.ts +39 -0
- package/dist/workflow/turn-boundary.js +128 -0
- package/dist/workflow/turn-boundary.js.map +1 -0
- package/dist/workflow/turn-runner.d.ts +40 -0
- package/dist/workflow/turn-runner.js +122 -0
- package/dist/workflow/turn-runner.js.map +1 -0
- package/dist/workflow/workflow-tools.d.ts +46 -0
- package/dist/workflow/workflow-tools.js +90 -0
- package/dist/workflow/workflow-tools.js.map +1 -0
- 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
|