runcycles 0.2.0 → 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 +34 -10
- package/dist/index.cjs +17 -5
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +17 -5
- package/package.json +19 -9
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
[](https://www.npmjs.com/package/runcycles)
|
|
2
|
+
[](https://www.npmjs.com/package/runcycles)
|
|
2
3
|
[](https://github.com/runcycles/cycles-client-typescript/actions)
|
|
3
4
|
[](LICENSE)
|
|
5
|
+
[](https://github.com/runcycles/cycles-client-typescript/actions)
|
|
4
6
|
|
|
5
7
|
# Cycles TypeScript Client
|
|
6
8
|
|
|
@@ -314,9 +316,9 @@ interface WithCyclesConfig {
|
|
|
314
316
|
actual?: number | ((result) => number); // Actual cost (static or computed from result)
|
|
315
317
|
useEstimateIfActualNotProvided?: boolean; // Default: true — use estimate as actual
|
|
316
318
|
|
|
317
|
-
// Action identification
|
|
318
|
-
actionKind?: string;
|
|
319
|
-
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")
|
|
320
322
|
actionTags?: string[]; // Optional tags for categorization
|
|
321
323
|
|
|
322
324
|
// Budget unit
|
|
@@ -328,13 +330,13 @@ interface WithCyclesConfig {
|
|
|
328
330
|
overagePolicy?: string; // "ALLOW_IF_AVAILABLE" (default), "REJECT", "ALLOW_WITH_OVERDRAFT"
|
|
329
331
|
dryRun?: boolean; // Shadow mode — evaluates budget without executing
|
|
330
332
|
|
|
331
|
-
// Subject fields (override config defaults)
|
|
332
|
-
tenant?: string;
|
|
333
|
-
workspace?: string;
|
|
334
|
-
app?: string;
|
|
335
|
-
workflow?: string;
|
|
336
|
-
agent?: string;
|
|
337
|
-
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);
|
|
338
340
|
dimensions?: Record<string, string>; // Custom key-value dimensions
|
|
339
341
|
|
|
340
342
|
// Client
|
|
@@ -342,6 +344,28 @@ interface WithCyclesConfig {
|
|
|
342
344
|
}
|
|
343
345
|
```
|
|
344
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
|
+
|
|
345
369
|
## Context Access
|
|
346
370
|
|
|
347
371
|
Inside a `withCycles`-guarded function, access the active reservation via `getCyclesContext()`:
|
package/dist/index.cjs
CHANGED
|
@@ -929,7 +929,13 @@ function evaluateActual(expr, result, estimate, useEstimateFallback) {
|
|
|
929
929
|
"actual expression is required when useEstimateIfActualNotProvided is false"
|
|
930
930
|
);
|
|
931
931
|
}
|
|
932
|
-
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) {
|
|
933
939
|
validateNonNegative(estimate, "estimate");
|
|
934
940
|
const ttlMs = cfg.ttlMs ?? DEFAULT_TTL_MS;
|
|
935
941
|
validateTtlMs(ttlMs);
|
|
@@ -942,7 +948,8 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
942
948
|
"agent",
|
|
943
949
|
"toolset"
|
|
944
950
|
]) {
|
|
945
|
-
const
|
|
951
|
+
const resolved = evaluateStringField(cfg[field], args);
|
|
952
|
+
const val = resolved ?? defaultSubject[field];
|
|
946
953
|
if (val) {
|
|
947
954
|
subject[field] = val;
|
|
948
955
|
}
|
|
@@ -952,8 +959,8 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
952
959
|
}
|
|
953
960
|
validateSubject(subject);
|
|
954
961
|
const action = {
|
|
955
|
-
kind: cfg.actionKind ?? "unknown",
|
|
956
|
-
name: cfg.actionName ?? "unknown"
|
|
962
|
+
kind: evaluateStringField(cfg.actionKind, args) ?? "unknown",
|
|
963
|
+
name: evaluateStringField(cfg.actionName, args) ?? "unknown"
|
|
957
964
|
};
|
|
958
965
|
if (cfg.actionTags) {
|
|
959
966
|
action.tags = cfg.actionTags;
|
|
@@ -1008,7 +1015,12 @@ var AsyncCyclesLifecycle = class {
|
|
|
1008
1015
|
}
|
|
1009
1016
|
async execute(fn, args, cfg) {
|
|
1010
1017
|
const estimate = evaluateAmount(cfg.estimate, args);
|
|
1011
|
-
const createBody = buildReservationBody(
|
|
1018
|
+
const createBody = buildReservationBody(
|
|
1019
|
+
cfg,
|
|
1020
|
+
estimate,
|
|
1021
|
+
this._defaultSubject,
|
|
1022
|
+
args
|
|
1023
|
+
);
|
|
1012
1024
|
const resResponse = await this._client.createReservation(createBody);
|
|
1013
1025
|
if (!resResponse.isSuccess) {
|
|
1014
1026
|
throw buildProtocolException("Failed to create reservation", resResponse);
|
package/dist/index.d.cts
CHANGED
|
@@ -342,20 +342,20 @@ declare class CyclesClient {
|
|
|
342
342
|
interface WithCyclesConfig<TArgs extends unknown[] = unknown[], TResult = unknown> {
|
|
343
343
|
estimate: number | ((...args: TArgs) => number);
|
|
344
344
|
actual?: number | ((result: TResult) => number);
|
|
345
|
-
actionKind?: string;
|
|
346
|
-
actionName?: string;
|
|
345
|
+
actionKind?: string | ((...args: TArgs) => string | undefined);
|
|
346
|
+
actionName?: string | ((...args: TArgs) => string | undefined);
|
|
347
347
|
actionTags?: string[];
|
|
348
348
|
unit?: string;
|
|
349
349
|
ttlMs?: number;
|
|
350
350
|
gracePeriodMs?: number;
|
|
351
351
|
overagePolicy?: string;
|
|
352
352
|
dryRun?: boolean;
|
|
353
|
-
tenant?: string;
|
|
354
|
-
workspace?: string;
|
|
355
|
-
app?: string;
|
|
356
|
-
workflow?: string;
|
|
357
|
-
agent?: string;
|
|
358
|
-
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);
|
|
359
359
|
dimensions?: Record<string, string>;
|
|
360
360
|
useEstimateIfActualNotProvided?: boolean;
|
|
361
361
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -342,20 +342,20 @@ declare class CyclesClient {
|
|
|
342
342
|
interface WithCyclesConfig<TArgs extends unknown[] = unknown[], TResult = unknown> {
|
|
343
343
|
estimate: number | ((...args: TArgs) => number);
|
|
344
344
|
actual?: number | ((result: TResult) => number);
|
|
345
|
-
actionKind?: string;
|
|
346
|
-
actionName?: string;
|
|
345
|
+
actionKind?: string | ((...args: TArgs) => string | undefined);
|
|
346
|
+
actionName?: string | ((...args: TArgs) => string | undefined);
|
|
347
347
|
actionTags?: string[];
|
|
348
348
|
unit?: string;
|
|
349
349
|
ttlMs?: number;
|
|
350
350
|
gracePeriodMs?: number;
|
|
351
351
|
overagePolicy?: string;
|
|
352
352
|
dryRun?: boolean;
|
|
353
|
-
tenant?: string;
|
|
354
|
-
workspace?: string;
|
|
355
|
-
app?: string;
|
|
356
|
-
workflow?: string;
|
|
357
|
-
agent?: string;
|
|
358
|
-
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);
|
|
359
359
|
dimensions?: Record<string, string>;
|
|
360
360
|
useEstimateIfActualNotProvided?: boolean;
|
|
361
361
|
}
|
package/dist/index.js
CHANGED
|
@@ -854,7 +854,13 @@ function evaluateActual(expr, result, estimate, useEstimateFallback) {
|
|
|
854
854
|
"actual expression is required when useEstimateIfActualNotProvided is false"
|
|
855
855
|
);
|
|
856
856
|
}
|
|
857
|
-
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) {
|
|
858
864
|
validateNonNegative(estimate, "estimate");
|
|
859
865
|
const ttlMs = cfg.ttlMs ?? DEFAULT_TTL_MS;
|
|
860
866
|
validateTtlMs(ttlMs);
|
|
@@ -867,7 +873,8 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
867
873
|
"agent",
|
|
868
874
|
"toolset"
|
|
869
875
|
]) {
|
|
870
|
-
const
|
|
876
|
+
const resolved = evaluateStringField(cfg[field], args);
|
|
877
|
+
const val = resolved ?? defaultSubject[field];
|
|
871
878
|
if (val) {
|
|
872
879
|
subject[field] = val;
|
|
873
880
|
}
|
|
@@ -877,8 +884,8 @@ function buildReservationBody(cfg, estimate, defaultSubject) {
|
|
|
877
884
|
}
|
|
878
885
|
validateSubject(subject);
|
|
879
886
|
const action = {
|
|
880
|
-
kind: cfg.actionKind ?? "unknown",
|
|
881
|
-
name: cfg.actionName ?? "unknown"
|
|
887
|
+
kind: evaluateStringField(cfg.actionKind, args) ?? "unknown",
|
|
888
|
+
name: evaluateStringField(cfg.actionName, args) ?? "unknown"
|
|
882
889
|
};
|
|
883
890
|
if (cfg.actionTags) {
|
|
884
891
|
action.tags = cfg.actionTags;
|
|
@@ -933,7 +940,12 @@ var AsyncCyclesLifecycle = class {
|
|
|
933
940
|
}
|
|
934
941
|
async execute(fn, args, cfg) {
|
|
935
942
|
const estimate = evaluateAmount(cfg.estimate, args);
|
|
936
|
-
const createBody = buildReservationBody(
|
|
943
|
+
const createBody = buildReservationBody(
|
|
944
|
+
cfg,
|
|
945
|
+
estimate,
|
|
946
|
+
this._defaultSubject,
|
|
947
|
+
args
|
|
948
|
+
);
|
|
937
949
|
const resResponse = await this._client.createReservation(createBody);
|
|
938
950
|
if (!resResponse.isSuccess) {
|
|
939
951
|
throw buildProtocolException("Failed to create reservation", resResponse);
|
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
|
}
|