@relayflows/sdk 2.0.12 → 2.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/authored-budget.d.ts.map +1 -1
  2. package/dist/authored-budget.js +16 -7
  3. package/dist/authored-budget.js.map +1 -1
  4. package/dist/authored-flow-executor.d.ts.map +1 -1
  5. package/dist/authored-flow-executor.js +5 -0
  6. package/dist/authored-flow-executor.js.map +1 -1
  7. package/dist/budget-preflight.d.ts +17 -6
  8. package/dist/budget-preflight.d.ts.map +1 -1
  9. package/dist/budget-preflight.js +9 -6
  10. package/dist/budget-preflight.js.map +1 -1
  11. package/dist/cli/build.d.ts.map +1 -1
  12. package/dist/cli/build.js +8 -0
  13. package/dist/cli/build.js.map +1 -1
  14. package/dist/cli/deploy.d.ts.map +1 -1
  15. package/dist/cli/deploy.js +5 -0
  16. package/dist/cli/deploy.js.map +1 -1
  17. package/dist/cli/direct-run.d.ts.map +1 -1
  18. package/dist/cli/direct-run.js +28 -0
  19. package/dist/cli/direct-run.js.map +1 -1
  20. package/dist/cli/run.d.ts +8 -0
  21. package/dist/cli/run.d.ts.map +1 -1
  22. package/dist/cli/run.js +34 -11
  23. package/dist/cli/run.js.map +1 -1
  24. package/dist/cli/step-failure.d.ts +40 -0
  25. package/dist/cli/step-failure.d.ts.map +1 -0
  26. package/dist/cli/step-failure.js +150 -0
  27. package/dist/cli/step-failure.js.map +1 -0
  28. package/dist/cli.d.ts.map +1 -1
  29. package/dist/cli.js +4 -4
  30. package/dist/cli.js.map +1 -1
  31. package/dist/daemon-connection.d.ts +7 -0
  32. package/dist/daemon-connection.d.ts.map +1 -1
  33. package/dist/daemon-connection.js +7 -0
  34. package/dist/daemon-connection.js.map +1 -1
  35. package/dist/failure-kinds.d.ts +25 -3
  36. package/dist/failure-kinds.d.ts.map +1 -1
  37. package/dist/failure-kinds.js +5 -2
  38. package/dist/failure-kinds.js.map +1 -1
  39. package/dist/journal-client.d.ts +2 -6
  40. package/dist/journal-client.d.ts.map +1 -1
  41. package/dist/journal-client.js.map +1 -1
  42. package/dist/model-pricing.d.ts +7 -5
  43. package/dist/model-pricing.d.ts.map +1 -1
  44. package/dist/model-pricing.js +7 -5
  45. package/dist/model-pricing.js.map +1 -1
  46. package/dist/preflight.d.ts +5 -0
  47. package/dist/preflight.d.ts.map +1 -1
  48. package/dist/preflight.js.map +1 -1
  49. package/dist/protocol.d.ts +22 -5
  50. package/dist/protocol.d.ts.map +1 -1
  51. package/dist/spec.d.ts +22 -7
  52. package/dist/spec.d.ts.map +1 -1
  53. package/dist/worker-spend.d.ts +13 -9
  54. package/dist/worker-spend.d.ts.map +1 -1
  55. package/dist/worker-spend.js +22 -8
  56. package/dist/worker-spend.js.map +1 -1
  57. package/package.json +2 -2
  58. package/src/authored-budget.ts +35 -9
  59. package/src/authored-flow-executor.ts +5 -0
  60. package/src/budget-preflight.ts +17 -6
  61. package/src/cli/build.ts +7 -0
  62. package/src/cli/deploy.ts +4 -0
  63. package/src/cli/direct-run.ts +28 -0
  64. package/src/cli/run.ts +43 -11
  65. package/src/cli/step-failure.ts +160 -0
  66. package/src/cli.ts +4 -4
  67. package/src/daemon-connection.ts +8 -0
  68. package/src/failure-kinds.ts +25 -3
  69. package/src/journal-client.ts +2 -1
  70. package/src/model-pricing.ts +7 -5
  71. package/src/preflight.ts +5 -0
  72. package/src/protocol.ts +15 -2
  73. package/src/spec.ts +23 -1
  74. package/src/worker-spend.ts +25 -9
  75. package/dist/cli/deterministic-failure.d.ts +0 -5
  76. package/dist/cli/deterministic-failure.d.ts.map +0 -1
  77. package/dist/cli/deterministic-failure.js +0 -66
  78. package/dist/cli/deterministic-failure.js.map +0 -1
  79. package/src/cli/deterministic-failure.ts +0 -69
