@rulvar/core 1.5.2 → 1.6.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rulvar/core
2
2
 
3
- The rulvar engine in one dependency-light package: the L0 contracts and
3
+ The Rulvar engine in one dependency-light package: the L0 contracts and
4
4
  SPI interfaces, the journal kernel behind the never-pay-twice invariant,
5
5
  the model router with the capability and price registry, the agent
6
6
  runtime, the tool system and MCP bus, the `ctx` primitives and run
@@ -10,7 +10,7 @@ adapters plug in from their own packages. Key exports: `createEngine`,
10
10
  `defineWorkflow`, `tool`, `mcp`, `orchestrate`, `InMemoryStore`,
11
11
  `JsonlFileStore`.
12
12
 
13
- Part of [rulvar](https://rulvar.com), an embeddable TypeScript engine
13
+ Part of [Rulvar](https://rulvar.com), an embeddable TypeScript engine
14
14
  for durable, budget-bounded multi-agent LLM workflows, where a completed
15
15
  LLM call is never paid for twice. Full documentation:
16
16
  [docs.rulvar.com](https://docs.rulvar.com).
package/dist/index.d.ts CHANGED
@@ -1029,6 +1029,25 @@ declare function validateSchemaSpec<S extends SchemaSpec>(spec: S, value: unknow
1029
1029
  //#endregion
1030
1030
  //#region src/l0/spi/provider.d.ts
1031
1031
  /**
1032
+ * One long-context price tier. When the full prompt (canonical
1033
+ * inputTokens, cache included) is strictly above `aboveInputTokens`, the
1034
+ * ENTIRE request is re-priced with these multipliers, not only the tokens
1035
+ * past the threshold (how providers state their long-context rules).
1036
+ * `inputMultiplier` scales every input-side rate: input, cache read, and
1037
+ * cache write.
1038
+ * `outputMultiplier` scales the output rate. Provider pricing pages state
1039
+ * multipliers for "input" without saying whether cache rates scale;
1040
+ * scaling them with input is the conservative reading for budget
1041
+ * enforcement (it never underestimates spend). With several tiers, the
1042
+ * highest threshold below the prompt size wins, independent of array
1043
+ * order.
1044
+ */
1045
+ interface PricingTier {
1046
+ aboveInputTokens: number;
1047
+ inputMultiplier: number;
1048
+ outputMultiplier: number;
1049
+ }
1050
+ /**
1032
1051
  * Per-model pricing in USD per million tokens. The registry's
1033
1052
  * versioned price table wins over adapter-
1034
1053
  * reported caps.pricing, which is a fallback only.
@@ -1041,6 +1060,8 @@ interface Pricing {
1041
1060
  cacheWriteUsdPerMTok?: number;
1042
1061
  /** 1h write premium rate where the provider distinguishes. */
1043
1062
  cacheWrite1hUsdPerMTok?: number;
1063
+ /** Long-context tiers; a row without them is one linear price. */
1064
+ tiers?: PricingTier[];
1044
1065
  }
1045
1066
  /** Capability facts the router consumes for tier selection and scrubbing. */
1046
1067
  type ModelCaps = {
@@ -2740,6 +2761,15 @@ interface RuntimeEventSink {
2740
2761
  interface BudgetHooks {
2741
2762
  /** Layer 2: before every turn; throws BudgetExhaustedError to block dispatch. */
2742
2763
  beforeTurn(): void;
2764
+ /**
2765
+ * Layer 2b, the pre-dispatch output bound: the output tokens the
2766
+ * remaining budget still affords from `servedBy` for a prompt of
2767
+ * `estimatedInputTokens`. The dispatch clamps the request's
2768
+ * maxOutputTokens to it and denies the turn entirely when not even one
2769
+ * output token fits. Undefined = unbounded (no ceiling, no price row,
2770
+ * or free output).
2771
+ */
2772
+ maxAffordableOutputTokens?: (servedBy: ModelRef, estimatedInputTokens: number) => number | undefined;
2743
2773
  /** Live usage accounting; layer 3 may respond by aborting `signal`. */
2744
2774
  onUsage(usage: Usage, servedBy: ModelRef): void;
2745
2775
  /** Layer 3: the ceiling AbortSignal. */
@@ -3292,15 +3322,20 @@ declare const DEFAULT_FLAT_RESERVE_USD = .5;
3292
3322
  /** The run-root account scope. */
3293
3323
  declare const ROOT_ACCOUNT = "run";
3294
3324
  /**
3295
- * The admission reserve for a spawn: opts.estCost, else profile.estCost, else
3296
- * price(countTokens(input) + caps.maxOutputTokens), else the engine flat
3297
- * default.
3325
+ * The admission reserve for a spawn: opts.estCost, else profile.estCost,
3326
+ * else price(countTokens(input) + one turn's worth of output), else the
3327
+ * engine flat default. The output term is caps.maxOutputTokens clamped to
3328
+ * limits.maxOutputTokensPerTurn when the spawn carries one, so a host can
3329
+ * bound reserves without hand-written estimates. The priced path uses the
3330
+ * SAME price function as settlement (priceUsdOf), so long-context tiers
3331
+ * apply to estimates too.
3298
3332
  */
3299
3333
  declare function admissionReserveUsd(options: {
3300
3334
  estCost?: number;
3301
3335
  profileEstCost?: number;
3302
3336
  inputTokens?: number;
3303
3337
  caps?: ModelCaps;
3338
+ maxOutputTokensPerTurn?: number;
3304
3339
  flatReserveUsd?: number;
3305
3340
  }): number;
3306
3341
  /** Read-only projection of one account. */
@@ -3324,6 +3359,7 @@ declare class RunBudget {
3324
3359
  private readonly lifetimeSpawnCap;
3325
3360
  private readonly events?;
3326
3361
  private readonly priceUsd?;
3362
+ private readonly pricingOf?;
3327
3363
  private readonly accounts;
3328
3364
  private usageInternal;
3329
3365
  private agentsSpawnedInternal;
@@ -3334,7 +3370,8 @@ declare class RunBudget {
3334
3370
  ceilingUsd?: number;
3335
3371
  lifetimeSpawnCap?: number;
3336
3372
  events?: RuntimeEventSink;
3337
- priceUsd?: (servedBy: ModelRef, usage: Usage) => number | undefined;
3373
+ priceUsd?: (servedBy: ModelRef, usage: Usage) => number | undefined; /** Raw price-row resolution for the layer-2b output bound. */
3374
+ pricingOf?: (servedBy: ModelRef) => Pricing | undefined;
3338
3375
  /**
3339
3376
  * The resume ledger fold: spend is never
3340
3377
  * reset and never double-counted; replayed entries are already inside
@@ -3381,9 +3418,15 @@ declare class RunBudget {
3381
3418
  /** Spawn headroom under the engine lifetime cap (embedded in admission verdicts). */
3382
3419
  get spawnHeadroom(): number;
3383
3420
  /**
3384
- * Layer 1: admission before spawn. Blocks when spent + committedReserve
3385
- * has reached the ceiling on ANY account in the ancestor chain of
3386
- * `accountScope`, otherwise commits the reserve along the whole chain.
3421
+ * Layer 1: PROJECTED admission before spawn. A spawn is admitted only
3422
+ * when every account in the ancestor chain of `accountScope` still has
3423
+ * admission headroom AND fits the PROPOSED reserve on top of spent +
3424
+ * committedReserve + finalizeReserve (the finalize reserve is
3425
+ * untouchable by admission, DEF-7). An exact fill is allowed; one
3426
+ * dollar past the ceiling is not: a spawn is never admitted on the
3427
+ * argument that the money it needs is merely not committed yet. The
3428
+ * whole chain is checked before anything commits, so a rejection
3429
+ * mutates no account, increments no counter, and journals nothing.
3387
3430
  * Also enforces the engine lifetime spawn cap.
3388
3431
  */
3389
3432
  admitSpawn(reserveUsd: number, accountScope?: string): void;
@@ -3416,6 +3459,17 @@ declare class RunBudget {
3416
3459
  /** Layer 2: the per-turn guard. A turn that would cross any ceiling in the chain is not dispatched. */
3417
3460
  beforeTurn(accountScope?: string): void;
3418
3461
  /**
3462
+ * Layer 2b, the pre-dispatch output bound: the output tokens the
3463
+ * remaining chain budget (min over capped ancestors of ceiling minus
3464
+ * spend) still affords from `servedBy` for an estimated prompt, priced
3465
+ * by the same function as settlement, long-context tiers included.
3466
+ * Undefined when no account in the chain carries a USD ceiling, when
3467
+ * the model has no price row (the once-per-model unpriced warning in
3468
+ * onUsage covers that hole), or when output is free. Zero or negative
3469
+ * means the turn cannot be dispatched within the budget.
3470
+ */
3471
+ maxAffordableOutputTokens(servedBy: ModelRef, estimatedInputTokens: number, accountScope?: string): number | undefined;
3472
+ /**
3419
3473
  * Live accounting; spend propagates from `accountScope` to every
3420
3474
  * ancestor. Crossing a ceiling severs the crossing account's subtree
3421
3475
  * via its layer-3 AbortSignal (overshoot bounded by one turn per
@@ -4180,11 +4234,13 @@ type OnEscalation = (result: EscalatedResult<unknown>) => EscalationDecision | P
4180
4234
  /**
4181
4235
  * The mode (a) runner for human-authored closures. Determinism is enforced
4182
4236
  * by convention, lint, and the ctx shims, NOT by a VM: only the sequence
4183
- * of keys must be stable. Dev mode (NODE_ENV !== 'production') patches
4184
- * Date.now and Math.random for the duration of execute to emit one warning
4185
- * per run pointing at ctx.now()/ctx.random(); the patch preserves behavior
4186
- * and restores the prior functions on exit (nesting-safe by capturing the
4187
- * prior value; concurrent runs may lose the warning, never correctness).
4237
+ * of keys must be stable. Dev mode (NODE_ENV !== 'production') detects
4238
+ * bare Date.now and Math.random and emits one warning per run pointing at
4239
+ * ctx.now()/ctx.random(). Detection is attributed by AsyncLocalStorage:
4240
+ * only code inside the workflow body's async context can trigger it, so
4241
+ * host code running concurrently, engine internals outside the body, and
4242
+ * other runs never produce a false warning, and nothing is ever restored,
4243
+ * so concurrent executes cannot race the patch state.
4188
4244
  */
4189
4245
  declare class InProcessRunner implements ScriptRunner {
4190
4246
  private readonly onEscalation?;
@@ -4209,13 +4265,29 @@ interface PriceTable {
4209
4265
  */
4210
4266
  declare function resolvePricing(ref: ModelRef, table: PriceTable | undefined, capsPricing: Pricing | undefined): Pricing | undefined;
4211
4267
  /**
4212
- * Dollars from normalized usage against one pricing row (the adapter
4213
- * normalized the usage; inputTokens is the
4214
- * full prompt). Cache writes price at the 5m premium rate; the 1h rate
4215
- * applies where a provider distinguishes it in usage, which the
4268
+ * Dollars from normalized usage against one pricing row. Under the Usage
4269
+ * invariant inputTokens is the FULL prompt including cache reads and
4270
+ * writes, so the input rate bills only the uncached remainder and cache
4271
+ * tokens bill at their own rates, never twice; a row that omits a cache
4272
+ * rate bills those tokens at the plain input rate rather than silently
4273
+ * for free. A row may carry long-context tiers: the highest threshold
4274
+ * strictly below the full prompt re-prices the ENTIRE request
4275
+ * (input-side rates scale by inputMultiplier, the output rate by
4276
+ * outputMultiplier). Cache writes price at the 5m premium rate; the 1h
4277
+ * rate applies where a provider distinguishes it in usage, which the
4216
4278
  * canonical Usage does not yet carry.
4217
4279
  */
4218
4280
  declare function priceUsdOf(pricing: Pricing, usage: Usage): number;
4281
+ /**
4282
+ * The output tokens `remainingUsd` still buys from one pricing row after
4283
+ * paying for an estimated prompt of `estimatedInputTokens`, priced with
4284
+ * the same tier rules as settlement (the tier is selected by the
4285
+ * estimated prompt). Floored to whole tokens; zero or negative means not
4286
+ * even one output token fits, so the turn must not be dispatched.
4287
+ * Undefined when the row prices output at zero (a free model needs no
4288
+ * output bound).
4289
+ */
4290
+ declare function affordableOutputTokens(pricing: Pricing, remainingUsd: number, estimatedInputTokens: number): number | undefined;
4219
4291
  //#endregion
4220
4292
  //#region src/engine/engine.d.ts
4221
4293
  /**
@@ -4331,7 +4403,14 @@ interface CreateEngineOptions {
4331
4403
  interface RunOptions {
4332
4404
  /** Explicit id; otherwise the engine mints a ULID. */
4333
4405
  runId?: string;
4334
- /** Run ceiling B0; immutable after start. */
4406
+ /**
4407
+ * Run ceiling B0; immutable after start. Enforced by projected
4408
+ * admission (a spawn whose reserve does not fit is denied before any
4409
+ * dispatch), the per-turn guard with a budget-derived maxOutputTokens
4410
+ * clamp, and live stream cuts on crossing; the residual
4411
+ * provider-dependent overshoot is bounded by one in-flight turn per
4412
+ * concurrent agent. Contract: https://docs.rulvar.com/guide/budgets.
4413
+ */
4335
4414
  budgetUsd?: number;
4336
4415
  /** Run-level defaults merged over engine defaults. */
4337
4416
  limits?: UsageLimits;
@@ -5306,6 +5385,8 @@ interface RunInternals {
5306
5385
  dropped: DroppedItem[];
5307
5386
  cost: CostAttribution;
5308
5387
  priceUsd: (servedBy: ModelRef, usage: Usage) => number | undefined;
5388
+ /** Raw price-row resolution (table wins, caps fallback); undefined = unpriced. */
5389
+ pricingOf?: (servedBy: ModelRef) => Pricing | undefined;
5309
5390
  runSignal?: AbortSignal;
5310
5391
  /** The worktree lifecycle provider. */
5311
5392
  isolation?: IsolationProvider;
@@ -6100,4 +6181,4 @@ interface SandboxBridge {
6100
6181
  }
6101
6182
  declare function createSandboxBridge(ctx: Ctx<never>, options: SandboxBridgeOptions): SandboxBridge;
6102
6183
  //#endregion
6103
- export { AWAIT_SCHEMA, AbandonAttempt, AbandonFold, AbandonPayload, AbandonedSpendView, AbortClass, type AdaptiveEvents, AdmissionController, AdmissionDecision, AdmissionRejectedError, AdmissionStatsBefore, AdmitLineage, AdmitRejectReason, AdmitSpec, AdmitVerdict, AgentCallError, AgentError, type AgentEvents, AgentIdentityInput, AgentOpts, AgentProfile, AgentProfilePermissions, AgentResult, AgentResultMeta, AgentStatus, ApproachSignatureInputs, ApprovalDecision, ApprovalIdentityInput, Artifact, AttemptOutcomeClass, BUDGET_ABORT_REASON, BriefOpts, BudgetAccountView, BudgetDefaults, BudgetExhaustedError, BudgetHooks, BudgetReserve, type Bytes, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, CacheHint, CacheTtl, CanUseTool, CanonicalId, CanonicalIdentity, CanonicalLadderSpec, CanonicalModelSpec, ChatEvent, ChatRequest, CheckpointState, ChildIdentityInput, type ClaimClass, type ClaimOp, type ClaimStatus, ClaimValidationOptions, CollectOpts, CollectedTurn, CompactionConfig, CompiledPermissionChain, CompiledWorkflow, ConfigError, type CoreEvents, CostAttribution, CostReport, CreateEngineOptions, Ctx, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DebitResult, DeclaredLadder, DedupIndex, DedupNote, DerivedKey, DeriverRegistry, DispositionRule, DispositionTable, DonorCandidate, DonorRef, DroppedItem, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EffectiveUsageLimits, Effort, Engine, EngineDefaults, EntryKind, EntryRef, EntryStatus, ErrorClass, ErrorCode, ErrorPolicy, EscalatedResult, EscalationDecision, EscalationDigest, EscalationKind, EscalationLimits, EscalationOptions, EscalationReport, EscalationRequest, EventBus, type EvidenceRef, ExtensionAppendInput, ExtensionDispatchSpec, ExternalIdentityInput, ExternalRegistry, ExtractNecessityInput, FINISH_SCHEMA, FINISH_TOOL_NAME, FailoverTarget, FailoverTrigger, FallbackField, FallbackTrigger, FileModelKnowledgeStore, FileModelKnowledgeStoreOptions, FileTranscriptStore, FinishInfo, Gate, GateAudit, type GateRecord, GitWorktreeProvider, GitWorktreeProviderOptions, GraftBoot, HashVersion, HookVerdict, INBOX_PROPOSAL_TTL_DAYS, IdentityInput, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, InvocationRole, type IsolationProvider, type IsolationSpec, Issue$1 as Issue, JournalCompatSubCode, JournalCompatibilityError, JournalEntry, JournalMatcher, JournalMissError, JournalOperation, JournalOrderViolation, JournalSerializationHook, type JournalStore, type Json, JsonSchema, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, type KbProposal, type KbProposalTrigger, KeyDeriver, KeyRing, KeyedLimiter, KnowledgeCasError, type KnowledgeSnapshot, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LadderSpec, type LeasableStore, type Lease, LeaseHeldError, Ledger, LineageCounters, LineageIndex, LineageRef, LineageRelation, LineageStats, LogicalTaskId, MASKED_SECRET, MAX_DEPTH_CEILING, MatchResult, McpConfig, MechanicalGateProfile, MechanicalGateVerdict, type ModelCaps, ModelChoice, type ModelClaim, ModelEpochInputs, type ModelKnowledgeHandle, type ModelKnowledgeStore, ModelListConstraint, ModelRef, ModelRetry, ModelSpec, Msg, NoProgressDetector, NodeId, NodeLinkValue, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OnEscalation, OperationDisposition, OrchestrateOptions, OrchestratorBudgetSpec, OrchestratorCapConfigError, OrchestratorExtension, OrchestratorExtensionIO, OrchestratorRuntime, Out, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, Part, PendingExternal, PendingToolTurn, PermissionConfig, PermissionGate, PermissionHook, PermissionPreset, PermissionRule, PermissionVerdict, PhaseTarget, PipelineCollected, PipelineOpts, PlanInvariantError, PriceTable, PricedUsage, type Pricing, type ProviderAdapter, QualityFloors, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RandIdentityInput, RandPayload, RefEntryAppender, RefEntryClassification, RefusalInfo, ReplayDisposition, ReplayMode, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionAttempt, ResolutionBy, ResolutionFold, ResolutionLayer, ResolutionOutcome, ResolutionPayload, ResolvedInvocation, ResolvedToolset, ResumeHandle, ResumeOptions, ResumePreview, ResumeReport, RetryClass, RetryPolicy, ReuseConfig, RiskRuleValue, Role, RulvarError, RulvarErrorCode, RunAgentOptions, RunBudget, RunEventSink, type RunFilter, RunHandle, RunInternals, type RunMeta, RunOptions, RunOutcome, RunProfile, RunStatus, RuntimeEventSink, SPAWN_AGENT_SCHEMA, SandboxBridge, SandboxBridgeOptions, SandboxError, SandboxHostToWorker, SandboxMethod, SandboxWorkerToHost, SchemaPair, SchemaSpec, SchemaValidationResult, ScopeSegment, ScriptRejected, ScriptRunner, ScrubNote, Semaphore, SerializationHook, Settled, ShellPatternRules, ShellSegment, ShellVerdict, SinglePhaseAppend, SpanMinter, SpanRegistry, SpawnAdmissionValue, SpawnAgentParams, SpawnKey, SpawnLineage, SpawnLineageOpt, SpawnOrigin, SpawnRecord, Spend, Stage, type StandardJSONSchemaV1, type StandardSchemaV1, StepIdentityInput, StructuredOutputTier, SuspendedAppend, SuspensionState, TOOL_NAME_PATTERN, type TaskClass, TaskDigest, TaskSpec, TerminalPatch, TerminationAccount, TerminationAccountSnapshot, TerminationDeniedValue, TerminationDeniedWriter, TerminationInitValue, TerminationLimits, TerminationResource, ToolCallRequest, ToolChoice, type ToolContext, ToolContextSeed, ToolContract, type ToolDef, type ToolEvents, type ToolExecutor, ToolInit, type ToolRisk, ToolRuntime, type ToolSource, type ToolSourceSession, ToolsOption, TranscriptSerializationHook, type TranscriptStore, TriggerClass, TtlState, Usage, UsageLimits, UsageSlice, VerifiedRecommendation, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, WakeBudgetBlock, WakeDigest, WakeTrigger, WireError, Workflow, WorkflowCallOpts, type WorkflowEvent, type WorkflowEventBody, WorkflowRegistry, admissionReserveUsd, agentErrorFromWire, agentErrorToWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateSchemaSpec, validateTerminationLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
6184
+ export { AWAIT_SCHEMA, AbandonAttempt, AbandonFold, AbandonPayload, AbandonedSpendView, AbortClass, type AdaptiveEvents, AdmissionController, AdmissionDecision, AdmissionRejectedError, AdmissionStatsBefore, AdmitLineage, AdmitRejectReason, AdmitSpec, AdmitVerdict, AgentCallError, AgentError, type AgentEvents, AgentIdentityInput, AgentOpts, AgentProfile, AgentProfilePermissions, AgentResult, AgentResultMeta, AgentStatus, ApproachSignatureInputs, ApprovalDecision, ApprovalIdentityInput, Artifact, AttemptOutcomeClass, BUDGET_ABORT_REASON, BriefOpts, BudgetAccountView, BudgetDefaults, BudgetExhaustedError, BudgetHooks, BudgetReserve, type Bytes, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, CacheHint, CacheTtl, CanUseTool, CanonicalId, CanonicalIdentity, CanonicalLadderSpec, CanonicalModelSpec, ChatEvent, ChatRequest, CheckpointState, ChildIdentityInput, type ClaimClass, type ClaimOp, type ClaimStatus, ClaimValidationOptions, CollectOpts, CollectedTurn, CompactionConfig, CompiledPermissionChain, CompiledWorkflow, ConfigError, type CoreEvents, CostAttribution, CostReport, CreateEngineOptions, Ctx, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DebitResult, DeclaredLadder, DedupIndex, DedupNote, DerivedKey, DeriverRegistry, DispositionRule, DispositionTable, DonorCandidate, DonorRef, DroppedItem, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EffectiveUsageLimits, Effort, Engine, EngineDefaults, EntryKind, EntryRef, EntryStatus, ErrorClass, ErrorCode, ErrorPolicy, EscalatedResult, EscalationDecision, EscalationDigest, EscalationKind, EscalationLimits, EscalationOptions, EscalationReport, EscalationRequest, EventBus, type EvidenceRef, ExtensionAppendInput, ExtensionDispatchSpec, ExternalIdentityInput, ExternalRegistry, ExtractNecessityInput, FINISH_SCHEMA, FINISH_TOOL_NAME, FailoverTarget, FailoverTrigger, FallbackField, FallbackTrigger, FileModelKnowledgeStore, FileModelKnowledgeStoreOptions, FileTranscriptStore, FinishInfo, Gate, GateAudit, type GateRecord, GitWorktreeProvider, GitWorktreeProviderOptions, GraftBoot, HashVersion, HookVerdict, INBOX_PROPOSAL_TTL_DAYS, IdentityInput, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, InvocationRole, type IsolationProvider, type IsolationSpec, Issue$1 as Issue, JournalCompatSubCode, JournalCompatibilityError, JournalEntry, JournalMatcher, JournalMissError, JournalOperation, JournalOrderViolation, JournalSerializationHook, type JournalStore, type Json, JsonSchema, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, type KbProposal, type KbProposalTrigger, KeyDeriver, KeyRing, KeyedLimiter, KnowledgeCasError, type KnowledgeSnapshot, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LadderSpec, type LeasableStore, type Lease, LeaseHeldError, Ledger, LineageCounters, LineageIndex, LineageRef, LineageRelation, LineageStats, LogicalTaskId, MASKED_SECRET, MAX_DEPTH_CEILING, MatchResult, McpConfig, MechanicalGateProfile, MechanicalGateVerdict, type ModelCaps, ModelChoice, type ModelClaim, ModelEpochInputs, type ModelKnowledgeHandle, type ModelKnowledgeStore, ModelListConstraint, ModelRef, ModelRetry, ModelSpec, Msg, NoProgressDetector, NodeId, NodeLinkValue, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OnEscalation, OperationDisposition, OrchestrateOptions, OrchestratorBudgetSpec, OrchestratorCapConfigError, OrchestratorExtension, OrchestratorExtensionIO, OrchestratorRuntime, Out, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, Part, PendingExternal, PendingToolTurn, PermissionConfig, PermissionGate, PermissionHook, PermissionPreset, PermissionRule, PermissionVerdict, PhaseTarget, PipelineCollected, PipelineOpts, PlanInvariantError, PriceTable, PricedUsage, type Pricing, type PricingTier, type ProviderAdapter, QualityFloors, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RandIdentityInput, RandPayload, RefEntryAppender, RefEntryClassification, RefusalInfo, ReplayDisposition, ReplayMode, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionAttempt, ResolutionBy, ResolutionFold, ResolutionLayer, ResolutionOutcome, ResolutionPayload, ResolvedInvocation, ResolvedToolset, ResumeHandle, ResumeOptions, ResumePreview, ResumeReport, RetryClass, RetryPolicy, ReuseConfig, RiskRuleValue, Role, RulvarError, RulvarErrorCode, RunAgentOptions, RunBudget, RunEventSink, type RunFilter, RunHandle, RunInternals, type RunMeta, RunOptions, RunOutcome, RunProfile, RunStatus, RuntimeEventSink, SPAWN_AGENT_SCHEMA, SandboxBridge, SandboxBridgeOptions, SandboxError, SandboxHostToWorker, SandboxMethod, SandboxWorkerToHost, SchemaPair, SchemaSpec, SchemaValidationResult, ScopeSegment, ScriptRejected, ScriptRunner, ScrubNote, Semaphore, SerializationHook, Settled, ShellPatternRules, ShellSegment, ShellVerdict, SinglePhaseAppend, SpanMinter, SpanRegistry, SpawnAdmissionValue, SpawnAgentParams, SpawnKey, SpawnLineage, SpawnLineageOpt, SpawnOrigin, SpawnRecord, Spend, Stage, type StandardJSONSchemaV1, type StandardSchemaV1, StepIdentityInput, StructuredOutputTier, SuspendedAppend, SuspensionState, TOOL_NAME_PATTERN, type TaskClass, TaskDigest, TaskSpec, TerminalPatch, TerminationAccount, TerminationAccountSnapshot, TerminationDeniedValue, TerminationDeniedWriter, TerminationInitValue, TerminationLimits, TerminationResource, ToolCallRequest, ToolChoice, type ToolContext, ToolContextSeed, ToolContract, type ToolDef, type ToolEvents, type ToolExecutor, ToolInit, type ToolRisk, ToolRuntime, type ToolSource, type ToolSourceSession, ToolsOption, TranscriptSerializationHook, type TranscriptStore, TriggerClass, TtlState, Usage, UsageLimits, UsageSlice, VerifiedRecommendation, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, WakeBudgetBlock, WakeDigest, WakeTrigger, WireError, Workflow, WorkflowCallOpts, type WorkflowEvent, type WorkflowEventBody, WorkflowRegistry, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateSchemaSpec, validateTerminationLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
package/dist/index.js CHANGED
@@ -6495,15 +6495,51 @@ function fallbackTriggerOf(outcome) {
6495
6495
  function resolvePricing(ref, table, capsPricing) {
6496
6496
  return table?.models[ref] ?? capsPricing;
6497
6497
  }
6498
- /**
6499
- * Dollars from normalized usage against one pricing row (the adapter
6500
- * normalized the usage; inputTokens is the
6501
- * full prompt). Cache writes price at the 5m premium rate; the 1h rate
6502
- * applies where a provider distinguishes it in usage, which the
6498
+ /** The tier a full prompt lands in: the highest threshold strictly below it. */
6499
+ function tierFor(pricing, inputTokens) {
6500
+ let tier;
6501
+ for (const candidate of pricing.tiers ?? []) if (inputTokens > candidate.aboveInputTokens && (tier === void 0 || candidate.aboveInputTokens > tier.aboveInputTokens)) tier = candidate;
6502
+ return tier;
6503
+ }
6504
+ /**
6505
+ * Dollars from normalized usage against one pricing row. Under the Usage
6506
+ * invariant inputTokens is the FULL prompt including cache reads and
6507
+ * writes, so the input rate bills only the uncached remainder and cache
6508
+ * tokens bill at their own rates, never twice; a row that omits a cache
6509
+ * rate bills those tokens at the plain input rate rather than silently
6510
+ * for free. A row may carry long-context tiers: the highest threshold
6511
+ * strictly below the full prompt re-prices the ENTIRE request
6512
+ * (input-side rates scale by inputMultiplier, the output rate by
6513
+ * outputMultiplier). Cache writes price at the 5m premium rate; the 1h
6514
+ * rate applies where a provider distinguishes it in usage, which the
6503
6515
  * canonical Usage does not yet carry.
6504
6516
  */
6505
6517
  function priceUsdOf(pricing, usage) {
6506
- return usage.inputTokens / 1e6 * pricing.inputUsdPerMTok + usage.outputTokens / 1e6 * pricing.outputUsdPerMTok + usage.cacheReadTokens / 1e6 * (pricing.cacheReadUsdPerMTok ?? 0) + usage.cacheWriteTokens / 1e6 * (pricing.cacheWriteUsdPerMTok ?? 0);
6518
+ const tier = tierFor(pricing, usage.inputTokens);
6519
+ const inputMul = tier?.inputMultiplier ?? 1;
6520
+ const outputMul = tier?.outputMultiplier ?? 1;
6521
+ return Math.max(0, usage.inputTokens - usage.cacheReadTokens - usage.cacheWriteTokens) / 1e6 * pricing.inputUsdPerMTok * inputMul + usage.outputTokens / 1e6 * pricing.outputUsdPerMTok * outputMul + usage.cacheReadTokens / 1e6 * (pricing.cacheReadUsdPerMTok ?? pricing.inputUsdPerMTok) * inputMul + usage.cacheWriteTokens / 1e6 * (pricing.cacheWriteUsdPerMTok ?? pricing.inputUsdPerMTok) * inputMul;
6522
+ }
6523
+ /**
6524
+ * The output tokens `remainingUsd` still buys from one pricing row after
6525
+ * paying for an estimated prompt of `estimatedInputTokens`, priced with
6526
+ * the same tier rules as settlement (the tier is selected by the
6527
+ * estimated prompt). Floored to whole tokens; zero or negative means not
6528
+ * even one output token fits, so the turn must not be dispatched.
6529
+ * Undefined when the row prices output at zero (a free model needs no
6530
+ * output bound).
6531
+ */
6532
+ function affordableOutputTokens(pricing, remainingUsd, estimatedInputTokens) {
6533
+ const tier = tierFor(pricing, estimatedInputTokens);
6534
+ const outputRate = pricing.outputUsdPerMTok * (tier?.outputMultiplier ?? 1);
6535
+ if (outputRate <= 0) return;
6536
+ const inputUsd = priceUsdOf(pricing, {
6537
+ inputTokens: estimatedInputTokens,
6538
+ outputTokens: 0,
6539
+ cacheReadTokens: 0,
6540
+ cacheWriteTokens: 0
6541
+ });
6542
+ return Math.floor((remainingUsd - inputUsd) / outputRate * 1e6);
6507
6543
  }
6508
6544
  //#endregion
6509
6545
  //#region src/model/profile-card.ts
@@ -7681,6 +7717,50 @@ function buildRequest(resolved, messages, limits, tools) {
7681
7717
  return req;
7682
7718
  }
7683
7719
  /**
7720
+ * Cheap deterministic prompt-size estimate (about four serialized
7721
+ * characters per token) for the layer-2b output bound. Never used for
7722
+ * identity, accounting, or anything the journal records.
7723
+ */
7724
+ function estimateInputTokens(messages) {
7725
+ let chars = 0;
7726
+ for (const msg of messages) chars += JSON.stringify(msg.parts).length;
7727
+ return Math.ceil(chars / 4);
7728
+ }
7729
+ /**
7730
+ * Layer 2b at the wire boundary: clamps the outgoing request's
7731
+ * maxOutputTokens to what the remaining budget affords from the serving
7732
+ * model. The clamp uses the heuristic prompt estimate; the DENIAL does
7733
+ * not: a turn is refused (BudgetExhaustedError, never dispatched) only
7734
+ * when the remainder cannot buy even ONE output token at zero input,
7735
+ * which is exact. Denying on the estimate would kill turns the budget
7736
+ * still funds, including the DEF-7 forced finish paid from the released
7737
+ * finalize reserve; when the estimate says the prompt alone spends the
7738
+ * remainder, the turn dispatches with a one-token output floor and the
7739
+ * exact layers (2 and 3) settle the difference. A no-op without a hook
7740
+ * or when the hook reports no bound. The clamp touches only the wire
7741
+ * request, exactly like limits.maxOutputTokensPerTurn above it; identity
7742
+ * is computed at the ctx layer and never sees it.
7743
+ */
7744
+ function applyOutputBudget(req, target, budget) {
7745
+ const hook = budget?.maxAffordableOutputTokens;
7746
+ if (hook === void 0) return req;
7747
+ const affordable = hook(target.resolved.ref, estimateInputTokens(req.messages));
7748
+ if (affordable === void 0) return req;
7749
+ if (affordable < 1) {
7750
+ const zeroInputAffordable = hook(target.resolved.ref, 0);
7751
+ if (zeroInputAffordable !== void 0 && zeroInputAffordable < 1) throw new BudgetExhaustedError(`the remaining budget cannot afford one output token from ${target.resolved.ref}; the turn was not dispatched`);
7752
+ return {
7753
+ ...req,
7754
+ maxOutputTokens: 1
7755
+ };
7756
+ }
7757
+ if (req.maxOutputTokens === void 0 || affordable < req.maxOutputTokens) return {
7758
+ ...req,
7759
+ maxOutputTokens: affordable
7760
+ };
7761
+ return req;
7762
+ }
7763
+ /**
7684
7764
  * Builds the turn's canonical assistant message. Retained provider-raw
7685
7765
  * parts go at the HEAD: on both first-class providers the retained
7686
7766
  * blocks (thinking blocks, reasoning items) precede the turn's text and
@@ -8168,28 +8248,41 @@ async function runAgent(options) {
8168
8248
  turns += 1;
8169
8249
  const signals = [];
8170
8250
  if (options.signal !== void 0) signals.push(options.signal);
8171
- const { outcome, target: servedTarget } = await dispatchPhase({
8172
- chain: loopChain,
8173
- cursor: loopCursor,
8174
- requestFor: (target) => {
8175
- let req = buildRequest(target.resolved, projectHistory(messages, providerOf(target.adapter)), limits, options.tools?.contracts);
8176
- if (options.schema !== void 0 && options.canonicalSchema !== void 0 && !separateExtract) req = applyStructuredOutputTier(req, rideTierFor(target), options.canonicalSchema);
8177
- return req;
8178
- },
8179
- streamOptionsFor: (target) => {
8180
- const streamTurnOptions = {
8181
- idleTimeoutMs: limits.streamIdleTimeoutMs,
8182
- signals,
8183
- onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
8184
- };
8185
- if (options.budget?.signal !== void 0) streamTurnOptions.budgetSignal = options.budget.signal;
8186
- if (options.stream === true) streamTurnOptions.onDelta = (delta) => events?.emit({
8187
- type: "agent:stream",
8188
- delta
8189
- });
8190
- return streamTurnOptions;
8191
- }
8192
- });
8251
+ let loopDispatch;
8252
+ try {
8253
+ loopDispatch = await dispatchPhase({
8254
+ chain: loopChain,
8255
+ cursor: loopCursor,
8256
+ requestFor: (target) => {
8257
+ let req = buildRequest(target.resolved, projectHistory(messages, providerOf(target.adapter)), limits, options.tools?.contracts);
8258
+ if (options.schema !== void 0 && options.canonicalSchema !== void 0 && !separateExtract) req = applyStructuredOutputTier(req, rideTierFor(target), options.canonicalSchema);
8259
+ return applyOutputBudget(req, target, options.budget);
8260
+ },
8261
+ streamOptionsFor: (target) => {
8262
+ const streamTurnOptions = {
8263
+ idleTimeoutMs: limits.streamIdleTimeoutMs,
8264
+ signals,
8265
+ onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
8266
+ };
8267
+ if (options.budget?.signal !== void 0) streamTurnOptions.budgetSignal = options.budget.signal;
8268
+ if (options.stream === true) streamTurnOptions.onDelta = (delta) => events?.emit({
8269
+ type: "agent:stream",
8270
+ delta
8271
+ });
8272
+ return streamTurnOptions;
8273
+ }
8274
+ });
8275
+ } catch (thrown) {
8276
+ if (!(thrown instanceof BudgetExhaustedError)) throw thrown;
8277
+ status = "error";
8278
+ agentError = {
8279
+ kind: "budget",
8280
+ retryable: false
8281
+ };
8282
+ errorMessage = thrown.message;
8283
+ break;
8284
+ }
8285
+ const { outcome, target: servedTarget } = loopDispatch;
8193
8286
  servedBy = servedTarget.resolved.ref;
8194
8287
  usageApprox = usageApprox || outcome.usageApprox;
8195
8288
  lastTurnUsage = {
@@ -8346,30 +8439,43 @@ async function runAgent(options) {
8346
8439
  break;
8347
8440
  }
8348
8441
  turns += 1;
8349
- const { outcome: summary } = await dispatchPhase({
8350
- chain: [{
8351
- adapter: options.summarize.adapter,
8352
- resolved: options.summarize.resolved
8353
- }, ...options.summarize.fallbacks ?? []],
8354
- cursor: { index: 0 },
8355
- requestFor: (target) => {
8356
- let req = buildRequest(target.resolved, [...projectHistory(messages, providerOf(target.adapter)), summarizeInstruction()], limits, options.tools?.contracts);
8357
- if (req.tools !== void 0) req = {
8358
- ...req,
8359
- toolChoice: "none"
8360
- };
8361
- return req;
8362
- },
8363
- streamOptionsFor: (target) => {
8364
- const summarizeStreamOptions = {
8365
- idleTimeoutMs: limits.streamIdleTimeoutMs,
8366
- signals: options.signal === void 0 ? [] : [options.signal],
8367
- onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
8368
- };
8369
- if (options.budget?.signal !== void 0) summarizeStreamOptions.budgetSignal = options.budget.signal;
8370
- return summarizeStreamOptions;
8371
- }
8372
- });
8442
+ let summaryDispatch;
8443
+ try {
8444
+ summaryDispatch = await dispatchPhase({
8445
+ chain: [{
8446
+ adapter: options.summarize.adapter,
8447
+ resolved: options.summarize.resolved
8448
+ }, ...options.summarize.fallbacks ?? []],
8449
+ cursor: { index: 0 },
8450
+ requestFor: (target) => {
8451
+ let req = buildRequest(target.resolved, [...projectHistory(messages, providerOf(target.adapter)), summarizeInstruction()], limits, options.tools?.contracts);
8452
+ if (req.tools !== void 0) req = {
8453
+ ...req,
8454
+ toolChoice: "none"
8455
+ };
8456
+ return applyOutputBudget(req, target, options.budget);
8457
+ },
8458
+ streamOptionsFor: (target) => {
8459
+ const summarizeStreamOptions = {
8460
+ idleTimeoutMs: limits.streamIdleTimeoutMs,
8461
+ signals: options.signal === void 0 ? [] : [options.signal],
8462
+ onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
8463
+ };
8464
+ if (options.budget?.signal !== void 0) summarizeStreamOptions.budgetSignal = options.budget.signal;
8465
+ return summarizeStreamOptions;
8466
+ }
8467
+ });
8468
+ } catch (thrown) {
8469
+ if (!(thrown instanceof BudgetExhaustedError)) throw thrown;
8470
+ status = "error";
8471
+ agentError = {
8472
+ kind: "budget",
8473
+ retryable: false
8474
+ };
8475
+ errorMessage = thrown.message;
8476
+ break;
8477
+ }
8478
+ const { outcome: summary } = summaryDispatch;
8373
8479
  usageApprox = usageApprox || summary.usageApprox;
8374
8480
  if (summary.aborted === "budget") {
8375
8481
  status = "cancelled";
@@ -8473,66 +8579,80 @@ async function runAgent(options) {
8473
8579
  }
8474
8580
  if (proceed) {
8475
8581
  turns += 1;
8476
- const { outcome, target: finalizeTarget } = await dispatchPhase({
8477
- chain: [{
8478
- adapter: options.finalize.adapter,
8479
- resolved: options.finalize.resolved
8480
- }, ...options.finalize.fallbacks ?? []],
8481
- cursor: { index: 0 },
8482
- requestFor: (target) => ({
8483
- ...buildRequest(target.resolved, projectHistory(messages, providerOf(target.adapter)), limits, options.tools?.contracts),
8484
- toolChoice: "none"
8485
- }),
8486
- streamOptionsFor: (target) => {
8487
- const finalizeStreamOptions = {
8488
- idleTimeoutMs: limits.streamIdleTimeoutMs,
8489
- signals: options.signal === void 0 ? [] : [options.signal],
8490
- onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
8491
- };
8492
- if (options.budget?.signal !== void 0) finalizeStreamOptions.budgetSignal = options.budget.signal;
8493
- if (options.stream === true) finalizeStreamOptions.onDelta = (delta) => events?.emit({
8494
- type: "agent:stream",
8495
- delta
8496
- });
8497
- return finalizeStreamOptions;
8498
- }
8499
- });
8500
- usageApprox = usageApprox || outcome.usageApprox;
8501
- messages.push(assistantMsg(outcome.turn, liftRetainedParts(outcome.providerMetadata, finalizeTarget.adapter)));
8502
- if (invariantViolation !== void 0) {
8582
+ let finalizeDispatch;
8583
+ try {
8584
+ finalizeDispatch = await dispatchPhase({
8585
+ chain: [{
8586
+ adapter: options.finalize.adapter,
8587
+ resolved: options.finalize.resolved
8588
+ }, ...options.finalize.fallbacks ?? []],
8589
+ cursor: { index: 0 },
8590
+ requestFor: (target) => applyOutputBudget({
8591
+ ...buildRequest(target.resolved, projectHistory(messages, providerOf(target.adapter)), limits, options.tools?.contracts),
8592
+ toolChoice: "none"
8593
+ }, target, options.budget),
8594
+ streamOptionsFor: (target) => {
8595
+ const finalizeStreamOptions = {
8596
+ idleTimeoutMs: limits.streamIdleTimeoutMs,
8597
+ signals: options.signal === void 0 ? [] : [options.signal],
8598
+ onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
8599
+ };
8600
+ if (options.budget?.signal !== void 0) finalizeStreamOptions.budgetSignal = options.budget.signal;
8601
+ if (options.stream === true) finalizeStreamOptions.onDelta = (delta) => events?.emit({
8602
+ type: "agent:stream",
8603
+ delta
8604
+ });
8605
+ return finalizeStreamOptions;
8606
+ }
8607
+ });
8608
+ } catch (thrown) {
8609
+ if (!(thrown instanceof BudgetExhaustedError)) throw thrown;
8503
8610
  status = "error";
8504
8611
  agentError = {
8505
- kind: "transport",
8612
+ kind: "budget",
8506
8613
  retryable: false
8507
8614
  };
8508
- errorMessage = invariantViolation;
8509
- } else if (outcome.aborted !== void 0 || outcome.wireError !== void 0) {
8510
- status = outcome.aborted === "external" ? "cancelled" : "error";
8511
- if (outcome.wireError !== void 0) {
8512
- agentError = classifyWireError(outcome.wireError);
8513
- errorMessage = outcome.wireError.message;
8514
- } else if (outcome.aborted === "budget") {
8515
- status = "cancelled";
8615
+ errorMessage = thrown.message;
8616
+ }
8617
+ if (finalizeDispatch !== void 0) {
8618
+ const { outcome, target: finalizeTarget } = finalizeDispatch;
8619
+ usageApprox = usageApprox || outcome.usageApprox;
8620
+ messages.push(assistantMsg(outcome.turn, liftRetainedParts(outcome.providerMetadata, finalizeTarget.adapter)));
8621
+ if (invariantViolation !== void 0) {
8622
+ status = "error";
8516
8623
  agentError = {
8517
- kind: "budget",
8624
+ kind: "transport",
8518
8625
  retryable: false
8519
8626
  };
8520
- } else if (outcome.aborted === "idle") {
8627
+ errorMessage = invariantViolation;
8628
+ } else if (outcome.aborted !== void 0 || outcome.wireError !== void 0) {
8629
+ status = outcome.aborted === "external" ? "cancelled" : "error";
8630
+ if (outcome.wireError !== void 0) {
8631
+ agentError = classifyWireError(outcome.wireError);
8632
+ errorMessage = outcome.wireError.message;
8633
+ } else if (outcome.aborted === "budget") {
8634
+ status = "cancelled";
8635
+ agentError = {
8636
+ kind: "budget",
8637
+ retryable: false
8638
+ };
8639
+ } else if (outcome.aborted === "idle") {
8640
+ status = "error";
8641
+ agentError = {
8642
+ kind: "transport",
8643
+ retryable: true
8644
+ };
8645
+ errorMessage = `stream idle for ${limits.streamIdleTimeoutMs}ms`;
8646
+ }
8647
+ } else if (outcome.finish?.reason === "refusal" || outcome.finish?.reason === "context-window-exceeded") {
8521
8648
  status = "error";
8522
8649
  agentError = {
8523
- kind: "transport",
8524
- retryable: true
8650
+ kind: "terminal",
8651
+ retryable: false
8525
8652
  };
8526
- errorMessage = `stream idle for ${limits.streamIdleTimeoutMs}ms`;
8527
- }
8528
- } else if (outcome.finish?.reason === "refusal" || outcome.finish?.reason === "context-window-exceeded") {
8529
- status = "error";
8530
- agentError = {
8531
- kind: "terminal",
8532
- retryable: false
8533
- };
8534
- if (outcome.finish.reason === "refusal") errorMessage = `model refusal (${outcome.finish.refusal.provider})`;
8535
- } else if (options.schema === void 0) output = outcome.turn.text;
8653
+ if (outcome.finish.reason === "refusal") errorMessage = `model refusal (${outcome.finish.refusal.provider})`;
8654
+ } else if (options.schema === void 0) output = outcome.turn.text;
8655
+ }
8536
8656
  }
8537
8657
  }
8538
8658
  if (status === "ok" && !finishedViaTool && separateExtract && options.extract !== void 0 && options.schema !== void 0) {
@@ -8570,28 +8690,42 @@ async function runAgent(options) {
8570
8690
  break;
8571
8691
  }
8572
8692
  turns += 1;
8573
- const { outcome, target: extractTarget } = await dispatchPhase({
8574
- chain: extractChain,
8575
- cursor: extractCursor,
8576
- requestFor: (target) => {
8577
- const targetTier = extractTierFor(target);
8578
- let req = buildRequest(target.resolved, projectHistory(extractMessages, providerOf(target.adapter)), limits, options.tools?.contracts);
8579
- if (req.tools !== void 0 && targetTier !== "forced-tool") req = {
8580
- ...req,
8581
- toolChoice: "none"
8582
- };
8583
- return applyStructuredOutputTier(req, targetTier, options.canonicalSchema ?? {});
8584
- },
8585
- streamOptionsFor: (target) => {
8586
- const extractStreamOptions = {
8587
- idleTimeoutMs: limits.streamIdleTimeoutMs,
8588
- signals: options.signal === void 0 ? [] : [options.signal],
8589
- onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
8590
- };
8591
- if (options.budget?.signal !== void 0) extractStreamOptions.budgetSignal = options.budget.signal;
8592
- return extractStreamOptions;
8593
- }
8594
- });
8693
+ let extractDispatch;
8694
+ try {
8695
+ extractDispatch = await dispatchPhase({
8696
+ chain: extractChain,
8697
+ cursor: extractCursor,
8698
+ requestFor: (target) => {
8699
+ const targetTier = extractTierFor(target);
8700
+ let req = buildRequest(target.resolved, projectHistory(extractMessages, providerOf(target.adapter)), limits, options.tools?.contracts);
8701
+ if (req.tools !== void 0 && targetTier !== "forced-tool") req = {
8702
+ ...req,
8703
+ toolChoice: "none"
8704
+ };
8705
+ req = applyStructuredOutputTier(req, targetTier, options.canonicalSchema ?? {});
8706
+ return applyOutputBudget(req, target, options.budget);
8707
+ },
8708
+ streamOptionsFor: (target) => {
8709
+ const extractStreamOptions = {
8710
+ idleTimeoutMs: limits.streamIdleTimeoutMs,
8711
+ signals: options.signal === void 0 ? [] : [options.signal],
8712
+ onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
8713
+ };
8714
+ if (options.budget?.signal !== void 0) extractStreamOptions.budgetSignal = options.budget.signal;
8715
+ return extractStreamOptions;
8716
+ }
8717
+ });
8718
+ } catch (thrown) {
8719
+ if (!(thrown instanceof BudgetExhaustedError)) throw thrown;
8720
+ status = "error";
8721
+ agentError = {
8722
+ kind: "budget",
8723
+ retryable: false
8724
+ };
8725
+ errorMessage = thrown.message;
8726
+ break;
8727
+ }
8728
+ const { outcome, target: extractTarget } = extractDispatch;
8595
8729
  usageApprox = usageApprox || outcome.usageApprox;
8596
8730
  if (invariantViolation !== void 0) {
8597
8731
  status = "error";
@@ -8674,10 +8808,16 @@ async function runAgent(options) {
8674
8808
  //#region src/engine/budget.ts
8675
8809
  /**
8676
8810
  * Three-layer budget (M1-T09, hierarchical sub-accounts M6-T06;
8677
- * invariant I4). Layer 1: admission before spawn (spent + committedReserve
8678
- * >= ceiling blocks on ANY account in the ancestor chain). Layer 2: the
8679
- * per-turn guard against the spawn's own chain. Layer 3: the AbortSignal
8680
- * ceiling severing live streams, with partial usage written usageApprox.
8811
+ * invariant I4). Layer 1: PROJECTED admission before spawn: a spawn is
8812
+ * admitted only when spent + committedReserve + finalizeReserve + the
8813
+ * PROPOSED reserve fits the ceiling of EVERY account in the ancestor
8814
+ * chain (exact fill allowed), checked atomically before any commit.
8815
+ * Layer 2: the per-turn guard against the spawn's own chain, plus the
8816
+ * pre-dispatch output bound (layer 2b): every turn's maxOutputTokens is
8817
+ * clamped to what the remaining chain budget affords from the serving
8818
+ * model, and a turn that cannot afford one output token is denied before
8819
+ * dispatch. Layer 3: the AbortSignal ceiling severing live streams, with
8820
+ * partial usage written usageApprox.
8681
8821
  * B0 is immutable after start: no API tops it up.
8682
8822
  *
8683
8823
  * The account tree: the run root plus one
@@ -8702,15 +8842,27 @@ const ZERO_USAGE = {
8702
8842
  cacheWriteTokens: 0
8703
8843
  };
8704
8844
  /**
8705
- * The admission reserve for a spawn: opts.estCost, else profile.estCost, else
8706
- * price(countTokens(input) + caps.maxOutputTokens), else the engine flat
8707
- * default.
8845
+ * The admission reserve for a spawn: opts.estCost, else profile.estCost,
8846
+ * else price(countTokens(input) + one turn's worth of output), else the
8847
+ * engine flat default. The output term is caps.maxOutputTokens clamped to
8848
+ * limits.maxOutputTokensPerTurn when the spawn carries one, so a host can
8849
+ * bound reserves without hand-written estimates. The priced path uses the
8850
+ * SAME price function as settlement (priceUsdOf), so long-context tiers
8851
+ * apply to estimates too.
8708
8852
  */
8709
8853
  function admissionReserveUsd(options) {
8710
8854
  if (options.estCost !== void 0) return options.estCost;
8711
8855
  if (options.profileEstCost !== void 0) return options.profileEstCost;
8712
8856
  const pricing = options.caps?.pricing;
8713
- if (options.inputTokens !== void 0 && pricing !== void 0 && options.caps !== void 0) return options.inputTokens / 1e6 * pricing.inputUsdPerMTok + options.caps.maxOutputTokens / 1e6 * pricing.outputUsdPerMTok;
8857
+ if (options.inputTokens !== void 0 && pricing !== void 0 && options.caps !== void 0) {
8858
+ const outputTokens = options.maxOutputTokensPerTurn === void 0 ? options.caps.maxOutputTokens : Math.min(options.caps.maxOutputTokens, options.maxOutputTokensPerTurn);
8859
+ return priceUsdOf(pricing, {
8860
+ inputTokens: options.inputTokens,
8861
+ outputTokens,
8862
+ cacheReadTokens: 0,
8863
+ cacheWriteTokens: 0
8864
+ });
8865
+ }
8714
8866
  return options.flatReserveUsd ?? .5;
8715
8867
  }
8716
8868
  /**
@@ -8725,6 +8877,7 @@ var RunBudget = class {
8725
8877
  lifetimeSpawnCap;
8726
8878
  events;
8727
8879
  priceUsd;
8880
+ pricingOf;
8728
8881
  accounts = /* @__PURE__ */ new Map();
8729
8882
  usageInternal = { ...ZERO_USAGE };
8730
8883
  agentsSpawnedInternal = 0;
@@ -8736,6 +8889,7 @@ var RunBudget = class {
8736
8889
  this.lifetimeSpawnCap = options.lifetimeSpawnCap ?? 500;
8737
8890
  if (options.events !== void 0) this.events = options.events;
8738
8891
  if (options.priceUsd !== void 0) this.priceUsd = options.priceUsd;
8892
+ if (options.pricingOf !== void 0) this.pricingOf = options.pricingOf;
8739
8893
  const root = {
8740
8894
  scope: "run",
8741
8895
  spentUsd: 0,
@@ -8841,9 +8995,15 @@ var RunBudget = class {
8841
8995
  return Math.max(0, this.lifetimeSpawnCap - this.agentsSpawnedInternal);
8842
8996
  }
8843
8997
  /**
8844
- * Layer 1: admission before spawn. Blocks when spent + committedReserve
8845
- * has reached the ceiling on ANY account in the ancestor chain of
8846
- * `accountScope`, otherwise commits the reserve along the whole chain.
8998
+ * Layer 1: PROJECTED admission before spawn. A spawn is admitted only
8999
+ * when every account in the ancestor chain of `accountScope` still has
9000
+ * admission headroom AND fits the PROPOSED reserve on top of spent +
9001
+ * committedReserve + finalizeReserve (the finalize reserve is
9002
+ * untouchable by admission, DEF-7). An exact fill is allowed; one
9003
+ * dollar past the ceiling is not: a spawn is never admitted on the
9004
+ * argument that the money it needs is merely not committed yet. The
9005
+ * whole chain is checked before anything commits, so a rejection
9006
+ * mutates no account, increments no counter, and journals nothing.
8847
9007
  * Also enforces the engine lifetime spawn cap.
8848
9008
  */
8849
9009
  admitSpawn(reserveUsd, accountScope = "run") {
@@ -8852,14 +9012,19 @@ var RunBudget = class {
8852
9012
  throw new BudgetExhaustedError(`engine lifetime spawn cap reached (${this.lifetimeSpawnCap} spawns per run; budgetDefaults.lifetimeSpawnCap)`, { data: { cap: this.lifetimeSpawnCap } });
8853
9013
  }
8854
9014
  const chain = this.chainOf(accountScope);
8855
- for (const account of chain) if (account.ceilingUsd !== void 0 && account.spentUsd + account.committedReserveUsd + account.finalizeReserveUsd >= account.ceilingUsd) {
8856
- if (account.scope === "run") this.exhaustedInternal = true;
8857
- throw new BudgetExhaustedError(`budget ceiling reached on account '${account.scope}': spent ${account.spentUsd.toFixed(4)} USD plus committed reserve ${account.committedReserveUsd.toFixed(4)} USD is at the ceiling ${account.ceilingUsd.toFixed(4)} USD`, { data: {
8858
- account: account.scope,
8859
- spentUsd: account.spentUsd,
8860
- committedReserveUsd: account.committedReserveUsd,
8861
- ceilingUsd: account.ceilingUsd
8862
- } });
9015
+ for (const account of chain) {
9016
+ if (account.ceilingUsd === void 0) continue;
9017
+ const committed = account.spentUsd + account.committedReserveUsd + account.finalizeReserveUsd;
9018
+ if (committed >= account.ceilingUsd || committed + reserveUsd > account.ceilingUsd) {
9019
+ if (account.scope === "run") this.exhaustedInternal = true;
9020
+ throw new BudgetExhaustedError(`budget ceiling reached on account '${account.scope}': spent ${account.spentUsd.toFixed(4)} USD plus committed reserves ${(account.committedReserveUsd + account.finalizeReserveUsd).toFixed(4)} USD plus the proposed reserve ${reserveUsd.toFixed(4)} USD does not fit the ceiling ${account.ceilingUsd.toFixed(4)} USD`, { data: {
9021
+ account: account.scope,
9022
+ spentUsd: account.spentUsd,
9023
+ committedReserveUsd: account.committedReserveUsd,
9024
+ proposedReserveUsd: reserveUsd,
9025
+ ceilingUsd: account.ceilingUsd
9026
+ } });
9027
+ }
8863
9028
  }
8864
9029
  this.agentsSpawnedInternal += 1;
8865
9030
  for (const account of chain) account.committedReserveUsd += reserveUsd;
@@ -8921,6 +9086,28 @@ var RunBudget = class {
8921
9086
  }
8922
9087
  }
8923
9088
  /**
9089
+ * Layer 2b, the pre-dispatch output bound: the output tokens the
9090
+ * remaining chain budget (min over capped ancestors of ceiling minus
9091
+ * spend) still affords from `servedBy` for an estimated prompt, priced
9092
+ * by the same function as settlement, long-context tiers included.
9093
+ * Undefined when no account in the chain carries a USD ceiling, when
9094
+ * the model has no price row (the once-per-model unpriced warning in
9095
+ * onUsage covers that hole), or when output is free. Zero or negative
9096
+ * means the turn cannot be dispatched within the budget.
9097
+ */
9098
+ maxAffordableOutputTokens(servedBy, estimatedInputTokens, accountScope = "run") {
9099
+ const pricing = this.pricingOf?.(servedBy);
9100
+ if (pricing === void 0) return;
9101
+ let remainingUsd;
9102
+ for (const account of this.chainOf(accountScope)) {
9103
+ if (account.ceilingUsd === void 0) continue;
9104
+ const headroom = account.ceilingUsd - account.spentUsd;
9105
+ remainingUsd = remainingUsd === void 0 ? headroom : Math.min(remainingUsd, headroom);
9106
+ }
9107
+ if (remainingUsd === void 0) return;
9108
+ return affordableOutputTokens(pricing, Math.max(0, remainingUsd), estimatedInputTokens);
9109
+ }
9110
+ /**
8924
9111
  * Live accounting; spend propagates from `accountScope` to every
8925
9112
  * ancestor. Crossing a ceiling severs the crossing account's subtree
8926
9113
  * via its layer-3 AbortSignal (overshoot bounded by one turn per
@@ -9193,13 +9380,16 @@ var AdmissionController = class {
9193
9380
  },
9194
9381
  statsBefore
9195
9382
  };
9196
- const reserveUsd = spec.estCostUsd ?? this.flatReserveUsd;
9197
- const reserve = { reserveUsd };
9383
+ let childCeilingUsd;
9198
9384
  const parentRemainder = this.budget.remainderOf(spec.parentAccountScope);
9199
9385
  if (parentRemainder !== void 0) {
9200
9386
  const fractionCap = this.childBudgetFraction * parentRemainder;
9201
- reserve.childCeilingUsd = spec.budgetUsd === void 0 ? fractionCap : Math.min(spec.budgetUsd, fractionCap);
9202
- } else if (spec.budgetUsd !== void 0) reserve.childCeilingUsd = spec.budgetUsd;
9387
+ childCeilingUsd = spec.budgetUsd === void 0 ? fractionCap : Math.min(spec.budgetUsd, fractionCap);
9388
+ } else if (spec.budgetUsd !== void 0) childCeilingUsd = spec.budgetUsd;
9389
+ let reserveUsd = spec.estCostUsd ?? this.flatReserveUsd;
9390
+ if (childCeilingUsd !== void 0) reserveUsd = Math.min(reserveUsd, childCeilingUsd);
9391
+ const reserve = { reserveUsd };
9392
+ if (childCeilingUsd !== void 0) reserve.childCeilingUsd = childCeilingUsd;
9203
9393
  if (this.budget.spawnHeadroom <= 0) return {
9204
9394
  verdict: {
9205
9395
  kind: "reject",
@@ -10172,6 +10362,7 @@ function createCtx(internals, rootWorkflow) {
10172
10362
  }
10173
10363
  const adapter = adapterOf(loopResolved);
10174
10364
  const caps = adapter.caps(loopResolved.model);
10365
+ const limits = mergeUsageLimits(opts.limits, profile?.limits, internals.defaults.limits);
10175
10366
  let inputTokens;
10176
10367
  if (opts.estCost === void 0 && profile?.estCost === void 0 && adapter.countTokens) try {
10177
10368
  inputTokens = await adapter.countTokens({
@@ -10191,8 +10382,9 @@ function createCtx(internals, rootWorkflow) {
10191
10382
  if (opts.estCost !== void 0) reserveOptions.estCost = opts.estCost;
10192
10383
  if (profile?.estCost !== void 0) reserveOptions.profileEstCost = profile.estCost;
10193
10384
  if (inputTokens !== void 0) reserveOptions.inputTokens = inputTokens;
10385
+ if (limits.maxOutputTokensPerTurn !== void 0) reserveOptions.maxOutputTokensPerTurn = limits.maxOutputTokensPerTurn;
10194
10386
  if (internals.flatReserveUsd !== void 0) reserveOptions.flatReserveUsd = internals.flatReserveUsd;
10195
- const reserve = admissionReserveUsd(reserveOptions);
10387
+ const reserve = internals.pricingOf !== void 0 && internals.pricingOf(loopResolved.ref) === void 0 && opts.estCost === void 0 && profile?.estCost === void 0 ? 0 : admissionReserveUsd(reserveOptions);
10196
10388
  const budgetAccount = state.budgetScope ?? "run";
10197
10389
  internals.budget.admitSpawn(reserve, budgetAccount);
10198
10390
  let acquired;
@@ -10222,7 +10414,6 @@ function createCtx(internals, rootWorkflow) {
10222
10414
  running = await internals.replayer.appendRunning(runningInput);
10223
10415
  }
10224
10416
  opts[kOnRunning]?.(running.seq);
10225
- const limits = mergeUsageLimits(opts.limits, profile?.limits, internals.defaults.limits);
10226
10417
  const agentSink = { emit: (body) => internals.events.emit(body, spanId) };
10227
10418
  const ckptRef = checkpointRefFor(internals.runId, running.seq);
10228
10419
  let checkpointWritten = false;
@@ -10338,6 +10529,7 @@ function createCtx(internals, rootWorkflow) {
10338
10529
  },
10339
10530
  budget: {
10340
10531
  beforeTurn: () => internals.budget.beforeTurn(budgetAccount),
10532
+ maxAffordableOutputTokens: (servedBy, estimatedInputTokens) => internals.budget.maxAffordableOutputTokens(servedBy, estimatedInputTokens, budgetAccount),
10341
10533
  onUsage: (usage, servedBy) => internals.budget.onUsage(usage, servedBy, budgetAccount),
10342
10534
  signal: budgetAccount === "run" ? internals.budget.signal : AbortSignal.any([internals.budget.signal, internals.budget.signalOf(budgetAccount)].filter((signal) => signal !== void 0))
10343
10535
  },
@@ -11702,7 +11894,7 @@ function makeOrchestratorWorkflow(goal, opts) {
11702
11894
  role: "orchestrate",
11703
11895
  result: "full",
11704
11896
  tools: [...buildOrchestratorTools(orchestratorRuntime, fullCardText), ...extension?.tools(io) ?? []],
11705
- ...capState === void 0 ? {} : { estCost: capState.effectiveCapUsd },
11897
+ ...capState === void 0 ? {} : { estCost: capState.effectiveCapUsd - (orchestratorAccount === void 0 ? 0 : internals.budget.accountView(orchestratorAccount)?.finalizeReserveUsd ?? 0) },
11706
11898
  ...opts?.model === void 0 ? {} : { model: opts.model },
11707
11899
  ...opts?.limits === void 0 ? {} : { limits: opts.limits },
11708
11900
  [kOnRunning]: (seq) => {
@@ -11904,13 +12096,75 @@ var EventBus = class {
11904
12096
  //#endregion
11905
12097
  //#region src/runner/inprocess.ts
11906
12098
  /**
12099
+ * ScriptRunner SPI and InProcessRunner (M1-T11).
12100
+ *
12101
+ * Script runner contract: https://docs.rulvar.com/guide/planner
12102
+ * Workflow (a closure value) runs in process only; CompiledWorkflow is the
12103
+ * only form admissible to the worker sandbox and first exists at M6
12104
+ * (compileScript in @rulvar/planner), so until then the engine accepts
12105
+ * only in-process Workflow values. The SPI's L0 listing refers
12106
+ * to its frozen-seam status; the declaration lives here with its types.
12107
+ */
12108
+ const detection = new AsyncLocalStorage();
12109
+ let globalsPatched = false;
12110
+ /**
12111
+ * Stack line 0 names the Error, line 1 this helper, line 2 the patched
12112
+ * global, line 3 the caller whose provenance decides. Library code (a
12113
+ * provider SDK, any installed dependency, rulvar's own published dist)
12114
+ * lives under node_modules and is exempt: the guard exists for workflow
12115
+ * code, which imports from node_modules but does not live there.
12116
+ */
12117
+ function libraryCaller() {
12118
+ const caller = (/* @__PURE__ */ new Error()).stack?.split("\n")[3];
12119
+ return caller !== void 0 && caller.includes("node_modules");
12120
+ }
12121
+ /**
12122
+ * Patches Date.now and Math.random ONCE per process and never restores:
12123
+ * outside a workflow's async context the store is absent and the patch is
12124
+ * a transparent passthrough. The previous per-execute patch/restore pair
12125
+ * could race under concurrent runs (one run's restore removed another's
12126
+ * patch, and the second restore re-installed a stale patched function
12127
+ * PERMANENTLY, which could then warn on host code outside any run: the
12128
+ * false RULVAR_BARE_DATE_NOW class the 1.5.2 review reproduced).
12129
+ */
12130
+ function patchGlobalsOnce() {
12131
+ if (globalsPatched) return;
12132
+ globalsPatched = true;
12133
+ const priorNow = Date.now;
12134
+ const priorRandom = Math.random;
12135
+ Date.now = function rulvarPatchedDateNow() {
12136
+ const state = detection.getStore();
12137
+ if (state !== void 0 && !state.warnedNow && !libraryCaller()) {
12138
+ state.warnedNow = true;
12139
+ process.emitWarning("bare Date.now() called inside a rulvar run; use ctx.now() so the value is journaled and stable on replay", {
12140
+ code: "RULVAR_BARE_DATE_NOW",
12141
+ type: "RulvarWarning"
12142
+ });
12143
+ }
12144
+ return priorNow();
12145
+ };
12146
+ Math.random = function rulvarPatchedMathRandom() {
12147
+ const state = detection.getStore();
12148
+ if (state !== void 0 && !state.warnedRandom && !libraryCaller()) {
12149
+ state.warnedRandom = true;
12150
+ process.emitWarning("bare Math.random() called inside a rulvar run; use ctx.random() so the value is journaled and stable on replay", {
12151
+ code: "RULVAR_BARE_MATH_RANDOM",
12152
+ type: "RulvarWarning"
12153
+ });
12154
+ }
12155
+ return priorRandom();
12156
+ };
12157
+ }
12158
+ /**
11907
12159
  * The mode (a) runner for human-authored closures. Determinism is enforced
11908
12160
  * by convention, lint, and the ctx shims, NOT by a VM: only the sequence
11909
- * of keys must be stable. Dev mode (NODE_ENV !== 'production') patches
11910
- * Date.now and Math.random for the duration of execute to emit one warning
11911
- * per run pointing at ctx.now()/ctx.random(); the patch preserves behavior
11912
- * and restores the prior functions on exit (nesting-safe by capturing the
11913
- * prior value; concurrent runs may lose the warning, never correctness).
12161
+ * of keys must be stable. Dev mode (NODE_ENV !== 'production') detects
12162
+ * bare Date.now and Math.random and emits one warning per run pointing at
12163
+ * ctx.now()/ctx.random(). Detection is attributed by AsyncLocalStorage:
12164
+ * only code inside the workflow body's async context can trigger it, so
12165
+ * host code running concurrently, engine internals outside the body, and
12166
+ * other runs never produce a false warning, and nothing is ever restored,
12167
+ * so concurrent executes cannot race the patch state.
11914
12168
  */
11915
12169
  var InProcessRunner = class {
11916
12170
  onEscalation;
@@ -11923,47 +12177,14 @@ var InProcessRunner = class {
11923
12177
  }
11924
12178
  async execute(wf, ctx, args) {
11925
12179
  if (wf.kind !== "workflow") throw new TypeError("InProcessRunner executes closure Workflow values only; CompiledWorkflow runs in the worker sandbox (@rulvar/planner, M6)");
11926
- const devMode = process.env.NODE_ENV !== "production";
11927
- let restore;
11928
- if (devMode) {
11929
- const priorNow = Date.now;
11930
- const priorRandom = Math.random;
11931
- let warnedNow = false;
11932
- let warnedRandom = false;
11933
- const libraryCaller = () => {
11934
- const caller = (/* @__PURE__ */ new Error()).stack?.split("\n")[3];
11935
- return caller !== void 0 && caller.includes("node_modules");
11936
- };
11937
- Date.now = function rulvarPatchedDateNow() {
11938
- if (!warnedNow && !libraryCaller()) {
11939
- warnedNow = true;
11940
- process.emitWarning("bare Date.now() called inside a rulvar run; use ctx.now() so the value is journaled and stable on replay", {
11941
- code: "RULVAR_BARE_DATE_NOW",
11942
- type: "RulvarWarning"
11943
- });
11944
- }
11945
- return priorNow();
11946
- };
11947
- Math.random = function rulvarPatchedMathRandom() {
11948
- if (!warnedRandom && !libraryCaller()) {
11949
- warnedRandom = true;
11950
- process.emitWarning("bare Math.random() called inside a rulvar run; use ctx.random() so the value is journaled and stable on replay", {
11951
- code: "RULVAR_BARE_MATH_RANDOM",
11952
- type: "RulvarWarning"
11953
- });
11954
- }
11955
- return priorRandom();
11956
- };
11957
- restore = () => {
11958
- Date.now = priorNow;
11959
- Math.random = priorRandom;
11960
- };
11961
- }
11962
- try {
11963
- return await wf.body(ctx, args);
11964
- } finally {
11965
- restore?.();
12180
+ if (process.env.NODE_ENV !== "production") {
12181
+ patchGlobalsOnce();
12182
+ return detection.run({
12183
+ warnedNow: false,
12184
+ warnedRandom: false
12185
+ }, () => wf.body(ctx, args));
11966
12186
  }
12187
+ return await wf.body(ctx, args);
11967
12188
  }
11968
12189
  };
11969
12190
  //#endregion
@@ -12000,10 +12221,13 @@ function createEngine(options) {
12000
12221
  const runner = new InProcessRunner(options.onEscalation === void 0 ? void 0 : { onEscalation: options.onEscalation });
12001
12222
  const mintRunId = createCanonicalIdMinter();
12002
12223
  const realNow = Date.now.bind(globalThis);
12224
+ const pricingOf = (servedBy) => {
12225
+ const { adapterId, model } = parseModelRef(servedBy);
12226
+ return resolvePricing(servedBy, options.pricing, adapters.get(adapterId)?.caps(model).pricing);
12227
+ };
12003
12228
  const priceUsd = (servedBy, usage) => {
12004
12229
  if (servedBy === void 0) return;
12005
- const { adapterId, model } = parseModelRef(servedBy);
12006
- const pricing = resolvePricing(servedBy, options.pricing, adapters.get(adapterId)?.caps(model).pricing);
12230
+ const pricing = pricingOf(servedBy);
12007
12231
  if (pricing === void 0) return;
12008
12232
  return priceUsdOf(pricing, usage);
12009
12233
  };
@@ -12029,6 +12253,7 @@ function createEngine(options) {
12029
12253
  lifetimeSpawnCap: options.budgetDefaults?.lifetimeSpawnCap ?? 500,
12030
12254
  events: { emit: (body) => bus.emit(body, rootSpanId) },
12031
12255
  priceUsd,
12256
+ pricingOf,
12032
12257
  ...budgetSeed === void 0 ? {} : { seed: budgetSeed }
12033
12258
  });
12034
12259
  const invalidated = new Set(resumeCtx?.invalidate ?? []);
@@ -12132,6 +12357,7 @@ function createEngine(options) {
12132
12357
  }
12133
12358
  },
12134
12359
  priceUsd: (servedBy, usage) => priceUsd(servedBy, usage),
12360
+ pricingOf,
12135
12361
  runSignal: controller.signal,
12136
12362
  ...defaults.isolation === void 0 ? {} : { isolation: defaults.isolation },
12137
12363
  ...options.onEscalation === void 0 ? {} : { onEscalation: options.onEscalation },
@@ -12675,4 +12901,4 @@ function createSandboxBridge(ctx, options) {
12675
12901
  };
12676
12902
  }
12677
12903
  //#endregion
12678
- export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DedupIndex, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EventBus, ExternalRegistry, FINISH_SCHEMA, FINISH_TOOL_NAME, FileModelKnowledgeStore, FileTranscriptStore, GitWorktreeProvider, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, PlanInvariantError, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, agentErrorFromWire, agentErrorToWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateSchemaSpec, validateTerminationLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
12904
+ export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DedupIndex, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EventBus, ExternalRegistry, FINISH_SCHEMA, FINISH_TOOL_NAME, FileModelKnowledgeStore, FileTranscriptStore, GitWorktreeProvider, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, PlanInvariantError, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateSchemaSpec, validateTerminationLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.5.2",
4
- "description": "rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
3
+ "version": "1.6.0",
4
+ "description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "engines": {