runcycles 0.1.2 → 0.3.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 +87 -11
- package/dist/index.cjs +22 -7
- package/dist/index.d.cts +12 -8
- package/dist/index.d.ts +12 -8
- package/dist/index.js +22 -7
- package/package.json +19 -9
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/runcycles)
|
|
2
|
+
[](https://www.npmjs.com/package/runcycles)
|
|
3
|
+
[](https://github.com/runcycles/cycles-client-typescript/actions)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/runcycles/cycles-client-typescript/actions)
|
|
6
|
+
|
|
1
7
|
# Cycles TypeScript Client
|
|
2
8
|
|
|
3
9
|
TypeScript client for the [Cycles](https://runcycles.io) budget-management protocol — govern spend on AI calls, API usage, and any metered resource.
|
|
@@ -69,6 +75,24 @@ const result = await callLlm("Hello", 100);
|
|
|
69
75
|
|
|
70
76
|
**What happens:** `withCycles` reserves budget before calling your function, runs it inside an async context (so `getCyclesContext()` works), commits the actual cost on success, or releases the reservation on failure. A background heartbeat keeps the reservation alive.
|
|
71
77
|
|
|
78
|
+
### Budget lifecycle
|
|
79
|
+
|
|
80
|
+
| Scenario | Outcome | Detail |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| Reservation denied | **Neither** | `BudgetExceededError`, `OverdraftLimitExceededError`, or `DebtOutstandingError` thrown; function never executes |
|
|
83
|
+
| `dryRun: true`, any decision | **Neither** | Returns `DryRunResult` or throws; no real reservation created |
|
|
84
|
+
| Function returns successfully | **Commit** | Actual amount charged; unused remainder auto-released |
|
|
85
|
+
| Function throws any error | **Release** | Full reserved amount returned to budget; error re-thrown |
|
|
86
|
+
| Commit fails (5xx / network) | **Retry** | Exponential backoff with configurable attempts |
|
|
87
|
+
| Commit fails (non-retryable 4xx) | **Release** | Reservation released after non-retryable client error |
|
|
88
|
+
| Commit gets RESERVATION_EXPIRED | **Neither** | Server already reclaimed budget on TTL expiry |
|
|
89
|
+
| Commit gets RESERVATION_FINALIZED | **Neither** | Already committed or released (idempotent replay) |
|
|
90
|
+
| Commit gets IDEMPOTENCY_MISMATCH | **Neither** | Previous commit already processed; no release attempted |
|
|
91
|
+
|
|
92
|
+
**Streaming (`reserveForStream`):** Call `handle.commit(actual)` on success or `handle.release(reason)` on failure. If neither is called, the server reclaims the budget when the reservation TTL expires.
|
|
93
|
+
|
|
94
|
+
All thrown errors from the guarded function trigger release. See [How Reserve-Commit Works](https://runcycles.io/protocol/how-reserve-commit-works-in-cycles) for the full protocol-level explanation.
|
|
95
|
+
|
|
72
96
|
### 2. Streaming adapter
|
|
73
97
|
|
|
74
98
|
For LLM streaming where usage is only known after the stream finishes:
|
|
@@ -292,9 +316,9 @@ interface WithCyclesConfig {
|
|
|
292
316
|
actual?: number | ((result) => number); // Actual cost (static or computed from result)
|
|
293
317
|
useEstimateIfActualNotProvided?: boolean; // Default: true — use estimate as actual
|
|
294
318
|
|
|
295
|
-
// Action identification
|
|
296
|
-
actionKind?: string;
|
|
297
|
-
actionName?: string;
|
|
319
|
+
// Action identification (static or computed from args)
|
|
320
|
+
actionKind?: string | ((...args) => string | undefined); // e.g. "llm.completion" (default: "unknown")
|
|
321
|
+
actionName?: string | ((...args) => string | undefined); // e.g. "gpt-4" (default: "unknown")
|
|
298
322
|
actionTags?: string[]; // Optional tags for categorization
|
|
299
323
|
|
|
300
324
|
// Budget unit
|
|
@@ -303,16 +327,16 @@ interface WithCyclesConfig {
|
|
|
303
327
|
// Reservation settings
|
|
304
328
|
ttlMs?: number; // Time-to-live in ms (default: 60000, range: 1000–86400000)
|
|
305
329
|
gracePeriodMs?: number; // Grace period in ms (range: 0–60000)
|
|
306
|
-
overagePolicy?: string; // "
|
|
330
|
+
overagePolicy?: string; // "ALLOW_IF_AVAILABLE" (default), "REJECT", "ALLOW_WITH_OVERDRAFT"
|
|
307
331
|
dryRun?: boolean; // Shadow mode — evaluates budget without executing
|
|
308
332
|
|
|
309
|
-
// Subject fields (override config defaults)
|
|
310
|
-
tenant?: string;
|
|
311
|
-
workspace?: string;
|
|
312
|
-
app?: string;
|
|
313
|
-
workflow?: string;
|
|
314
|
-
agent?: string;
|
|
315
|
-
toolset?: string;
|
|
333
|
+
// Subject fields (override config defaults; static or computed from args)
|
|
334
|
+
tenant?: string | ((...args) => string | undefined);
|
|
335
|
+
workspace?: string | ((...args) => string | undefined);
|
|
336
|
+
app?: string | ((...args) => string | undefined);
|
|
337
|
+
workflow?: string | ((...args) => string | undefined);
|
|
338
|
+
agent?: string | ((...args) => string | undefined);
|
|
339
|
+
toolset?: string | ((...args) => string | undefined);
|
|
316
340
|
dimensions?: Record<string, string>; // Custom key-value dimensions
|
|
317
341
|
|
|
318
342
|
// Client
|
|
@@ -320,6 +344,28 @@ interface WithCyclesConfig {
|
|
|
320
344
|
}
|
|
321
345
|
```
|
|
322
346
|
|
|
347
|
+
A callable returning `undefined` falls through to the client-config default for subject fields, or to `"unknown"` for `actionKind` / `actionName` — same fallback semantics as a missing static. Callables run before the reservation is created; if one throws, the reservation is never attempted and the error propagates to the caller.
|
|
348
|
+
|
|
349
|
+
### Dynamic subject and action fields
|
|
350
|
+
|
|
351
|
+
Derive the subject scope or action identity from per-call arguments:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
const runRequest = withCycles(
|
|
355
|
+
{
|
|
356
|
+
estimate: (req, workspaceId) => req.tokens * 10,
|
|
357
|
+
workspace: (_req, workspaceId) => workspaceId,
|
|
358
|
+
actionKind: "llm.completion",
|
|
359
|
+
actionName: (req) => req.model,
|
|
360
|
+
client,
|
|
361
|
+
},
|
|
362
|
+
async (req: { tokens: number; model: string }, workspaceId: string) => {
|
|
363
|
+
// ... the reservation routes to this workspaceId ...
|
|
364
|
+
return callLLM(req);
|
|
365
|
+
},
|
|
366
|
+
);
|
|
367
|
+
```
|
|
368
|
+
|
|
323
369
|
## Context Access
|
|
324
370
|
|
|
325
371
|
Inside a `withCycles`-guarded function, access the active reservation via `getCyclesContext()`:
|
|
@@ -795,6 +841,29 @@ ErrorCode.INTERNAL_ERROR
|
|
|
795
841
|
ErrorCode.UNKNOWN
|
|
796
842
|
```
|
|
797
843
|
|
|
844
|
+
## Nested `withCycles` Calls
|
|
845
|
+
|
|
846
|
+
Calling a `withCycles`-wrapped function from inside another `withCycles`-wrapped function is allowed — it will not throw an error. However, each wrapper creates an **independent reservation** that deducts budget separately:
|
|
847
|
+
|
|
848
|
+
```typescript
|
|
849
|
+
const inner = withCycles({ estimate: 100, actionName: "inner" }, async () => "done");
|
|
850
|
+
const outer = withCycles({ estimate: 500, actionName: "outer" }, async () => {
|
|
851
|
+
return await inner(); // creates a SECOND reservation — 600 total deducted, not 500
|
|
852
|
+
});
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
This means nested guards **double-count budget**. The outer reservation already covers the full estimated cost of the operation, so an inner reservation deducts additional budget from the same pool.
|
|
856
|
+
|
|
857
|
+
**Recommended pattern:** Place `withCycles` at the outermost entry point only. Inner functions should be plain async functions without their own guard:
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
const inner = async () => "done"; // no withCycles — called within a guarded operation
|
|
861
|
+
|
|
862
|
+
const outer = withCycles({ estimate: 500, actionName: "outer" }, async () => {
|
|
863
|
+
return await inner(); // single reservation — 500 total
|
|
864
|
+
});
|
|
865
|
+
```
|
|
866
|
+
|
|
798
867
|
## Examples
|
|
799
868
|
|
|
800
869
|
See the [`examples/`](./examples/) directory:
|
|
@@ -824,6 +893,13 @@ See the [`examples/`](./examples/) directory:
|
|
|
824
893
|
- **Dual ESM/CJS**: Works with both module systems
|
|
825
894
|
- **Input validation**: Client-side validation of TTL, amounts, subject fields, and more
|
|
826
895
|
|
|
896
|
+
## Documentation
|
|
897
|
+
|
|
898
|
+
- [Cycles Documentation](https://runcycles.io) — full docs site
|
|
899
|
+
- [TypeScript Quickstart](https://runcycles.io/quickstart/getting-started-with-the-typescript-client) — getting started guide
|
|
900
|
+
- [TypeScript Client Configuration Reference](https://runcycles.io/configuration/typescript-client-configuration-reference) — all configuration options
|
|
901
|
+
- [Error Handling Patterns in TypeScript](https://runcycles.io/how-to/error-handling-patterns-in-typescript) — handling budget errors
|
|
902
|
+
|
|
827
903
|
## License
|
|
828
904
|
|
|
829
905
|
Apache-2.0
|
package/dist/index.cjs
CHANGED
|
@@ -835,12 +835,15 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
835
835
|
ErrorCode2["FORBIDDEN"] = "FORBIDDEN";
|
|
836
836
|
ErrorCode2["NOT_FOUND"] = "NOT_FOUND";
|
|
837
837
|
ErrorCode2["BUDGET_EXCEEDED"] = "BUDGET_EXCEEDED";
|
|
838
|
+
ErrorCode2["BUDGET_FROZEN"] = "BUDGET_FROZEN";
|
|
839
|
+
ErrorCode2["BUDGET_CLOSED"] = "BUDGET_CLOSED";
|
|
838
840
|
ErrorCode2["RESERVATION_EXPIRED"] = "RESERVATION_EXPIRED";
|
|
839
841
|
ErrorCode2["RESERVATION_FINALIZED"] = "RESERVATION_FINALIZED";
|
|
840
842
|
ErrorCode2["IDEMPOTENCY_MISMATCH"] = "IDEMPOTENCY_MISMATCH";
|
|
841
843
|
ErrorCode2["UNIT_MISMATCH"] = "UNIT_MISMATCH";
|
|
842
844
|
ErrorCode2["OVERDRAFT_LIMIT_EXCEEDED"] = "OVERDRAFT_LIMIT_EXCEEDED";
|
|
843
845
|
ErrorCode2["DEBT_OUTSTANDING"] = "DEBT_OUTSTANDING";
|
|
846
|
+
ErrorCode2["MAX_EXTENSIONS_EXCEEDED"] = "MAX_EXTENSIONS_EXCEEDED";
|
|
844
847
|
ErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
845
848
|
ErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
846
849
|
return ErrorCode2;
|
|
@@ -926,7 +929,13 @@ function evaluateActual(expr, result, estimate, useEstimateFallback) {
|
|
|
926
929
|
"actual expression is required when useEstimateIfActualNotProvided is false"
|
|
927
930
|
);
|
|
928
931
|
}
|
|
929
|
-
function
|
|
932
|
+
function evaluateStringField(expr, args) {
|
|
933
|
+
if (typeof expr === "function") {
|
|
934
|
+
return expr(...args);
|
|
935
|
+
}
|
|
936
|
+
return expr;
|
|
937
|
+
}
|
|
938
|
+
function buildReservationBody(cfg, estimate, defaultSubject, args) {
|
|
930
939
|
validateNonNegative(estimate, "estimate");
|
|
931
940
|
const ttlMs = cfg.ttlMs ?? DEFAULT_TTL_MS;
|
|
932
941
|
validateTtlMs(ttlMs);
|
|
@@ -939,7 +948,8 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
939
948
|
"agent",
|
|
940
949
|
"toolset"
|
|
941
950
|
]) {
|
|
942
|
-
const
|
|
951
|
+
const resolved = evaluateStringField(cfg[field], args);
|
|
952
|
+
const val = resolved ?? defaultSubject[field];
|
|
943
953
|
if (val) {
|
|
944
954
|
subject[field] = val;
|
|
945
955
|
}
|
|
@@ -949,8 +959,8 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
949
959
|
}
|
|
950
960
|
validateSubject(subject);
|
|
951
961
|
const action = {
|
|
952
|
-
kind: cfg.actionKind ?? "unknown",
|
|
953
|
-
name: cfg.actionName ?? "unknown"
|
|
962
|
+
kind: evaluateStringField(cfg.actionKind, args) ?? "unknown",
|
|
963
|
+
name: evaluateStringField(cfg.actionName, args) ?? "unknown"
|
|
954
964
|
};
|
|
955
965
|
if (cfg.actionTags) {
|
|
956
966
|
action.tags = cfg.actionTags;
|
|
@@ -962,7 +972,7 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
962
972
|
action,
|
|
963
973
|
estimate: { unit, amount: estimate },
|
|
964
974
|
ttl_ms: ttlMs,
|
|
965
|
-
overage_policy: cfg.overagePolicy ?? "
|
|
975
|
+
overage_policy: cfg.overagePolicy ?? "ALLOW_IF_AVAILABLE"
|
|
966
976
|
};
|
|
967
977
|
validateGracePeriodMs(cfg.gracePeriodMs);
|
|
968
978
|
if (cfg.gracePeriodMs !== void 0) {
|
|
@@ -1005,7 +1015,12 @@ var AsyncCyclesLifecycle = class {
|
|
|
1005
1015
|
}
|
|
1006
1016
|
async execute(fn, args, cfg) {
|
|
1007
1017
|
const estimate = evaluateAmount(cfg.estimate, args);
|
|
1008
|
-
const createBody = buildReservationBody(
|
|
1018
|
+
const createBody = buildReservationBody(
|
|
1019
|
+
cfg,
|
|
1020
|
+
estimate,
|
|
1021
|
+
this._defaultSubject,
|
|
1022
|
+
args
|
|
1023
|
+
);
|
|
1009
1024
|
const resResponse = await this._client.createReservation(createBody);
|
|
1010
1025
|
if (!resResponse.isSuccess) {
|
|
1011
1026
|
throw buildProtocolException("Failed to create reservation", resResponse);
|
|
@@ -1284,7 +1299,7 @@ async function reserveForStream(options) {
|
|
|
1284
1299
|
actionTags,
|
|
1285
1300
|
ttlMs = DEFAULT_TTL_MS,
|
|
1286
1301
|
gracePeriodMs,
|
|
1287
|
-
overagePolicy = "
|
|
1302
|
+
overagePolicy = "ALLOW_IF_AVAILABLE",
|
|
1288
1303
|
dimensions
|
|
1289
1304
|
} = options;
|
|
1290
1305
|
validateNonNegative(estimate, "estimate");
|
package/dist/index.d.cts
CHANGED
|
@@ -76,12 +76,15 @@ declare enum ErrorCode {
|
|
|
76
76
|
FORBIDDEN = "FORBIDDEN",
|
|
77
77
|
NOT_FOUND = "NOT_FOUND",
|
|
78
78
|
BUDGET_EXCEEDED = "BUDGET_EXCEEDED",
|
|
79
|
+
BUDGET_FROZEN = "BUDGET_FROZEN",
|
|
80
|
+
BUDGET_CLOSED = "BUDGET_CLOSED",
|
|
79
81
|
RESERVATION_EXPIRED = "RESERVATION_EXPIRED",
|
|
80
82
|
RESERVATION_FINALIZED = "RESERVATION_FINALIZED",
|
|
81
83
|
IDEMPOTENCY_MISMATCH = "IDEMPOTENCY_MISMATCH",
|
|
82
84
|
UNIT_MISMATCH = "UNIT_MISMATCH",
|
|
83
85
|
OVERDRAFT_LIMIT_EXCEEDED = "OVERDRAFT_LIMIT_EXCEEDED",
|
|
84
86
|
DEBT_OUTSTANDING = "DEBT_OUTSTANDING",
|
|
87
|
+
MAX_EXTENSIONS_EXCEEDED = "MAX_EXTENSIONS_EXCEEDED",
|
|
85
88
|
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
86
89
|
UNKNOWN = "UNKNOWN"
|
|
87
90
|
}
|
|
@@ -213,6 +216,7 @@ interface DecisionResponse {
|
|
|
213
216
|
interface EventCreateResponse {
|
|
214
217
|
status: EventStatus;
|
|
215
218
|
eventId: string;
|
|
219
|
+
charged?: Amount;
|
|
216
220
|
balances?: Balance[];
|
|
217
221
|
}
|
|
218
222
|
interface DryRunResult {
|
|
@@ -338,20 +342,20 @@ declare class CyclesClient {
|
|
|
338
342
|
interface WithCyclesConfig<TArgs extends unknown[] = unknown[], TResult = unknown> {
|
|
339
343
|
estimate: number | ((...args: TArgs) => number);
|
|
340
344
|
actual?: number | ((result: TResult) => number);
|
|
341
|
-
actionKind?: string;
|
|
342
|
-
actionName?: string;
|
|
345
|
+
actionKind?: string | ((...args: TArgs) => string | undefined);
|
|
346
|
+
actionName?: string | ((...args: TArgs) => string | undefined);
|
|
343
347
|
actionTags?: string[];
|
|
344
348
|
unit?: string;
|
|
345
349
|
ttlMs?: number;
|
|
346
350
|
gracePeriodMs?: number;
|
|
347
351
|
overagePolicy?: string;
|
|
348
352
|
dryRun?: boolean;
|
|
349
|
-
tenant?: string;
|
|
350
|
-
workspace?: string;
|
|
351
|
-
app?: string;
|
|
352
|
-
workflow?: string;
|
|
353
|
-
agent?: string;
|
|
354
|
-
toolset?: string;
|
|
353
|
+
tenant?: string | ((...args: TArgs) => string | undefined);
|
|
354
|
+
workspace?: string | ((...args: TArgs) => string | undefined);
|
|
355
|
+
app?: string | ((...args: TArgs) => string | undefined);
|
|
356
|
+
workflow?: string | ((...args: TArgs) => string | undefined);
|
|
357
|
+
agent?: string | ((...args: TArgs) => string | undefined);
|
|
358
|
+
toolset?: string | ((...args: TArgs) => string | undefined);
|
|
355
359
|
dimensions?: Record<string, string>;
|
|
356
360
|
useEstimateIfActualNotProvided?: boolean;
|
|
357
361
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -76,12 +76,15 @@ declare enum ErrorCode {
|
|
|
76
76
|
FORBIDDEN = "FORBIDDEN",
|
|
77
77
|
NOT_FOUND = "NOT_FOUND",
|
|
78
78
|
BUDGET_EXCEEDED = "BUDGET_EXCEEDED",
|
|
79
|
+
BUDGET_FROZEN = "BUDGET_FROZEN",
|
|
80
|
+
BUDGET_CLOSED = "BUDGET_CLOSED",
|
|
79
81
|
RESERVATION_EXPIRED = "RESERVATION_EXPIRED",
|
|
80
82
|
RESERVATION_FINALIZED = "RESERVATION_FINALIZED",
|
|
81
83
|
IDEMPOTENCY_MISMATCH = "IDEMPOTENCY_MISMATCH",
|
|
82
84
|
UNIT_MISMATCH = "UNIT_MISMATCH",
|
|
83
85
|
OVERDRAFT_LIMIT_EXCEEDED = "OVERDRAFT_LIMIT_EXCEEDED",
|
|
84
86
|
DEBT_OUTSTANDING = "DEBT_OUTSTANDING",
|
|
87
|
+
MAX_EXTENSIONS_EXCEEDED = "MAX_EXTENSIONS_EXCEEDED",
|
|
85
88
|
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
86
89
|
UNKNOWN = "UNKNOWN"
|
|
87
90
|
}
|
|
@@ -213,6 +216,7 @@ interface DecisionResponse {
|
|
|
213
216
|
interface EventCreateResponse {
|
|
214
217
|
status: EventStatus;
|
|
215
218
|
eventId: string;
|
|
219
|
+
charged?: Amount;
|
|
216
220
|
balances?: Balance[];
|
|
217
221
|
}
|
|
218
222
|
interface DryRunResult {
|
|
@@ -338,20 +342,20 @@ declare class CyclesClient {
|
|
|
338
342
|
interface WithCyclesConfig<TArgs extends unknown[] = unknown[], TResult = unknown> {
|
|
339
343
|
estimate: number | ((...args: TArgs) => number);
|
|
340
344
|
actual?: number | ((result: TResult) => number);
|
|
341
|
-
actionKind?: string;
|
|
342
|
-
actionName?: string;
|
|
345
|
+
actionKind?: string | ((...args: TArgs) => string | undefined);
|
|
346
|
+
actionName?: string | ((...args: TArgs) => string | undefined);
|
|
343
347
|
actionTags?: string[];
|
|
344
348
|
unit?: string;
|
|
345
349
|
ttlMs?: number;
|
|
346
350
|
gracePeriodMs?: number;
|
|
347
351
|
overagePolicy?: string;
|
|
348
352
|
dryRun?: boolean;
|
|
349
|
-
tenant?: string;
|
|
350
|
-
workspace?: string;
|
|
351
|
-
app?: string;
|
|
352
|
-
workflow?: string;
|
|
353
|
-
agent?: string;
|
|
354
|
-
toolset?: string;
|
|
353
|
+
tenant?: string | ((...args: TArgs) => string | undefined);
|
|
354
|
+
workspace?: string | ((...args: TArgs) => string | undefined);
|
|
355
|
+
app?: string | ((...args: TArgs) => string | undefined);
|
|
356
|
+
workflow?: string | ((...args: TArgs) => string | undefined);
|
|
357
|
+
agent?: string | ((...args: TArgs) => string | undefined);
|
|
358
|
+
toolset?: string | ((...args: TArgs) => string | undefined);
|
|
355
359
|
dimensions?: Record<string, string>;
|
|
356
360
|
useEstimateIfActualNotProvided?: boolean;
|
|
357
361
|
}
|
package/dist/index.js
CHANGED
|
@@ -760,12 +760,15 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
760
760
|
ErrorCode2["FORBIDDEN"] = "FORBIDDEN";
|
|
761
761
|
ErrorCode2["NOT_FOUND"] = "NOT_FOUND";
|
|
762
762
|
ErrorCode2["BUDGET_EXCEEDED"] = "BUDGET_EXCEEDED";
|
|
763
|
+
ErrorCode2["BUDGET_FROZEN"] = "BUDGET_FROZEN";
|
|
764
|
+
ErrorCode2["BUDGET_CLOSED"] = "BUDGET_CLOSED";
|
|
763
765
|
ErrorCode2["RESERVATION_EXPIRED"] = "RESERVATION_EXPIRED";
|
|
764
766
|
ErrorCode2["RESERVATION_FINALIZED"] = "RESERVATION_FINALIZED";
|
|
765
767
|
ErrorCode2["IDEMPOTENCY_MISMATCH"] = "IDEMPOTENCY_MISMATCH";
|
|
766
768
|
ErrorCode2["UNIT_MISMATCH"] = "UNIT_MISMATCH";
|
|
767
769
|
ErrorCode2["OVERDRAFT_LIMIT_EXCEEDED"] = "OVERDRAFT_LIMIT_EXCEEDED";
|
|
768
770
|
ErrorCode2["DEBT_OUTSTANDING"] = "DEBT_OUTSTANDING";
|
|
771
|
+
ErrorCode2["MAX_EXTENSIONS_EXCEEDED"] = "MAX_EXTENSIONS_EXCEEDED";
|
|
769
772
|
ErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
770
773
|
ErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
771
774
|
return ErrorCode2;
|
|
@@ -851,7 +854,13 @@ function evaluateActual(expr, result, estimate, useEstimateFallback) {
|
|
|
851
854
|
"actual expression is required when useEstimateIfActualNotProvided is false"
|
|
852
855
|
);
|
|
853
856
|
}
|
|
854
|
-
function
|
|
857
|
+
function evaluateStringField(expr, args) {
|
|
858
|
+
if (typeof expr === "function") {
|
|
859
|
+
return expr(...args);
|
|
860
|
+
}
|
|
861
|
+
return expr;
|
|
862
|
+
}
|
|
863
|
+
function buildReservationBody(cfg, estimate, defaultSubject, args) {
|
|
855
864
|
validateNonNegative(estimate, "estimate");
|
|
856
865
|
const ttlMs = cfg.ttlMs ?? DEFAULT_TTL_MS;
|
|
857
866
|
validateTtlMs(ttlMs);
|
|
@@ -864,7 +873,8 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
864
873
|
"agent",
|
|
865
874
|
"toolset"
|
|
866
875
|
]) {
|
|
867
|
-
const
|
|
876
|
+
const resolved = evaluateStringField(cfg[field], args);
|
|
877
|
+
const val = resolved ?? defaultSubject[field];
|
|
868
878
|
if (val) {
|
|
869
879
|
subject[field] = val;
|
|
870
880
|
}
|
|
@@ -874,8 +884,8 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
874
884
|
}
|
|
875
885
|
validateSubject(subject);
|
|
876
886
|
const action = {
|
|
877
|
-
kind: cfg.actionKind ?? "unknown",
|
|
878
|
-
name: cfg.actionName ?? "unknown"
|
|
887
|
+
kind: evaluateStringField(cfg.actionKind, args) ?? "unknown",
|
|
888
|
+
name: evaluateStringField(cfg.actionName, args) ?? "unknown"
|
|
879
889
|
};
|
|
880
890
|
if (cfg.actionTags) {
|
|
881
891
|
action.tags = cfg.actionTags;
|
|
@@ -887,7 +897,7 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
887
897
|
action,
|
|
888
898
|
estimate: { unit, amount: estimate },
|
|
889
899
|
ttl_ms: ttlMs,
|
|
890
|
-
overage_policy: cfg.overagePolicy ?? "
|
|
900
|
+
overage_policy: cfg.overagePolicy ?? "ALLOW_IF_AVAILABLE"
|
|
891
901
|
};
|
|
892
902
|
validateGracePeriodMs(cfg.gracePeriodMs);
|
|
893
903
|
if (cfg.gracePeriodMs !== void 0) {
|
|
@@ -930,7 +940,12 @@ var AsyncCyclesLifecycle = class {
|
|
|
930
940
|
}
|
|
931
941
|
async execute(fn, args, cfg) {
|
|
932
942
|
const estimate = evaluateAmount(cfg.estimate, args);
|
|
933
|
-
const createBody = buildReservationBody(
|
|
943
|
+
const createBody = buildReservationBody(
|
|
944
|
+
cfg,
|
|
945
|
+
estimate,
|
|
946
|
+
this._defaultSubject,
|
|
947
|
+
args
|
|
948
|
+
);
|
|
934
949
|
const resResponse = await this._client.createReservation(createBody);
|
|
935
950
|
if (!resResponse.isSuccess) {
|
|
936
951
|
throw buildProtocolException("Failed to create reservation", resResponse);
|
|
@@ -1209,7 +1224,7 @@ async function reserveForStream(options) {
|
|
|
1209
1224
|
actionTags,
|
|
1210
1225
|
ttlMs = DEFAULT_TTL_MS,
|
|
1211
1226
|
gracePeriodMs,
|
|
1212
|
-
overagePolicy = "
|
|
1227
|
+
overagePolicy = "ALLOW_IF_AVAILABLE",
|
|
1213
1228
|
dimensions
|
|
1214
1229
|
} = options;
|
|
1215
1230
|
validateNonNegative(estimate, "estimate");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runcycles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "TypeScript client for the Cycles budget-management protocol",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "runcycles",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/runcycles/cycles-client-typescript.git"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://
|
|
11
|
+
"homepage": "https://runcycles.io",
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/runcycles/cycles-client-typescript/issues"
|
|
14
14
|
},
|
|
@@ -20,7 +20,14 @@
|
|
|
20
20
|
"api-client",
|
|
21
21
|
"ai",
|
|
22
22
|
"llm",
|
|
23
|
-
"runcycles"
|
|
23
|
+
"runcycles",
|
|
24
|
+
"cost-control",
|
|
25
|
+
"governance",
|
|
26
|
+
"agents",
|
|
27
|
+
"anthropic",
|
|
28
|
+
"openai",
|
|
29
|
+
"token-budget",
|
|
30
|
+
"spend-limit"
|
|
24
31
|
],
|
|
25
32
|
"type": "module",
|
|
26
33
|
"main": "./dist/index.cjs",
|
|
@@ -56,14 +63,17 @@
|
|
|
56
63
|
"prepublishOnly": "npm run lint && npm run build"
|
|
57
64
|
},
|
|
58
65
|
"devDependencies": {
|
|
59
|
-
"@types/node": "^
|
|
60
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
61
|
-
"@typescript-eslint/parser": "^8.
|
|
66
|
+
"@types/node": "^25.5.0",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
|
68
|
+
"@typescript-eslint/parser": "^8.58.0",
|
|
62
69
|
"@vitest/coverage-v8": "^4.1.0",
|
|
70
|
+
"ajv": "^8.18.0",
|
|
71
|
+
"ajv-formats": "^3.0.1",
|
|
63
72
|
"eslint": "^10.0.3",
|
|
64
73
|
"tsup": "^8.0.0",
|
|
65
|
-
"typescript": "^
|
|
66
|
-
"vite": "^
|
|
67
|
-
"vitest": "^4.1.0"
|
|
74
|
+
"typescript": "^6.0.2",
|
|
75
|
+
"vite": "^8.0.3",
|
|
76
|
+
"vitest": "^4.1.0",
|
|
77
|
+
"yaml": "^2.8.3"
|
|
68
78
|
}
|
|
69
79
|
}
|