package/src/spec.ts CHANGED
@@ -452,9 +452,31 @@ export interface KernelAgentStep extends KernelStepCommon {
452
452
 
453
453
  export type KernelStepSpec = KernelDeterministicStep | KernelLlmStep | KernelAgentStep;
454
454
 
455
+ /**
456
+ * Accounting carried into a run that continues an earlier one. The authored
457
+ * executor lowers each step to its own kernel run and sends the previous run's
458
+ * totals here. It mirrors the kernel's `PriorSpend` field for field, so the
459
+ * handoff cannot quietly narrow what a carried budget can express.
460
+ */
461
+ export interface KernelPriorSpend {
462
+ tokens_in: number;
463
+ tokens_out: number;
464
+ /** Metered dollars only; a lower bound when `dollars_unmetered` is set. */
465
+ dollars: string;
466
+ wallclock_ms: number;
467
+ day?: number;
468
+ /**
469
+ * At least one carried charge spent tokens of unknown dollar cost, so
470
+ * `dollars` is a lower bound rather than a measured total. Omitted when
471
+ * false, so a fully metered flow's payload keeps its exact bytes and a
472
+ * kernel that predates the key never receives it.
473
+ */
474
+ dollars_unmetered?: boolean;
475
+ }
476
+
455
477
  export interface KernelBudgetSpec {
456
478
  pricing?: 'frozen';
457
- prior_spend?: { tokens_in: number; tokens_out: number; dollars: string; wallclock_ms: number; day?: number };
479
+ prior_spend?: KernelPriorSpend;
458
480
  max_tokens?: number;
459
481
  max_wallclock_ms?: number;
460
482
  window?: 'day';
@@ -1,10 +1,19 @@
1
1
  import { pricedUsage } from './model-pricing.js';
2
+ import type { StepUsage } from './protocol.js';
2
3
  import type { WorkerCliResult } from './worker-cli.js';
3
4
 
4
- /** Zero-dollar usage for an unpriced step, only when the CLI reported tokens. */
5
- function unmeteredUsage(input: number | undefined, output: number | undefined) {
6
- if (input === undefined || output === undefined) return undefined;
7
- return { tokens_in: input, tokens_out: output, dollars: '0.000000' };
5
+ /**
6
+ * Usage for a step whose dollar cost is unknown (unpriced or undeclared model).
7
+ *
8
+ * It carries the reported tokens and `dollars_unmetered: true`, and never a
9
+ * `dollars` amount: unknown cost is not journaled as a measured $0. The kernel
10
+ * records the flag on the step's `budget`/`spend` and on the run total, counts
11
+ * the tokens toward token ceilings, and compares only metered dollars against
12
+ * `maxDollars`. A CLI that reported no tokens still marks the step unmetered,
13
+ * with zero tokens — the same token accounting any unreported step gets.
14
+ */
15
+ function unmeteredUsage(input: number | undefined, output: number | undefined): StepUsage {
16
+ return { tokens_in: input ?? 0, tokens_out: output ?? 0, dollars_unmetered: true };
8
17
  }
9
18
 
10
19
  /**
@@ -12,12 +21,19 @@ function unmeteredUsage(input: number | undefined, output: number | undefined) {
12
21
  * (non-integer or negative) are the one remaining failure mode — those are
13
22
  * journaled as `worker_error` with the usage projected from clamped counts.
14
23
  *
15
- * Unpriced models are NOT a failure here: the step journals zero dollars, so it
16
- * never trips a dollar budget, but its reported tokens still count toward any
17
- * token budget. Preflight (see `budgetDiagnostics`) warns that such a step is
18
- * unmetered for dollars.
24
+ * Contract across preflight worker kernel (keep all three aligned):
25
+ * - preflight (`budgetDiagnostics`) warns `budget_unmetered` for a
26
+ * dollar-budgeted step with no frozen price and lets it run;
27
+ * - this function returns priced usage (`dollars`) for a priced model, and
28
+ * unmetered usage (`dollars_unmetered: true`, no `dollars`) otherwise —
29
+ * never `undefined` for a successful decode, so token ceilings always see the
30
+ * step and the journal always says whether its dollars are known;
31
+ * - the kernel (`machine/budget.rs`) enforces tokens on every charge and
32
+ * dollars on metered charges only, and rejects unmetered usage that also
33
+ * claims non-zero dollars.
34
+ * `tests/budget-unmetered-live.test.ts` pins the chain end to end.
19
35
  */
20
- export function workerSpend(result: WorkerCliResult, model?: string) {
36
+ export function workerSpend(result: WorkerCliResult, model?: string): { result: WorkerCliResult; usage: StepUsage | undefined } {
21
37
  try {
22
38
  const usage = pricedUsage(model, result.tokens_input, result.tokens_output)
23
39
  ?? unmeteredUsage(result.tokens_input, result.tokens_output);
@@ -1,5 +0,0 @@
1
- import type { StepFailedDetails } from '../failure-kinds.js';
2
- import type { JournalClient } from '../journal-client.js';
3
- /** Read completion evidence through the existing protocol, including later pages. */
4
- export declare function deterministicFailureDetails(client: JournalClient, runId: string): Promise<StepFailedDetails | undefined>;
5
- //# sourceMappingURL=deterministic-failure.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deterministic-failure.d.ts","sourceRoot":"","sources":["../../src/cli/deterministic-failure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,qFAAqF;AACrF,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAqCxC"}
@@ -1,66 +0,0 @@
1
- const STDERR_TAIL_BYTES = 1_024;
2
- /** Read completion evidence through the existing protocol, including later pages. */
3
- export async function deterministicFailureDetails(client, runId) {
4
- const snapshot = await client.runGet(runId);
5
- if (!Object.values(snapshot.steps).some(step => step.type === 'deterministic'))
6
- return undefined;
7
- let fromSeq = 1;
8
- const failures = new Map();
9
- while (true) {
10
- const { entries } = await client.journalRead(runId, fromSeq, 100);
11
- if (entries.length === 0)
12
- break;
13
- for (const raw of entries) {
14
- const entry = record(raw);
15
- const seq = entry?.['seq'];
16
- if (typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < fromSeq) {
17
- throw new Error('invalid journal sequence in command failure inspection');
18
- }
19
- fromSeq = seq + 1;
20
- const stepId = entry?.['step_id'];
21
- if (entry?.['entry_type'] !== 'step.completed' || typeof stepId !== 'string'
22
- || snapshot.steps[stepId]?.type !== 'deterministic')
23
- continue;
24
- // A later completion supersedes an earlier failed attempt.
25
- failures.delete(stepId);
26
- const payload = record(entry['payload']);
27
- // Post-#292 the kernel preserves the captured `{exit_code, stdout_tail,
28
- // stderr_tail}` in `output` on failed completions; prefer it. Fall back
29
- // to `trajectory_tail` for journal records emitted before that fix.
30
- const output = record(payload?.['output']) ?? record(payload?.['trajectory_tail']);
31
- const exitCode = output?.['exit_code'];
32
- if (payload?.['disposition'] !== 'step_done' || payload['completionReason'] === 'success'
33
- || typeof exitCode !== 'number' || !Number.isSafeInteger(exitCode) || exitCode === 0)
34
- continue;
35
- failures.set(stepId, {
36
- stepId,
37
- exitCode,
38
- stderrTail: stderrTail(output?.['stderr_tail']),
39
- hint: `flows replay ${shellQuote(runId)} --at ${shellQuote(stepId)}`,
40
- });
41
- }
42
- }
43
- return [...failures.values()].at(-1);
44
- }
45
- function record(value) {
46
- return value !== null && typeof value === 'object' && !Array.isArray(value)
47
- ? value : undefined;
48
- }
49
- function stderrTail(value) {
50
- if (typeof value !== 'string')
51
- return '';
52
- const bytes = Buffer.from(value, 'utf8');
53
- let start = Math.max(0, bytes.length - STDERR_TAIL_BYTES);
54
- // Drop a partial leading code point, avoiding replacement-byte expansion.
55
- while (start < bytes.length && (bytes[start] & 0xc0) === 0x80)
56
- start += 1;
57
- // Preserve tabs/newlines; replace binary controls (including ESC and CR),
58
- // C1 controls and Unicode formatting controls without growing the excerpt.
59
- return bytes.subarray(start).toString('utf8')
60
- .replace(/[\p{Cc}\p{Cf}]/gu, character => character === '\n' || character === '\t' ? character : '?');
61
- }
62
- function shellQuote(value) {
63
- return /^[A-Za-z0-9_-]+$/.test(value) && !value.startsWith('-')
64
- ? value : `'${value.replace(/'/g, "'\\''")}'`;
65
- }
66
- //# sourceMappingURL=deterministic-failure.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deterministic-failure.js","sourceRoot":"","sources":["../../src/cli/deterministic-failure.ts"],"names":[],"mappings":"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"}
@@ -1,69 +0,0 @@
1
- import type { StepFailedDetails } from '../failure-kinds.js';
2
- import type { JournalClient } from '../journal-client.js';
3
-
4
- const STDERR_TAIL_BYTES = 1_024;
5
-
6
- /** Read completion evidence through the existing protocol, including later pages. */
7
- export async function deterministicFailureDetails(
8
- client: JournalClient,
9
- runId: string,
10
- ): Promise<StepFailedDetails | undefined> {
11
- const snapshot = await client.runGet(runId);
12
- if (!Object.values(snapshot.steps).some(step => step.type === 'deterministic')) return undefined;
13
- let fromSeq = 1;
14
- const failures = new Map<string, StepFailedDetails>();
15
- while (true) {
16
- const { entries } = await client.journalRead(runId, fromSeq, 100);
17
- if (entries.length === 0) break;
18
- for (const raw of entries) {
19
- const entry = record(raw);
20
- const seq = entry?.['seq'];
21
- if (typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < fromSeq) {
22
- throw new Error('invalid journal sequence in command failure inspection');
23
- }
24
- fromSeq = seq + 1;
25
- const stepId = entry?.['step_id'];
26
- if (entry?.['entry_type'] !== 'step.completed' || typeof stepId !== 'string'
27
- || snapshot.steps[stepId]?.type !== 'deterministic') continue;
28
- // A later completion supersedes an earlier failed attempt.
29
- failures.delete(stepId);
30
- const payload = record(entry['payload']);
31
- // Post-#292 the kernel preserves the captured `{exit_code, stdout_tail,
32
- // stderr_tail}` in `output` on failed completions; prefer it. Fall back
33
- // to `trajectory_tail` for journal records emitted before that fix.
34
- const output = record(payload?.['output']) ?? record(payload?.['trajectory_tail']);
35
- const exitCode = output?.['exit_code'];
36
- if (payload?.['disposition'] !== 'step_done' || payload['completionReason'] === 'success'
37
- || typeof exitCode !== 'number' || !Number.isSafeInteger(exitCode) || exitCode === 0) continue;
38
- failures.set(stepId, {
39
- stepId,
40
- exitCode,
41
- stderrTail: stderrTail(output?.['stderr_tail']),
42
- hint: `flows replay ${shellQuote(runId)} --at ${shellQuote(stepId)}`,
43
- });
44
- }
45
- }
46
- return [...failures.values()].at(-1);
47
- }
48
-
49
- function record(value: unknown): Record<string, unknown> | undefined {
50
- return value !== null && typeof value === 'object' && !Array.isArray(value)
51
- ? value as Record<string, unknown> : undefined;
52
- }
53
-
54
- function stderrTail(value: unknown): string {
55
- if (typeof value !== 'string') return '';
56
- const bytes = Buffer.from(value, 'utf8');
57
- let start = Math.max(0, bytes.length - STDERR_TAIL_BYTES);
58
- // Drop a partial leading code point, avoiding replacement-byte expansion.
59
- while (start < bytes.length && (bytes[start]! & 0xc0) === 0x80) start += 1;
60
- // Preserve tabs/newlines; replace binary controls (including ESC and CR),
61
- // C1 controls and Unicode formatting controls without growing the excerpt.
62
- return bytes.subarray(start).toString('utf8')
63
- .replace(/[\p{Cc}\p{Cf}]/gu, character => character === '\n' || character === '\t' ? character : '?');
64
- }
65
-
66
- function shellQuote(value: string): string {
67
- return /^[A-Za-z0-9_-]+$/.test(value) && !value.startsWith('-')
68
- ? value : `'${value.replace(/'/g, "'\\''")}'`;
69
- }