@elevasis/sdk 1.54.0 → 1.56.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Elevasis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @elevasis/sdk
2
+
3
+ SDK for building Elevasis organization resources — agents, workflows, and the CLI that deploys them.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add @elevasis/sdk
9
+ ```
10
+
11
+ ## Published surface
12
+
13
+ | Subpath | Contains |
14
+ | ---------------------------- | ------------------------------------------------------------------------- |
15
+ | `@elevasis/sdk` | Resource definitions, the registry, and its validators |
16
+ | `@elevasis/sdk/worker` | The worker-thread authoring surface: `Agent`, `Workflow`, collectors |
17
+ | `@elevasis/sdk/node` | Node-only helpers |
18
+ | `@elevasis/sdk/test-utils` | Test helpers for resource authors |
19
+
20
+ The package also installs an `elevasis-sdk` binary. Run `elevasis-sdk --help` for the command set;
21
+ every authenticated command accepts `--prod` to target production rather than a local API.
22
+
23
+ ## Relationship to `@elevasis/core`
24
+
25
+ The SDK inlines the `@repo/core` types it re-exports, so no `@elevasis/core` specifier survives in
26
+ the shipped declarations. That makes the SDK a second sanctioned route onto capabilities core does
27
+ not publish directly — import them from here rather than reaching for a core subpath that does not
28
+ resolve.
29
+
30
+ ## License
31
+
32
+ MIT — see [LICENSE](./LICENSE).
@@ -1,4 +1,4 @@
1
- import { ProcessingStageStatusSchema, ListBuilderStageKeySchema, LLMResponseParseError, errorToString, getErrorDetails, ExecutionError2, validateEntryPoint, validateTerminalSteps, validateStepReferences, WorkflowTimeoutError, WorkflowStalledError, WorkflowCancellationError, logWorkflowStart, detectCycle, WorkflowStepError, logStepStart, validateTerminalOutput, logStepSuccess, determineNextStep, logStepFailure, logExecutionPath, logWorkflowSuccess, logWorkflowFailure, estimateTokens, allSettledWithConcurrency, truncationCharBudget, buildIterationResponseSchema } from './chunk-OT4CHFQJ.js';
1
+ import { ProcessingStageStatusSchema, ListBuilderStageKeySchema, LLMResponseParseError, errorToString, getErrorDetails, ExecutionError2, validateEntryPoint, validateTerminalSteps, validateStepReferences, WorkflowTimeoutError, WorkflowStalledError, WorkflowCancellationError, WorkflowStepTimeoutError, logWorkflowStart, detectCycle, WorkflowStepError, logStepStart, validateTerminalOutput, logStepSuccess, determineNextStep, logStepFailure, logExecutionPath, logWorkflowSuccess, logWorkflowFailure, estimateTokens, allSettledWithConcurrency, calculateCost, truncationCharBudget, buildIterationResponseSchema } from './chunk-R3J6BEPO.js';
2
2
  import { workerData, parentPort } from 'worker_threads';
3
3
  import { zodToJsonSchema } from '@alcyone-labs/zod-to-json-schema';
4
4
  import { z, ZodError } from 'zod';
@@ -65,6 +65,71 @@ var Workflow = class {
65
65
  }
66
66
  return new WorkflowCancellationError("Execution cancelled by user", { stepId, executionPath });
67
67
  }
68
+ /**
69
+ * Run one step's handler, bounded by the step's own `timeout` when it declares one.
70
+ *
71
+ * Two things have to happen for a step deadline to mean anything, and doing only one of them is
72
+ * why this is not a bare `Promise.race`:
73
+ *
74
+ * 1. The handler is handed a context whose `signal` is the step's, chained off the execution's, so
75
+ * a handler that already honours `context.signal` (every `fetch`-based one does) aborts its own
76
+ * work at the step deadline instead of running on unattended.
77
+ * 2. The race rejects regardless, because a handler that ignores its signal would otherwise hold
78
+ * the graph open until the 2-hour execution ceiling -- which is the whole thing being fixed.
79
+ *
80
+ * The chaining direction matches `startExecutionDeadline` in the API's `lifecycle.ts`: the parent's
81
+ * abort reason is propagated verbatim into the child controller, so an execution-level cancel or
82
+ * timeout still classifies as itself rather than as a step deadline.
83
+ *
84
+ * A step with no `timeout` takes the untouched path and is handed `context` itself -- no derived
85
+ * controller, no listener, no race.
86
+ */
87
+ async runStepHandler(step, input, context, executionPath) {
88
+ if (step.timeout === void 0) return step.handler(input, context);
89
+ const controller = new AbortController();
90
+ let deadlineFired = false;
91
+ const timer = setTimeout(() => {
92
+ deadlineFired = true;
93
+ controller.abort("timeout");
94
+ }, step.timeout);
95
+ let onParentAbort;
96
+ if (context.signal) {
97
+ if (context.signal.aborted) {
98
+ controller.abort(context.signal.reason);
99
+ } else {
100
+ onParentAbort = () => controller.abort(context.signal?.reason);
101
+ context.signal.addEventListener("abort", onParentAbort, { once: true });
102
+ }
103
+ }
104
+ const aborted = new Promise((_, reject) => {
105
+ controller.signal.addEventListener(
106
+ "abort",
107
+ () => {
108
+ if (deadlineFired) {
109
+ reject(
110
+ new WorkflowStepTimeoutError(`Step exceeded its timeout (${step.timeout}ms) [${step.id}:${step.name}]`, {
111
+ timeout: step.timeout,
112
+ stepId: step.id,
113
+ stepName: step.name,
114
+ executionPath
115
+ })
116
+ );
117
+ return;
118
+ }
119
+ reject(
120
+ this.abortErrorFor(context.signal, step.id, executionPath) ?? new WorkflowCancellationError("Execution cancelled by user", { stepId: step.id, executionPath })
121
+ );
122
+ },
123
+ { once: true }
124
+ );
125
+ });
126
+ try {
127
+ return await Promise.race([step.handler(input, { ...context, signal: controller.signal }), aborted]);
128
+ } finally {
129
+ clearTimeout(timer);
130
+ if (onParentAbort) context.signal?.removeEventListener("abort", onParentAbort);
131
+ }
132
+ }
68
133
  /**
69
134
  * Execute the workflow with graph-based flow control
70
135
  * Context is required for execution tracking, logging, and organization isolation
@@ -98,7 +163,7 @@ var Workflow = class {
98
163
  logStepStart(context, step.id, step.name, currentData, stepStartTime);
99
164
  try {
100
165
  const validatedInput = step.inputSchema.parse(currentData);
101
- const rawOutput = await step.handler(validatedInput, context);
166
+ const rawOutput = await this.runStepHandler(step, validatedInput, context, executionPath);
102
167
  currentData = step.outputSchema.parse(rawOutput);
103
168
  if (step.next === null && this.shouldGenerateOutput) {
104
169
  validateTerminalOutput(step.id, currentData, this.contract.outputSchema);
@@ -120,6 +185,9 @@ var Workflow = class {
120
185
  const stepEndTime = Date.now();
121
186
  const duration = stepEndTime - stepStartTime;
122
187
  logStepFailure(context, step.id, step.name, error, duration, stepStartTime, stepEndTime);
188
+ if (error instanceof WorkflowStepTimeoutError || error instanceof WorkflowTimeoutError || error instanceof WorkflowStalledError || error instanceof WorkflowCancellationError) {
189
+ throw error;
190
+ }
123
191
  const cause = error instanceof ExecutionError2 ? error : void 0;
124
192
  throw new WorkflowStepError(
125
193
  `Step failed [${step.id}:${step.name}]: ${errorToString(error)}`,
@@ -1255,6 +1323,12 @@ function normalizeSessionMessages(actions, sessionCapable) {
1255
1323
  }
1256
1324
  var NO_PROGRESS_STREAK_KEY = "agent.actions.noProgressStreak";
1257
1325
  var MAX_CONSECUTIVE_NO_PROGRESS_ITERATIONS = 2;
1326
+ var LAST_PLAN_SIGNATURE_KEY = "agent.actions.lastPlanSignature";
1327
+ var REPEATED_PLAN_STREAK_KEY = "agent.actions.repeatedPlanStreak";
1328
+ var DEFAULT_MAX_IDENTICAL_ITERATIONS = 3;
1329
+ function planSignature(actions) {
1330
+ return JSON.stringify(actions);
1331
+ }
1258
1332
  async function processActions(iterationContext, response) {
1259
1333
  const normalizedActions = normalizeSessionMessages(response.nextActions, !!iterationContext.config.sessionCapable);
1260
1334
  if (normalizedActions.length === 0) {
@@ -1278,6 +1352,31 @@ async function processActions(iterationContext, response) {
1278
1352
  }
1279
1353
  } else {
1280
1354
  iterationContext.executionContext.store.delete(NO_PROGRESS_STREAK_KEY);
1355
+ const store = iterationContext.executionContext.store;
1356
+ const signature = planSignature(normalizedActions);
1357
+ const repeated = store.get(LAST_PLAN_SIGNATURE_KEY) === signature;
1358
+ const streak = repeated ? (store.get(REPEATED_PLAN_STREAK_KEY) ?? 0) + 1 : 0;
1359
+ store.set(LAST_PLAN_SIGNATURE_KEY, signature);
1360
+ store.set(REPEATED_PLAN_STREAK_KEY, streak);
1361
+ if (repeated) {
1362
+ const ceiling = iterationContext.config.constraints?.maxIdenticalIterations ?? DEFAULT_MAX_IDENTICAL_ITERATIONS;
1363
+ iterationContext.memoryManager.addToHistory({
1364
+ type: "error",
1365
+ content: JSON.stringify({
1366
+ error: `This iteration's plan is identical to the previous one (repeated ${streak}x). Repeating it again will produce the same result. Either act on what the last result returned, or complete.`
1367
+ }),
1368
+ turnNumber: iterationContext.executionContext.sessionTurnNumber ?? null,
1369
+ iterationNumber: iterationContext.iteration,
1370
+ source: "framework"
1371
+ });
1372
+ if (streak >= ceiling) {
1373
+ throw new AgentNoProgressError(`Agent repeated an identical plan for ${streak} consecutive iterations`, {
1374
+ iteration: iterationContext.iteration,
1375
+ streak,
1376
+ ceiling
1377
+ });
1378
+ }
1379
+ }
1281
1380
  }
