@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.
- package/dist/authored-budget.d.ts.map +1 -1
- package/dist/authored-budget.js +16 -7
- package/dist/authored-budget.js.map +1 -1
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +5 -0
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/budget-preflight.d.ts +17 -6
- package/dist/budget-preflight.d.ts.map +1 -1
- package/dist/budget-preflight.js +9 -6
- package/dist/budget-preflight.js.map +1 -1
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +8 -0
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/deploy.d.ts.map +1 -1
- package/dist/cli/deploy.js +5 -0
- package/dist/cli/deploy.js.map +1 -1
- package/dist/cli/direct-run.d.ts.map +1 -1
- package/dist/cli/direct-run.js +28 -0
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/run.d.ts +8 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +34 -11
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/step-failure.d.ts +40 -0
- package/dist/cli/step-failure.d.ts.map +1 -0
- package/dist/cli/step-failure.js +150 -0
- package/dist/cli/step-failure.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/daemon-connection.d.ts +7 -0
- package/dist/daemon-connection.d.ts.map +1 -1
- package/dist/daemon-connection.js +7 -0
- package/dist/daemon-connection.js.map +1 -1
- package/dist/failure-kinds.d.ts +25 -3
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +5 -2
- package/dist/failure-kinds.js.map +1 -1
- package/dist/journal-client.d.ts +2 -6
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js.map +1 -1
- package/dist/model-pricing.d.ts +7 -5
- package/dist/model-pricing.d.ts.map +1 -1
- package/dist/model-pricing.js +7 -5
- package/dist/model-pricing.js.map +1 -1
- package/dist/preflight.d.ts +5 -0
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +22 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/spec.d.ts +22 -7
- package/dist/spec.d.ts.map +1 -1
- package/dist/worker-spend.d.ts +13 -9
- package/dist/worker-spend.d.ts.map +1 -1
- package/dist/worker-spend.js +22 -8
- package/dist/worker-spend.js.map +1 -1
- package/package.json +2 -2
- package/src/authored-budget.ts +35 -9
- package/src/authored-flow-executor.ts +5 -0
- package/src/budget-preflight.ts +17 -6
- package/src/cli/build.ts +7 -0
- package/src/cli/deploy.ts +4 -0
- package/src/cli/direct-run.ts +28 -0
- package/src/cli/run.ts +43 -11
- package/src/cli/step-failure.ts +160 -0
- package/src/cli.ts +4 -4
- package/src/daemon-connection.ts +8 -0
- package/src/failure-kinds.ts +25 -3
- package/src/journal-client.ts +2 -1
- package/src/model-pricing.ts +7 -5
- package/src/preflight.ts +5 -0
- package/src/protocol.ts +15 -2
- package/src/spec.ts +23 -1
- package/src/worker-spend.ts +25 -9
- package/dist/cli/deterministic-failure.d.ts +0 -5
- package/dist/cli/deterministic-failure.d.ts.map +0 -1
- package/dist/cli/deterministic-failure.js +0 -66
- package/dist/cli/deterministic-failure.js.map +0 -1
- 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?:
|
|
479
|
+
prior_spend?: KernelPriorSpend;
|
|
458
480
|
max_tokens?: number;
|
|
459
481
|
max_wallclock_ms?: number;
|
|
460
482
|
window?: 'day';
|
package/src/worker-spend.ts
CHANGED
|
@@ -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
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
-
}
|