1282
1381
  const completeRequested = normalizedActions.some((action) => action.type === "complete");
1283
1382
  const toolCalls = [];
@@ -2139,6 +2238,14 @@ var Agent = class {
2139
2238
  while (iteration <= maxIterations) {
2140
2239
  const abortError = this.abortErrorFor(context.signal, iteration);
2141
2240
  if (abortError) throw abortError;
2241
+ const spendStop = this.spendCeilingReached(context);
2242
+ if (spendStop) {
2243
+ context.logger.warn(
2244
+ `Agent stopped at iteration ${iteration}: ${spendStop.limit} ceiling of ${spendStop.ceiling} reached (${spendStop.actual})`
2245
+ );
2246
+ this.stopReason = "spend_exhausted";
2247
+ return;
2248
+ }
2142
2249
  try {
2143
2250
  await context.onHeartbeat?.();
2144
2251
  } catch {
@@ -2162,6 +2269,33 @@ var Agent = class {
2162
2269
  }
2163
2270
  this.stopReason = "budget_exhausted";
2164
2271
  }
2272
+ /**
2273
+ * Whether a declared spend ceiling has been reached, and which one.
2274
+ *
2275
+ * Reads `ExecutionContext.aiUsageCollector` -- the same collector `ObservabilityService` persists
2276
+ * to `execution_metrics` -- so enforcement and the recorded figure can never disagree. The
2277
+ * collector was already being written on every LLM call and read only after the fact; this is the
2278
+ * first place its running total decides anything.
2279
+ *
2280
+ * Returns `null` when no ceiling is declared, or when no collector was injected. An execution
2281
+ * without a collector is not silently capped at zero -- it is uncapped, which is what it was
2282
+ * before any of this existed.
2283
+ *
2284
+ * @returns the breached ceiling, or `null` when none is.
2285
+ */
2286
+ spendCeilingReached(context) {
2287
+ const { maxCostUsd, maxTotalTokens } = this.config.constraints ?? {};
2288
+ if (maxCostUsd === void 0 && maxTotalTokens === void 0) return null;
2289
+ const summary = context.aiUsageCollector?.getSummary();
2290
+ if (!summary) return null;
2291
+ if (maxCostUsd !== void 0 && summary.totalCostUsd >= maxCostUsd) {
2292
+ return { limit: "maxCostUsd", ceiling: maxCostUsd, actual: summary.totalCostUsd };
2293
+ }
2294
+ if (maxTotalTokens !== void 0 && summary.totalTokens >= maxTotalTokens) {
2295
+ return { limit: "maxTotalTokens", ceiling: maxTotalTokens, actual: summary.totalTokens };
2296
+ }
2297
+ return null;
2298
+ }
2165
2299
  /**
2166
2300
  * Classify an aborted signal into the typed error the rest of the framework expects, regardless
2167
2301
  * of where the abort surfaced. An abort landing mid-`fetch` or mid-tool throws whatever the
@@ -2589,6 +2723,78 @@ Fix the errors and generate a valid output.
2589
2723
  }
2590
2724
  }
2591
2725
  };
2726
+
2727
+ // ../core/src/operations/observability/ai-usage-collector.ts
2728
+ var AIUsageCollector = class {
2729
+ model = "gpt-5";
2730
+ // Default, will be overwritten on first record()
2731
+ calls = [];
2732
+ callSequence = 0;
2733
+ /**
2734
+ * Record a single AI call with usage metrics
2735
+ *
2736
+ * @param usage - Token usage and latency data from LLM adapter
2737
+ * @param callType - Type discriminator (agent-reasoning, tool, etc.)
2738
+ * @param context - Optional typed context specific to callType
2739
+ */
2740
+ record(usage, callType = "other", context) {
2741
+ this.callSequence++;
2742
+ this.model = usage.model;
2743
+ const costUsd = usage.cost ?? calculateCost(
2744
+ usage.model,
2745
+ usage.inputTokens,
2746
+ usage.outputTokens,
2747
+ usage.cacheReadInputTokens,
2748
+ usage.cacheCreationInputTokens
2749
+ );
2750
+ const inputTokens = usage.inputTokens + (usage.cacheReadInputTokens ?? 0) + (usage.cacheCreationInputTokens ?? 0);
2751
+ this.calls.push({
2752
+ callSequence: this.callSequence,
2753
+ callType,
2754
+ model: usage.model,
2755
+ inputTokens,
2756
+ outputTokens: usage.outputTokens,
2757
+ costUsd,
2758
+ latencyMs: usage.latencyMs,
2759
+ context,
2760
+ ...usage.cacheReadInputTokens !== void 0 ? { cacheReadInputTokens: usage.cacheReadInputTokens } : {},
2761
+ ...usage.cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens: usage.cacheCreationInputTokens } : {},
2762
+ ...usage.inputWarnings?.length ? { inputWarnings: usage.inputWarnings } : {},
2763
+ ...usage.inputWarningsBySource?.length ? { inputWarningsBySource: usage.inputWarningsBySource } : {},
2764
+ ...usage.inputBlocked ? { inputBlocked: true } : {},
2765
+ ...usage.outputValidationError ? { outputValidationError: usage.outputValidationError } : {},
2766
+ ...usage.unvalidatedOutput ? { unvalidatedOutput: usage.unvalidatedOutput } : {},
2767
+ ...usage.strictStatus ? { strictStatus: usage.strictStatus } : {},
2768
+ ...usage.strictRefusalReasons?.length ? { strictRefusalReasons: usage.strictRefusalReasons } : {},
2769
+ ...usage.providerMs !== void 0 ? { providerMs: usage.providerMs } : {},
2770
+ ...usage.validateMs !== void 0 ? { validateMs: usage.validateMs } : {},
2771
+ ...usage.wallClockMs !== void 0 ? { wallClockMs: usage.wallClockMs } : {}
2772
+ });
2773
+ }
2774
+ /**
2775
+ * Get aggregated summary of all AI calls
2776
+ */
2777
+ getSummary() {
2778
+ const totalInputTokens = this.calls.reduce((sum, c) => sum + c.inputTokens, 0);
2779
+ const totalOutputTokens = this.calls.reduce((sum, c) => sum + c.outputTokens, 0);
2780
+ const totalCostUsd = this.calls.reduce((sum, c) => sum + c.costUsd, 0);
2781
+ return {
2782
+ model: this.model,
2783
+ totalInputTokens,
2784
+ totalOutputTokens,
2785
+ totalTokens: totalInputTokens + totalOutputTokens,
2786
+ totalCostUsd,
2787
+ callCount: this.calls.length,
2788
+ calls: this.calls
2789
+ };
2790
+ }
2791
+ /**
2792
+ * Check if any usage has been recorded
2793
+ */
2794
+ hasUsage() {
2795
+ return this.calls.length > 0;
2796
+ }
2797
+ };
2592
2798
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([
2593
2799
  "rate_limit_exceeded",
2594
2800
  "network_error",
@@ -2756,11 +2962,14 @@ var platform = {
2756
2962
 
2757
2963
  // src/worker/llm-adapter.ts
2758
2964
  var PostMessageLLMAdapter = class {
2759
- constructor(provider, model) {
2965
+ constructor(provider, model, aiUsageCollector, callType) {
2760
2966
  this.provider = provider;
2761
2967
  this.model = model;
2968
+ this.aiUsageCollector = aiUsageCollector;
2969
+ this.callType = callType;
2762
2970
  }
2763
2971
  async generate(request) {
2972
+ const startedAt = Date.now();
2764
2973
  const { result, usage } = await platform.callWithUsage({
2765
2974
  tool: "llm",
2766
2975
  method: "generate",
@@ -2777,6 +2986,22 @@ var PostMessageLLMAdapter = class {
2777
2986
  maxOutputTokens: request.maxOutputTokens
2778
2987
  }
2779
2988
  });
2989
+ if (usage && this.aiUsageCollector) {
2990
+ this.aiUsageCollector.record(
2991
+ {
2992
+ model: usage.model ?? this.model,
2993
+ inputTokens: usage.inputTokens,
2994
+ outputTokens: usage.outputTokens,
2995
+ latencyMs: Date.now() - startedAt,
2996
+ ...usage.cost !== void 0 && { cost: usage.cost },
2997
+ ...usage.cacheReadInputTokens !== void 0 && { cacheReadInputTokens: usage.cacheReadInputTokens },
2998
+ ...usage.cacheCreationInputTokens !== void 0 && {
2999
+ cacheCreationInputTokens: usage.cacheCreationInputTokens
3000
+ }
3001
+ },
3002
+ this.callType
3003
+ );
3004
+ }
2780
3005
  return {
2781
3006
  output: result,
2782
3007
  ...usage && {
@@ -2791,10 +3016,11 @@ var PostMessageLLMAdapter = class {
2791
3016
  }
2792
3017
  };
2793
3018
  function createPostMessageAdapterFactory() {
2794
- return (config) => new PostMessageLLMAdapter(config.provider, config.model);
3019
+ return (config, aiUsageCollector, callType) => new PostMessageLLMAdapter(config.provider, config.model, aiUsageCollector, callType);
2795
3020
  }
3021
+ var FULL_TOKEN_HEX_LENGTH = 32;
2796
3022
  function generateHmacToken(secret, data) {
2797
- return createHmac("sha256", secret).update(data.toLowerCase().trim()).digest("hex").slice(0, 16);
3023
+ return createHmac("sha256", secret).update(data.toLowerCase().trim()).digest("hex").slice(0, FULL_TOKEN_HEX_LENGTH);
2798
3024
  }
2799
3025
  function classifyPlatformToolError(err, options = {}) {
2800
3026
  const {
@@ -4244,6 +4470,12 @@ function buildWorkerExecutionContext(params) {
4244
4470
  organizationId: params.organizationId,
4245
4471
  organizationName: params.organizationName,
4246
4472
  resourceId: params.resourceId,
4473
+ // Worker-side usage accounting. The parent keeps its own collector and remains the source of the
4474
+ // persisted `execution_metrics` row; this one exists so the agent loop — which runs in this
4475
+ // thread — can read what the turn has spent so far and stop at a declared ceiling. Without it
4476
+ // `Agent.iterate` read `undefined` on every deployed execution and treated every agent as
4477
+ // uncapped, which a local dogfood run on 2026-09-02 is what caught.
4478
+ aiUsageCollector: new AIUsageCollector(),
4247
4479
  sessionId: params.sessionId,
4248
4480
  sessionTurnNumber: params.sessionTurnNumber,
4249
4481
  conversationHistory: params.conversationHistory,
@@ -4450,19 +4682,28 @@ function startWorker(org) {
4450
4682
  const durationMs = Date.now() - startTime;
4451
4683
  const stopReason = agentInstance.getStopReason();
4452
4684
  const budgetExhausted = stopReason === "budget_exhausted";
4685
+ const spendExhausted = stopReason === "spend_exhausted";
4686
+ const stoppedShort = budgetExhausted || spendExhausted;
4453
4687
  if (budgetExhausted) {
4454
4688
  console.warn(`[SDK-WORKER] Agent '${resourceId}' exhausted its iteration budget (${durationMs}ms)`);
4689
+ } else if (spendExhausted) {
4690
+ console.warn(`[SDK-WORKER] Agent '${resourceId}' reached its cost/token ceiling (${durationMs}ms)`);
4455
4691
  } else {
4456
4692
  console.log(`[SDK-WORKER] Agent '${resourceId}' completed (${durationMs}ms)`);
4457
4693
  }
4458
4694
  parentPort.postMessage({
4459
4695
  type: "result",
4460
- status: budgetExhausted ? "failed" : "completed",
4696
+ status: stoppedShort ? "failed" : "completed",
4461
4697
  ...budgetExhausted ? {
4462
4698
  error: "Agent stopped without completing: iteration budget exhausted. Any output below was synthesized from partial work.",
4463
4699
  errorName: "AgentBudgetExhaustedError",
4464
4700
  errorCode: "agent_budget_exhausted"
4465
4701
  } : {},
4702
+ ...spendExhausted ? {
4703
+ error: "Agent stopped without completing: cost or token ceiling reached. Any output below was synthesized from partial work.",
4704
+ errorName: "AgentSpendExhaustedError",
4705
+ errorCode: "agent_spend_exhausted"
4706
+ } : {},
4466
4707
  output,
4467
4708
  stopReason,
4468
4709
  // Whether the agent emitted an assistant message this turn. `Agent` has tracked this for
@@ -814,14 +814,25 @@ var SystemEntrySchema = z.object({
814
814
  * position-derived paths. Both still exist on this schema for backward compat.
815
815
  */
816
816
  systems: z.lazy(() => z.record(z.string().trim().min(1).max(100), SystemEntrySchema)).optional(),
817
- /** @deprecated Use systems. Accepted as a compatibility alias during the ontology bridge. */
817
+ /**
818
+ * @deprecated Use systems. Accepted as a compatibility alias during the ontology bridge.
819
+ *
820
+ * Accepted on INPUT only. Parsing used to mirror `systems` into this key so that
821
+ * either spelling could be read off a parsed model, which meant every parsed System
822
+ * carried the same children twice. Readers that walked both keys then visited each
823
+ * nested System twice -- `getOrgOsRouteContractSystems` reported 11 checked paths
824
+ * against command-center's 7 real Systems and would have raised every nested-System
825
+ * failure as two failures -- and readers that walked this key alone looked correct
826
+ * while depending entirely on the mirror. Both defects were live. The mirror is gone:
827
+ * a parsed model now carries children under whichever key the author wrote, so read
828
+ * `system.systems ?? system.subsystems` (helpers.ts, validation.ts, ontology.ts,
829
+ * selectDeclaredSystems.ts, validateManifests.ts and SystemOpsView.tsx all do).
830
+ */
818
831
  subsystems: z.lazy(() => z.record(z.string().trim().min(1).max(100), SystemEntrySchema)).optional()
819
832
  }).strict().refine((system) => system.label !== void 0 || system.title !== void 0, {
820
833
  path: ["label"],
821
834
  message: "System must provide label or title"
822
- }).transform(
823
- (system) => system.systems !== void 0 && system.subsystems === void 0 ? { ...system, subsystems: system.systems } : system
824
- );
835
+ });
825
836
  z.record(z.string(), SystemEntrySchema).refine((record) => Object.entries(record).every(([key, entry]) => entry.id === key), {
826
837
  message: "Each system entry id must match its map key"
827
838
  }).default({});
@@ -1992,6 +2003,8 @@ var AnthropicConfigSchema = z.discriminatedUnion("model", [
1992
2003
  AnthropicClaude5ConfigSchema,
1993
2004
  AnthropicStandardConfigSchema
1994
2005
  ]);
2006
+ var ANTHROPIC_CACHE_READ_RATE_MULTIPLIER = 0.1;
2007
+ var OPENAI_CACHE_READ_RATE_MULTIPLIER = 0.5;
1995
2008
  var GPT56_CACHE_READ_RATE_MULTIPLIER = 0.1;
1996
2009
  var CACHE_CREATION_RATE_MULTIPLIER = 1.25;
1997
2010
  var MODEL_INFO = {
@@ -2188,6 +2201,30 @@ function getModelInfo(model) {
2188
2201
  }
2189
2202
  return void 0;
2190
2203
  }
2204
+ function cacheReadRateMultiplier(model, info) {
2205
+ if (info?.cacheReadRateMultiplier !== void 0) return info.cacheReadRateMultiplier;
2206
+ return model.startsWith("claude-") ? ANTHROPIC_CACHE_READ_RATE_MULTIPLIER : OPENAI_CACHE_READ_RATE_MULTIPLIER;
2207
+ }
2208
+ function cacheCreationRateMultiplier(info) {
2209
+ return info?.cacheCreationRateMultiplier ?? CACHE_CREATION_RATE_MULTIPLIER;
2210
+ }
2211
+ function calculateCost(model, inputTokens, outputTokens, cacheReadInputTokens = 0, cacheCreationInputTokens = 0) {
2212
+ const info = getModelInfo(model);
2213
+ if (!info) {
2214
+ console.warn(
2215
+ `[CostCalculator] Unknown model '${model}' - cannot calculate cost. Available models:`,
2216
+ Object.keys(MODEL_INFO)
2217
+ );
2218
+ return 0;
2219
+ }
2220
+ const baseInputRate = info.inputCostPer1M / 100;
2221
+ const inputCostUsd = inputTokens / 1e6 * baseInputRate;
2222
+ const cacheReadCostUsd = cacheReadInputTokens / 1e6 * baseInputRate * cacheReadRateMultiplier(model, info);
2223
+ const cacheCreationCostUsd = cacheCreationInputTokens / 1e6 * baseInputRate * cacheCreationRateMultiplier(info);
2224
+ const outputCostUsd = outputTokens / 1e6 * (info.outputCostPer1M / 100);
2225
+ const totalCostUsd = inputCostUsd + cacheReadCostUsd + cacheCreationCostUsd + outputCostUsd;
2226
+ return totalCostUsd;
2227
+ }
2191
2228
  function validateModelConfig(config) {
2192
2229
  const model = config.model;
2193
2230
  if (!model) {
@@ -2615,6 +2652,18 @@ var WorkflowTimeoutError = class extends ExecutionError2 {
2615
2652
  return false;
2616
2653
  }
2617
2654
  };
2655
+ var WorkflowStepTimeoutError = class extends ExecutionError2 {
2656
+ type = "workflow_step_timeout_error";
2657
+ severity = "critical";
2658
+ category = "workflow";
2659
+ constructor(message, context) {
2660
+ super(message, context);
2661
+ }
2662
+ /** The run still had budget; a slow dependency is exactly the case a retry exists for. */
2663
+ isRetryable() {
2664
+ return true;
2665
+ }
2666
+ };
2618
2667
  var WorkflowStalledError = class extends ExecutionError2 {
2619
2668
  type = "workflow_stalled_error";
2620
2669
  severity = "critical";
@@ -5962,4 +6011,4 @@ function defineWorkflowConfig(resourceId, descriptors, actionRegistry = []) {
5962
6011
  }
5963
6012
  var ListBuilderStageKeySchema = z.string().min(1);
5964
6013
 
5965
- export { ActivityEventSchema, BuildPlanSnapshotStepSchema, ContractRefResolutionError, CrmStageKeySchema, CrmStateKeySchema, EmailSchema, ExecutionError, ExecutionError2, LLMResponseParseError, ListBuilderStageKeySchema, ProcessingStageStatusSchema, ProspectingBuildTemplateSchema, RegistryValidationError, ResourceRegistry, StepType, ToolingError, WorkflowCancellationError, WorkflowStalledError, WorkflowStepError, WorkflowTimeoutError, allSettledWithConcurrency, bindResourceDescriptor, buildIterationResponseSchema, compileBusinessOntologyValidationIndex, concurrentPool, createLeadGenStageValidators, defineContract, defineResource, defineResourceOntology, defineResources, defineSingleStepWorkflow, defineTopology, defineTopologyRelationship, defineWorkflowConfig, deriveActions, detectCycle, determineNextStep, diagnosticOutput, errorToString, estimateTokens, getErrorDetails, integrationInput, isBuiltInReadinessProfile, isZodType, logExecutionPath, logStepFailure, logStepStart, logStepSuccess, logWorkflowFailure, logWorkflowStart, logWorkflowSuccess, lookupReadinessProfile, parseTopologyNodeRef, registerReadinessProfile, resolveContractRef, runDiagnostic, splitName, topologyRef, topologyRelationship, truncationCharBudget, validateDeclaredSystemInterfaceReadiness, validateDeploymentSpec, validateEntryPoint, validateRelationships, validateResourceGovernance, validateStepReferences, validateTerminalOutput, validateTerminalSteps };
6014
+ export { ActivityEventSchema, BuildPlanSnapshotStepSchema, ContractRefResolutionError, CrmStageKeySchema, CrmStateKeySchema, EmailSchema, ExecutionError, ExecutionError2, LLMResponseParseError, ListBuilderStageKeySchema, ProcessingStageStatusSchema, ProspectingBuildTemplateSchema, RegistryValidationError, ResourceRegistry, StepType, ToolingError, WorkflowCancellationError, WorkflowStalledError, WorkflowStepError, WorkflowStepTimeoutError, WorkflowTimeoutError, allSettledWithConcurrency, bindResourceDescriptor, buildIterationResponseSchema, calculateCost, compileBusinessOntologyValidationIndex, concurrentPool, createLeadGenStageValidators, defineContract, defineResource, defineResourceOntology, defineResources, defineSingleStepWorkflow, defineTopology, defineTopologyRelationship, defineWorkflowConfig, deriveActions, detectCycle, determineNextStep, diagnosticOutput, errorToString, estimateTokens, getErrorDetails, integrationInput, isBuiltInReadinessProfile, isZodType, logExecutionPath, logStepFailure, logStepStart, logStepSuccess, logWorkflowFailure, logWorkflowStart, logWorkflowSuccess, lookupReadinessProfile, parseTopologyNodeRef, registerReadinessProfile, resolveContractRef, runDiagnostic, splitName, topologyRef, topologyRelationship, truncationCharBudget, validateDeclaredSystemInterfaceReadiness, validateDeploymentSpec, validateEntryPoint, validateRelationships, validateResourceGovernance, validateStepReferences, validateTerminalOutput, validateTerminalSteps };