runcycles 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1450 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BudgetExceededError: () => BudgetExceededError,
24
+ CommitOveragePolicy: () => CommitOveragePolicy,
25
+ CommitStatus: () => CommitStatus,
26
+ CyclesClient: () => CyclesClient,
27
+ CyclesConfig: () => CyclesConfig,
28
+ CyclesError: () => CyclesError,
29
+ CyclesProtocolError: () => CyclesProtocolError,
30
+ CyclesResponse: () => CyclesResponse,
31
+ CyclesTransportError: () => CyclesTransportError,
32
+ DebtOutstandingError: () => DebtOutstandingError,
33
+ Decision: () => Decision,
34
+ ErrorCode: () => ErrorCode,
35
+ EventStatus: () => EventStatus,
36
+ ExtendStatus: () => ExtendStatus,
37
+ OverdraftLimitExceededError: () => OverdraftLimitExceededError,
38
+ ReleaseStatus: () => ReleaseStatus,
39
+ ReservationExpiredError: () => ReservationExpiredError,
40
+ ReservationFinalizedError: () => ReservationFinalizedError,
41
+ ReservationStatus: () => ReservationStatus,
42
+ Unit: () => Unit,
43
+ balanceResponseFromWire: () => balanceResponseFromWire,
44
+ capsFromWire: () => capsFromWire,
45
+ commitRequestToWire: () => commitRequestToWire,
46
+ commitResponseFromWire: () => commitResponseFromWire,
47
+ decisionRequestToWire: () => decisionRequestToWire,
48
+ decisionResponseFromWire: () => decisionResponseFromWire,
49
+ errorCodeFromString: () => errorCodeFromString,
50
+ errorResponseFromWire: () => errorResponseFromWire,
51
+ eventCreateRequestToWire: () => eventCreateRequestToWire,
52
+ eventCreateResponseFromWire: () => eventCreateResponseFromWire,
53
+ getCyclesContext: () => getCyclesContext,
54
+ isAllowed: () => isAllowed,
55
+ isDenied: () => isDenied,
56
+ isMetricsEmpty: () => isMetricsEmpty2,
57
+ isRetryableErrorCode: () => isRetryableErrorCode,
58
+ isToolAllowed: () => isToolAllowed,
59
+ metricsToWire: () => metricsToWire,
60
+ releaseRequestToWire: () => releaseRequestToWire,
61
+ releaseResponseFromWire: () => releaseResponseFromWire,
62
+ reservationCreateRequestToWire: () => reservationCreateRequestToWire,
63
+ reservationCreateResponseFromWire: () => reservationCreateResponseFromWire,
64
+ reservationDetailFromWire: () => reservationDetailFromWire,
65
+ reservationExtendRequestToWire: () => reservationExtendRequestToWire,
66
+ reservationExtendResponseFromWire: () => reservationExtendResponseFromWire,
67
+ reservationListResponseFromWire: () => reservationListResponseFromWire,
68
+ reservationSummaryFromWire: () => reservationSummaryFromWire,
69
+ reserveForStream: () => reserveForStream,
70
+ setDefaultClient: () => setDefaultClient,
71
+ setDefaultConfig: () => setDefaultConfig,
72
+ withCycles: () => withCycles
73
+ });
74
+ module.exports = __toCommonJS(index_exports);
75
+
76
+ // src/constants.ts
77
+ var API_KEY_HEADER = "X-Cycles-API-Key";
78
+ var IDEMPOTENCY_KEY_HEADER = "X-Idempotency-Key";
79
+ var RESERVATIONS_PATH = "/v1/reservations";
80
+ var DECIDE_PATH = "/v1/decide";
81
+ var BALANCES_PATH = "/v1/balances";
82
+ var EVENTS_PATH = "/v1/events";
83
+
84
+ // src/mappers.ts
85
+ function stripUndefined(obj) {
86
+ const result = {};
87
+ for (const [key, value] of Object.entries(obj)) {
88
+ if (value !== void 0) {
89
+ result[key] = value;
90
+ }
91
+ }
92
+ return result;
93
+ }
94
+ function metricsToWire(metrics) {
95
+ return stripUndefined({
96
+ tokens_input: metrics.tokensInput,
97
+ tokens_output: metrics.tokensOutput,
98
+ latency_ms: metrics.latencyMs,
99
+ model_version: metrics.modelVersion,
100
+ custom: metrics.custom
101
+ });
102
+ }
103
+ function capsFromWire(wire) {
104
+ if (!wire) return void 0;
105
+ return stripUndefined({
106
+ maxTokens: wire.max_tokens,
107
+ maxStepsRemaining: wire.max_steps_remaining,
108
+ toolAllowlist: wire.tool_allowlist,
109
+ toolDenylist: wire.tool_denylist,
110
+ cooldownMs: wire.cooldown_ms
111
+ });
112
+ }
113
+ function amountFromWire(wire) {
114
+ if (!wire) return void 0;
115
+ return { unit: wire.unit, amount: wire.amount };
116
+ }
117
+ function signedAmountFromWire(wire) {
118
+ if (!wire) return void 0;
119
+ return { unit: wire.unit, amount: wire.amount };
120
+ }
121
+ function balanceFromWire(wire) {
122
+ return {
123
+ scope: wire.scope,
124
+ scopePath: wire.scope_path,
125
+ remaining: signedAmountFromWire(wire.remaining),
126
+ reserved: amountFromWire(wire.reserved),
127
+ spent: amountFromWire(wire.spent),
128
+ allocated: amountFromWire(wire.allocated),
129
+ debt: amountFromWire(wire.debt),
130
+ overdraftLimit: amountFromWire(wire.overdraft_limit),
131
+ isOverLimit: wire.is_over_limit
132
+ };
133
+ }
134
+ function balancesFromWire(wire) {
135
+ if (!wire) return void 0;
136
+ return wire.map((b) => balanceFromWire(b));
137
+ }
138
+ function reservationCreateResponseFromWire(wire) {
139
+ return {
140
+ decision: wire.decision,
141
+ reservationId: wire.reservation_id,
142
+ affectedScopes: wire.affected_scopes ?? [],
143
+ expiresAtMs: wire.expires_at_ms,
144
+ scopePath: wire.scope_path,
145
+ reserved: amountFromWire(wire.reserved),
146
+ caps: capsFromWire(wire.caps),
147
+ reasonCode: wire.reason_code,
148
+ retryAfterMs: wire.retry_after_ms,
149
+ balances: balancesFromWire(wire.balances)
150
+ };
151
+ }
152
+ function commitResponseFromWire(wire) {
153
+ return {
154
+ status: wire.status,
155
+ charged: amountFromWire(wire.charged),
156
+ released: amountFromWire(wire.released),
157
+ balances: balancesFromWire(wire.balances)
158
+ };
159
+ }
160
+ function releaseResponseFromWire(wire) {
161
+ return {
162
+ status: wire.status,
163
+ released: amountFromWire(wire.released),
164
+ balances: balancesFromWire(wire.balances)
165
+ };
166
+ }
167
+ function reservationExtendResponseFromWire(wire) {
168
+ return {
169
+ status: wire.status,
170
+ expiresAtMs: wire.expires_at_ms,
171
+ balances: balancesFromWire(wire.balances)
172
+ };
173
+ }
174
+ function decisionResponseFromWire(wire) {
175
+ return stripUndefined({
176
+ decision: wire.decision,
177
+ caps: capsFromWire(wire.caps),
178
+ reasonCode: wire.reason_code,
179
+ retryAfterMs: wire.retry_after_ms,
180
+ affectedScopes: wire.affected_scopes
181
+ });
182
+ }
183
+ function eventCreateResponseFromWire(wire) {
184
+ return {
185
+ status: wire.status,
186
+ eventId: wire.event_id,
187
+ balances: balancesFromWire(wire.balances)
188
+ };
189
+ }
190
+ function subjectFromWire(wire) {
191
+ const result = {};
192
+ if (wire.tenant !== void 0) result.tenant = wire.tenant;
193
+ if (wire.workspace !== void 0) result.workspace = wire.workspace;
194
+ if (wire.app !== void 0) result.app = wire.app;
195
+ if (wire.workflow !== void 0) result.workflow = wire.workflow;
196
+ if (wire.agent !== void 0) result.agent = wire.agent;
197
+ if (wire.toolset !== void 0) result.toolset = wire.toolset;
198
+ if (wire.dimensions !== void 0) result.dimensions = wire.dimensions;
199
+ return result;
200
+ }
201
+ function actionFromWire(wire) {
202
+ const result = {
203
+ kind: wire.kind,
204
+ name: wire.name
205
+ };
206
+ if (wire.tags !== void 0) result.tags = wire.tags;
207
+ return result;
208
+ }
209
+ function reservationDetailFromWire(wire) {
210
+ return {
211
+ reservationId: wire.reservation_id,
212
+ status: wire.status,
213
+ subject: subjectFromWire(wire.subject),
214
+ action: actionFromWire(wire.action),
215
+ reserved: amountFromWire(wire.reserved),
216
+ createdAtMs: wire.created_at_ms,
217
+ expiresAtMs: wire.expires_at_ms,
218
+ scopePath: wire.scope_path,
219
+ affectedScopes: wire.affected_scopes,
220
+ idempotencyKey: wire.idempotency_key,
221
+ committed: amountFromWire(wire.committed),
222
+ finalizedAtMs: wire.finalized_at_ms,
223
+ metadata: wire.metadata
224
+ };
225
+ }
226
+ function reservationSummaryFromWire(wire) {
227
+ return {
228
+ reservationId: wire.reservation_id,
229
+ status: wire.status,
230
+ subject: subjectFromWire(wire.subject),
231
+ action: actionFromWire(wire.action),
232
+ reserved: amountFromWire(wire.reserved),
233
+ createdAtMs: wire.created_at_ms,
234
+ expiresAtMs: wire.expires_at_ms,
235
+ scopePath: wire.scope_path,
236
+ affectedScopes: wire.affected_scopes,
237
+ idempotencyKey: wire.idempotency_key
238
+ };
239
+ }
240
+ function reservationListResponseFromWire(wire) {
241
+ const reservations = wire.reservations.map(
242
+ (r) => reservationSummaryFromWire(r)
243
+ );
244
+ return {
245
+ reservations,
246
+ nextCursor: wire.next_cursor,
247
+ hasMore: wire.has_more
248
+ };
249
+ }
250
+ function balanceResponseFromWire(wire) {
251
+ return {
252
+ balances: balancesFromWire(wire.balances) ?? [],
253
+ nextCursor: wire.next_cursor,
254
+ hasMore: wire.has_more
255
+ };
256
+ }
257
+ function errorResponseFromWire(wire) {
258
+ if (typeof wire.error !== "string" || typeof wire.message !== "string" || typeof wire.request_id !== "string") {
259
+ return void 0;
260
+ }
261
+ return {
262
+ error: wire.error,
263
+ message: wire.message,
264
+ requestId: wire.request_id,
265
+ details: wire.details
266
+ };
267
+ }
268
+ function actionToWire(action) {
269
+ return stripUndefined({
270
+ kind: action.kind,
271
+ name: action.name,
272
+ tags: action.tags
273
+ });
274
+ }
275
+ function subjectToWire(subject) {
276
+ return stripUndefined({
277
+ tenant: subject.tenant,
278
+ workspace: subject.workspace,
279
+ app: subject.app,
280
+ workflow: subject.workflow,
281
+ agent: subject.agent,
282
+ toolset: subject.toolset,
283
+ dimensions: subject.dimensions
284
+ });
285
+ }
286
+ function reservationCreateRequestToWire(req) {
287
+ return stripUndefined({
288
+ idempotency_key: req.idempotencyKey,
289
+ subject: subjectToWire(req.subject),
290
+ action: actionToWire(req.action),
291
+ estimate: req.estimate,
292
+ ttl_ms: req.ttlMs,
293
+ grace_period_ms: req.gracePeriodMs,
294
+ overage_policy: req.overagePolicy,
295
+ dry_run: req.dryRun,
296
+ metadata: req.metadata
297
+ });
298
+ }
299
+ function commitRequestToWire(req) {
300
+ return stripUndefined({
301
+ idempotency_key: req.idempotencyKey,
302
+ actual: req.actual,
303
+ metrics: req.metrics ? metricsToWire(req.metrics) : void 0,
304
+ metadata: req.metadata
305
+ });
306
+ }
307
+ function releaseRequestToWire(req) {
308
+ return stripUndefined({
309
+ idempotency_key: req.idempotencyKey,
310
+ reason: req.reason
311
+ });
312
+ }
313
+ function reservationExtendRequestToWire(req) {
314
+ return stripUndefined({
315
+ idempotency_key: req.idempotencyKey,
316
+ extend_by_ms: req.extendByMs,
317
+ metadata: req.metadata
318
+ });
319
+ }
320
+ function decisionRequestToWire(req) {
321
+ return stripUndefined({
322
+ idempotency_key: req.idempotencyKey,
323
+ subject: subjectToWire(req.subject),
324
+ action: actionToWire(req.action),
325
+ estimate: req.estimate,
326
+ metadata: req.metadata
327
+ });
328
+ }
329
+ function eventCreateRequestToWire(req) {
330
+ return stripUndefined({
331
+ idempotency_key: req.idempotencyKey,
332
+ subject: subjectToWire(req.subject),
333
+ action: actionToWire(req.action),
334
+ actual: req.actual,
335
+ overage_policy: req.overagePolicy,
336
+ metrics: req.metrics ? metricsToWire(req.metrics) : void 0,
337
+ client_time_ms: req.clientTimeMs,
338
+ metadata: req.metadata
339
+ });
340
+ }
341
+
342
+ // src/response.ts
343
+ var CyclesResponse = class _CyclesResponse {
344
+ status;
345
+ body;
346
+ errorMessage;
347
+ headers;
348
+ _isTransportError;
349
+ transportError;
350
+ constructor(options) {
351
+ this.status = options.status;
352
+ this.body = options.body;
353
+ this.errorMessage = options.errorMessage;
354
+ this.headers = options.headers ?? {};
355
+ this._isTransportError = options.isTransportError ?? false;
356
+ this.transportError = options.transportError;
357
+ }
358
+ static success(status, body, headers) {
359
+ return new _CyclesResponse({ status, body, headers });
360
+ }
361
+ static httpError(status, errorMessage, body, headers) {
362
+ return new _CyclesResponse({ status, body, errorMessage, headers });
363
+ }
364
+ static transportError(err) {
365
+ return new _CyclesResponse({
366
+ status: -1,
367
+ errorMessage: err.message,
368
+ isTransportError: true,
369
+ transportError: err
370
+ });
371
+ }
372
+ get requestId() {
373
+ return this.headers["x-request-id"];
374
+ }
375
+ get rateLimitRemaining() {
376
+ const val = this.headers["x-ratelimit-remaining"];
377
+ return val !== void 0 ? parseInt(val, 10) : void 0;
378
+ }
379
+ get rateLimitReset() {
380
+ const val = this.headers["x-ratelimit-reset"];
381
+ return val !== void 0 ? parseInt(val, 10) : void 0;
382
+ }
383
+ get cyclesTenant() {
384
+ return this.headers["x-cycles-tenant"];
385
+ }
386
+ get isSuccess() {
387
+ return this.status >= 200 && this.status < 300;
388
+ }
389
+ get isClientError() {
390
+ return this.status >= 400 && this.status < 500;
391
+ }
392
+ get isServerError() {
393
+ return this.status >= 500 && this.status < 600;
394
+ }
395
+ get isTransportError() {
396
+ return this._isTransportError;
397
+ }
398
+ getBodyAttribute(key) {
399
+ if (this.body && key in this.body) {
400
+ return this.body[key];
401
+ }
402
+ return void 0;
403
+ }
404
+ getErrorResponse() {
405
+ if (this.body && typeof this.body === "object") {
406
+ return errorResponseFromWire(this.body);
407
+ }
408
+ return void 0;
409
+ }
410
+ };
411
+
412
+ // src/client.ts
413
+ var RESPONSE_HEADERS = [
414
+ "x-request-id",
415
+ "x-ratelimit-remaining",
416
+ "x-ratelimit-reset",
417
+ "x-cycles-tenant"
418
+ ];
419
+ var BALANCE_FILTER_PARAMS = /* @__PURE__ */ new Set([
420
+ "tenant",
421
+ "workspace",
422
+ "app",
423
+ "workflow",
424
+ "agent",
425
+ "toolset"
426
+ ]);
427
+ function extractResponseHeaders(resp) {
428
+ const result = {};
429
+ for (const name of RESPONSE_HEADERS) {
430
+ const val = resp.headers.get(name);
431
+ if (val !== null) {
432
+ result[name] = val;
433
+ }
434
+ }
435
+ return result;
436
+ }
437
+ var CyclesClient = class {
438
+ _config;
439
+ constructor(config) {
440
+ this._config = config;
441
+ }
442
+ get config() {
443
+ return this._config;
444
+ }
445
+ async createReservation(request) {
446
+ return this._post(RESERVATIONS_PATH, request);
447
+ }
448
+ async commitReservation(reservationId, request) {
449
+ return this._post(
450
+ `${RESERVATIONS_PATH}/${reservationId}/commit`,
451
+ request
452
+ );
453
+ }
454
+ async releaseReservation(reservationId, request) {
455
+ return this._post(
456
+ `${RESERVATIONS_PATH}/${reservationId}/release`,
457
+ request
458
+ );
459
+ }
460
+ async extendReservation(reservationId, request) {
461
+ return this._post(
462
+ `${RESERVATIONS_PATH}/${reservationId}/extend`,
463
+ request
464
+ );
465
+ }
466
+ async decide(request) {
467
+ return this._post(DECIDE_PATH, request);
468
+ }
469
+ async listReservations(params) {
470
+ return this._get(RESERVATIONS_PATH, params);
471
+ }
472
+ async getReservation(reservationId) {
473
+ return this._get(`${RESERVATIONS_PATH}/${reservationId}`);
474
+ }
475
+ async getBalances(params) {
476
+ const hasFilter = Object.keys(params).some(
477
+ (k) => BALANCE_FILTER_PARAMS.has(k)
478
+ );
479
+ if (!hasFilter) {
480
+ throw new Error(
481
+ "getBalances requires at least one subject filter (tenant, workspace, app, workflow, agent, or toolset)"
482
+ );
483
+ }
484
+ return this._get(BALANCES_PATH, params);
485
+ }
486
+ async createEvent(request) {
487
+ return this._post(EVENTS_PATH, request);
488
+ }
489
+ async _post(path, body) {
490
+ try {
491
+ const headers = {
492
+ "Content-Type": "application/json",
493
+ [API_KEY_HEADER]: this._config.apiKey
494
+ };
495
+ const idemKey = body.idempotency_key;
496
+ if (typeof idemKey === "string") {
497
+ headers[IDEMPOTENCY_KEY_HEADER] = idemKey;
498
+ }
499
+ const url = `${this._config.baseUrl}${path}`;
500
+ const resp = await fetch(url, {
501
+ method: "POST",
502
+ headers,
503
+ body: JSON.stringify(body),
504
+ signal: AbortSignal.timeout(
505
+ this._config.connectTimeout + this._config.readTimeout
506
+ )
507
+ });
508
+ return this._handleResponse(resp);
509
+ } catch (err) {
510
+ return CyclesResponse.transportError(
511
+ err instanceof Error ? err : new Error(String(err))
512
+ );
513
+ }
514
+ }
515
+ async _get(path, params) {
516
+ try {
517
+ let url = `${this._config.baseUrl}${path}`;
518
+ if (params && Object.keys(params).length > 0) {
519
+ const qs = new URLSearchParams(params).toString();
520
+ url = `${url}?${qs}`;
521
+ }
522
+ const resp = await fetch(url, {
523
+ method: "GET",
524
+ headers: {
525
+ [API_KEY_HEADER]: this._config.apiKey
526
+ },
527
+ signal: AbortSignal.timeout(
528
+ this._config.connectTimeout + this._config.readTimeout
529
+ )
530
+ });
531
+ return this._handleResponse(resp);
532
+ } catch (err) {
533
+ return CyclesResponse.transportError(
534
+ err instanceof Error ? err : new Error(String(err))
535
+ );
536
+ }
537
+ }
538
+ async _handleResponse(resp) {
539
+ let body;
540
+ try {
541
+ body = await resp.json();
542
+ } catch {
543
+ body = void 0;
544
+ }
545
+ const headers = extractResponseHeaders(resp);
546
+ if (resp.status >= 200 && resp.status < 300) {
547
+ return CyclesResponse.success(resp.status, body ?? {}, headers);
548
+ }
549
+ let errorMsg;
550
+ if (body && typeof body === "object") {
551
+ errorMsg = body.message ?? body.error ?? void 0;
552
+ }
553
+ return CyclesResponse.httpError(
554
+ resp.status,
555
+ errorMsg ?? resp.statusText ?? "Unknown error",
556
+ body,
557
+ headers
558
+ );
559
+ }
560
+ };
561
+
562
+ // src/config.ts
563
+ var CyclesConfig = class _CyclesConfig {
564
+ baseUrl;
565
+ apiKey;
566
+ tenant;
567
+ workspace;
568
+ app;
569
+ workflow;
570
+ agent;
571
+ toolset;
572
+ connectTimeout;
573
+ readTimeout;
574
+ retryEnabled;
575
+ retryMaxAttempts;
576
+ retryInitialDelay;
577
+ retryMultiplier;
578
+ retryMaxDelay;
579
+ constructor(options) {
580
+ this.baseUrl = options.baseUrl;
581
+ this.apiKey = options.apiKey;
582
+ this.tenant = options.tenant;
583
+ this.workspace = options.workspace;
584
+ this.app = options.app;
585
+ this.workflow = options.workflow;
586
+ this.agent = options.agent;
587
+ this.toolset = options.toolset;
588
+ this.connectTimeout = options.connectTimeout ?? 2e3;
589
+ this.readTimeout = options.readTimeout ?? 5e3;
590
+ this.retryEnabled = options.retryEnabled ?? true;
591
+ this.retryMaxAttempts = options.retryMaxAttempts ?? 5;
592
+ this.retryInitialDelay = options.retryInitialDelay ?? 500;
593
+ this.retryMultiplier = options.retryMultiplier ?? 2;
594
+ this.retryMaxDelay = options.retryMaxDelay ?? 3e4;
595
+ }
596
+ static fromEnv(prefix = "CYCLES_") {
597
+ const baseUrl = process.env[`${prefix}BASE_URL`];
598
+ const apiKey = process.env[`${prefix}API_KEY`];
599
+ if (!baseUrl) {
600
+ throw new Error(`${prefix}BASE_URL environment variable is required`);
601
+ }
602
+ if (!apiKey) {
603
+ throw new Error(`${prefix}API_KEY environment variable is required`);
604
+ }
605
+ return new _CyclesConfig({
606
+ baseUrl,
607
+ apiKey,
608
+ tenant: process.env[`${prefix}TENANT`],
609
+ workspace: process.env[`${prefix}WORKSPACE`],
610
+ app: process.env[`${prefix}APP`],
611
+ workflow: process.env[`${prefix}WORKFLOW`],
612
+ agent: process.env[`${prefix}AGENT`],
613
+ toolset: process.env[`${prefix}TOOLSET`],
614
+ connectTimeout: optionalFloat(process.env[`${prefix}CONNECT_TIMEOUT`], 2e3),
615
+ readTimeout: optionalFloat(process.env[`${prefix}READ_TIMEOUT`], 5e3),
616
+ retryEnabled: process.env[`${prefix}RETRY_ENABLED`]?.toLowerCase() !== "false",
617
+ retryMaxAttempts: optionalInt(process.env[`${prefix}RETRY_MAX_ATTEMPTS`], 5),
618
+ retryInitialDelay: optionalFloat(process.env[`${prefix}RETRY_INITIAL_DELAY`], 500),
619
+ retryMultiplier: optionalFloat(process.env[`${prefix}RETRY_MULTIPLIER`], 2),
620
+ retryMaxDelay: optionalFloat(process.env[`${prefix}RETRY_MAX_DELAY`], 3e4)
621
+ });
622
+ }
623
+ };
624
+ function optionalInt(val, fallback) {
625
+ return val !== void 0 ? parseInt(val, 10) : fallback;
626
+ }
627
+ function optionalFloat(val, fallback) {
628
+ return val !== void 0 ? parseFloat(val) : fallback;
629
+ }
630
+
631
+ // src/lifecycle.ts
632
+ var import_node_crypto = require("crypto");
633
+
634
+ // src/context.ts
635
+ var import_node_async_hooks = require("async_hooks");
636
+ var storage = new import_node_async_hooks.AsyncLocalStorage();
637
+ function getCyclesContext() {
638
+ return storage.getStore();
639
+ }
640
+ function runWithContext(ctx, fn) {
641
+ return storage.run(ctx, fn);
642
+ }
643
+
644
+ // src/exceptions.ts
645
+ var CyclesError = class extends Error {
646
+ constructor(message) {
647
+ super(message);
648
+ this.name = "CyclesError";
649
+ }
650
+ };
651
+ var CyclesProtocolError = class extends CyclesError {
652
+ status;
653
+ errorCode;
654
+ reasonCode;
655
+ retryAfterMs;
656
+ requestId;
657
+ details;
658
+ constructor(message, options = {}) {
659
+ super(message);
660
+ this.name = "CyclesProtocolError";
661
+ this.status = options.status ?? 0;
662
+ this.errorCode = options.errorCode;
663
+ this.reasonCode = options.reasonCode;
664
+ this.retryAfterMs = options.retryAfterMs;
665
+ this.requestId = options.requestId;
666
+ this.details = options.details;
667
+ }
668
+ isBudgetExceeded() {
669
+ return this.errorCode === "BUDGET_EXCEEDED";
670
+ }
671
+ isOverdraftLimitExceeded() {
672
+ return this.errorCode === "OVERDRAFT_LIMIT_EXCEEDED";
673
+ }
674
+ isDebtOutstanding() {
675
+ return this.errorCode === "DEBT_OUTSTANDING";
676
+ }
677
+ isReservationExpired() {
678
+ return this.errorCode === "RESERVATION_EXPIRED";
679
+ }
680
+ isReservationFinalized() {
681
+ return this.errorCode === "RESERVATION_FINALIZED";
682
+ }
683
+ isIdempotencyMismatch() {
684
+ return this.errorCode === "IDEMPOTENCY_MISMATCH";
685
+ }
686
+ isUnitMismatch() {
687
+ return this.errorCode === "UNIT_MISMATCH";
688
+ }
689
+ isRetryable() {
690
+ return this.errorCode === "INTERNAL_ERROR" || this.errorCode === "UNKNOWN" || this.status >= 500;
691
+ }
692
+ };
693
+ var BudgetExceededError = class extends CyclesProtocolError {
694
+ constructor(message, options = {}) {
695
+ super(message, options);
696
+ this.name = "BudgetExceededError";
697
+ }
698
+ };
699
+ var OverdraftLimitExceededError = class extends CyclesProtocolError {
700
+ constructor(message, options = {}) {
701
+ super(message, options);
702
+ this.name = "OverdraftLimitExceededError";
703
+ }
704
+ };
705
+ var DebtOutstandingError = class extends CyclesProtocolError {
706
+ constructor(message, options = {}) {
707
+ super(message, options);
708
+ this.name = "DebtOutstandingError";
709
+ }
710
+ };
711
+ var ReservationExpiredError = class extends CyclesProtocolError {
712
+ constructor(message, options = {}) {
713
+ super(message, options);
714
+ this.name = "ReservationExpiredError";
715
+ }
716
+ };
717
+ var ReservationFinalizedError = class extends CyclesProtocolError {
718
+ constructor(message, options = {}) {
719
+ super(message, options);
720
+ this.name = "ReservationFinalizedError";
721
+ }
722
+ };
723
+ var CyclesTransportError = class extends CyclesError {
724
+ cause;
725
+ constructor(message, options) {
726
+ super(message);
727
+ this.name = "CyclesTransportError";
728
+ this.cause = options?.cause;
729
+ }
730
+ };
731
+
732
+ // src/errors.ts
733
+ function buildProtocolException(prefix, response) {
734
+ const errorResp = response.getErrorResponse();
735
+ let errorCode;
736
+ let reasonCode;
737
+ let message = prefix;
738
+ let requestId;
739
+ let details;
740
+ if (errorResp) {
741
+ errorCode = errorResp.error;
742
+ requestId = errorResp.requestId;
743
+ details = errorResp.details;
744
+ if (errorResp.message) {
745
+ message = `${prefix}: ${errorResp.message}`;
746
+ }
747
+ } else {
748
+ const rawError = response.getBodyAttribute("error");
749
+ if (typeof rawError === "string") {
750
+ errorCode = rawError;
751
+ }
752
+ if (response.errorMessage) {
753
+ message = `${prefix}: ${response.errorMessage}`;
754
+ }
755
+ }
756
+ reasonCode = response.getBodyAttribute("reason_code");
757
+ if (reasonCode === void 0 && errorCode !== void 0) {
758
+ reasonCode = errorCode;
759
+ }
760
+ const retryRaw = response.getBodyAttribute("retry_after_ms");
761
+ const retryAfterMs = retryRaw !== void 0 ? Number(retryRaw) : void 0;
762
+ const opts = {
763
+ status: response.status,
764
+ errorCode,
765
+ reasonCode,
766
+ retryAfterMs,
767
+ requestId,
768
+ details
769
+ };
770
+ switch (errorCode) {
771
+ case "BUDGET_EXCEEDED":
772
+ return new BudgetExceededError(message, opts);
773
+ case "OVERDRAFT_LIMIT_EXCEEDED":
774
+ return new OverdraftLimitExceededError(message, opts);
775
+ case "DEBT_OUTSTANDING":
776
+ return new DebtOutstandingError(message, opts);
777
+ case "RESERVATION_EXPIRED":
778
+ return new ReservationExpiredError(message, opts);
779
+ case "RESERVATION_FINALIZED":
780
+ return new ReservationFinalizedError(message, opts);
781
+ default:
782
+ return new CyclesProtocolError(message, opts);
783
+ }
784
+ }
785
+
786
+ // src/validation.ts
787
+ function validateSubject(subject) {
788
+ if (subject === void 0) return;
789
+ const hasField = !!(subject.tenant || subject.workspace || subject.app || subject.workflow || subject.agent || subject.toolset);
790
+ if (!hasField) {
791
+ throw new Error(
792
+ "Subject must have at least one standard field (tenant, workspace, app, workflow, agent, or toolset)"
793
+ );
794
+ }
795
+ }
796
+ function validateNonNegative(value, name) {
797
+ if (value < 0) {
798
+ throw new Error(`${name} must be non-negative, got ${value}`);
799
+ }
800
+ }
801
+ function validateTtlMs(ttlMs) {
802
+ if (ttlMs < 1e3 || ttlMs > 864e5) {
803
+ throw new Error(`ttl_ms must be between 1000 and 86400000, got ${ttlMs}`);
804
+ }
805
+ }
806
+ function validateGracePeriodMs(gracePeriodMs) {
807
+ if (gracePeriodMs !== void 0 && (gracePeriodMs < 0 || gracePeriodMs > 6e4)) {
808
+ throw new Error(`grace_period_ms must be between 0 and 60000, got ${gracePeriodMs}`);
809
+ }
810
+ }
811
+ function validateExtendByMs(extendByMs) {
812
+ if (extendByMs < 1 || extendByMs > 864e5) {
813
+ throw new Error(`extend_by_ms must be between 1 and 86400000, got ${extendByMs}`);
814
+ }
815
+ }
816
+
817
+ // src/lifecycle.ts
818
+ function evaluateAmount(expr, args) {
819
+ if (typeof expr === "function") {
820
+ return expr(...args);
821
+ }
822
+ return expr;
823
+ }
824
+ function evaluateActual(expr, result, estimate, useEstimateFallback) {
825
+ if (expr !== void 0) {
826
+ if (typeof expr === "function") {
827
+ return expr(result);
828
+ }
829
+ return expr;
830
+ }
831
+ if (useEstimateFallback) {
832
+ return estimate;
833
+ }
834
+ throw new Error(
835
+ "actual expression is required when useEstimateIfActualNotProvided is false"
836
+ );
837
+ }
838
+ function buildReservationBody(cfg, estimate, defaultSubject) {
839
+ validateNonNegative(estimate, "estimate");
840
+ const ttlMs = cfg.ttlMs ?? 6e4;
841
+ validateTtlMs(ttlMs);
842
+ const subject = {};
843
+ for (const field of [
844
+ "tenant",
845
+ "workspace",
846
+ "app",
847
+ "workflow",
848
+ "agent",
849
+ "toolset"
850
+ ]) {
851
+ const val = cfg[field] ?? defaultSubject[field];
852
+ if (val) {
853
+ subject[field] = val;
854
+ }
855
+ }
856
+ if (cfg.dimensions) {
857
+ subject.dimensions = cfg.dimensions;
858
+ }
859
+ validateSubject(subject);
860
+ const action = {
861
+ kind: cfg.actionKind ?? "unknown",
862
+ name: cfg.actionName ?? "unknown"
863
+ };
864
+ if (cfg.actionTags) {
865
+ action.tags = cfg.actionTags;
866
+ }
867
+ const unit = cfg.unit ?? "USD_MICROCENTS";
868
+ const body = {
869
+ idempotency_key: (0, import_node_crypto.randomUUID)(),
870
+ subject,
871
+ action,
872
+ estimate: { unit, amount: estimate },
873
+ ttl_ms: ttlMs,
874
+ overage_policy: cfg.overagePolicy ?? "REJECT"
875
+ };
876
+ validateGracePeriodMs(cfg.gracePeriodMs);
877
+ if (cfg.gracePeriodMs !== void 0) {
878
+ body.grace_period_ms = cfg.gracePeriodMs;
879
+ }
880
+ if (cfg.dryRun) {
881
+ body.dry_run = true;
882
+ }
883
+ return body;
884
+ }
885
+ function buildCommitBody(actual, unit, metrics, metadata) {
886
+ const body = {
887
+ idempotency_key: (0, import_node_crypto.randomUUID)(),
888
+ actual: { unit, amount: actual }
889
+ };
890
+ if (metrics && !isMetricsEmpty(metrics)) {
891
+ body.metrics = metricsToWire(metrics);
892
+ }
893
+ if (metadata) {
894
+ body.metadata = metadata;
895
+ }
896
+ return body;
897
+ }
898
+ function isMetricsEmpty(metrics) {
899
+ return metrics.tokensInput === void 0 && metrics.tokensOutput === void 0 && metrics.latencyMs === void 0 && metrics.modelVersion === void 0 && !metrics.custom;
900
+ }
901
+ function buildReleaseBody(reason) {
902
+ return { idempotency_key: (0, import_node_crypto.randomUUID)(), reason };
903
+ }
904
+ function buildExtendBody(extendByMs) {
905
+ validateExtendByMs(extendByMs);
906
+ return { idempotency_key: (0, import_node_crypto.randomUUID)(), extend_by_ms: extendByMs };
907
+ }
908
+ var AsyncCyclesLifecycle = class {
909
+ _client;
910
+ _retryEngine;
911
+ _defaultSubject;
912
+ constructor(client, retryEngine, defaultSubject) {
913
+ this._client = client;
914
+ this._retryEngine = retryEngine;
915
+ this._retryEngine.setClient(client);
916
+ this._defaultSubject = defaultSubject;
917
+ }
918
+ async execute(fn, args, cfg) {
919
+ const estimate = evaluateAmount(cfg.estimate, args);
920
+ const createBody = buildReservationBody(cfg, estimate, this._defaultSubject);
921
+ const resT1 = performance.now();
922
+ const resResponse = await this._client.createReservation(createBody);
923
+ if (!resResponse.isSuccess) {
924
+ throw buildProtocolException("Failed to create reservation", resResponse);
925
+ }
926
+ const resResult = reservationCreateResponseFromWire(
927
+ resResponse.body
928
+ );
929
+ const resT2 = performance.now();
930
+ const decision = resResult.decision;
931
+ const reservationId = resResult.reservationId;
932
+ const reasonCode = resResult.reasonCode;
933
+ if (cfg.dryRun) {
934
+ if (decision === "DENY") {
935
+ throw buildProtocolException("Dry-run denied", resResponse);
936
+ }
937
+ return {
938
+ decision,
939
+ caps: resResult.caps,
940
+ affectedScopes: resResult.affectedScopes,
941
+ scopePath: resResult.scopePath,
942
+ reserved: resResult.reserved,
943
+ balances: resResult.balances,
944
+ reasonCode,
945
+ retryAfterMs: resResult.retryAfterMs
946
+ };
947
+ }
948
+ if (decision === "DENY") {
949
+ throw buildProtocolException("Reservation denied", resResponse);
950
+ }
951
+ if (!reservationId) {
952
+ throw new CyclesProtocolError(
953
+ "Reservation successful but reservation_id missing",
954
+ { status: resResponse.status }
955
+ );
956
+ }
957
+ const unit = cfg.unit ?? "USD_MICROCENTS";
958
+ const ttlMs = cfg.ttlMs ?? 6e4;
959
+ const ctx = {
960
+ reservationId,
961
+ estimate,
962
+ decision,
963
+ caps: resResult.caps,
964
+ expiresAtMs: resResult.expiresAtMs,
965
+ affectedScopes: resResult.affectedScopes,
966
+ scopePath: resResult.scopePath,
967
+ reserved: resResult.reserved,
968
+ balances: resResult.balances
969
+ };
970
+ const heartbeatRef = this._startHeartbeat(reservationId, ttlMs, ctx);
971
+ try {
972
+ const result = await runWithContext(ctx, () => fn(...args));
973
+ const methodElapsed = Math.round(performance.now() - resT2);
974
+ const useEstimateFallback = cfg.useEstimateIfActualNotProvided !== false;
975
+ const actualAmount = evaluateActual(
976
+ cfg.actual,
977
+ result,
978
+ estimate,
979
+ useEstimateFallback
980
+ );
981
+ let metrics = ctx.metrics;
982
+ if (!metrics) {
983
+ metrics = {};
984
+ }
985
+ if (metrics.latencyMs === void 0) {
986
+ metrics = { ...metrics, latencyMs: methodElapsed };
987
+ }
988
+ const commitBody = buildCommitBody(
989
+ actualAmount,
990
+ unit,
991
+ metrics,
992
+ ctx.commitMetadata
993
+ );
994
+ await this._handleCommit(reservationId, commitBody);
995
+ return result;
996
+ } catch (err) {
997
+ await this._handleRelease(reservationId, "guarded_method_failed");
998
+ throw err;
999
+ } finally {
1000
+ if (heartbeatRef) {
1001
+ heartbeatRef.stop();
1002
+ }
1003
+ }
1004
+ }
1005
+ async _handleCommit(reservationId, commitBody) {
1006
+ try {
1007
+ const response = await this._client.commitReservation(
1008
+ reservationId,
1009
+ commitBody
1010
+ );
1011
+ if (response.isSuccess) {
1012
+ return;
1013
+ }
1014
+ if (response.isTransportError || response.isServerError) {
1015
+ this._retryEngine.schedule(reservationId, commitBody);
1016
+ return;
1017
+ }
1018
+ const errorResp = response.getErrorResponse();
1019
+ const errorCode = errorResp?.error;
1020
+ if (errorCode === "RESERVATION_FINALIZED" || errorCode === "RESERVATION_EXPIRED") {
1021
+ return;
1022
+ }
1023
+ if (errorCode === "IDEMPOTENCY_MISMATCH") {
1024
+ return;
1025
+ }
1026
+ if (response.isClientError) {
1027
+ await this._handleRelease(
1028
+ reservationId,
1029
+ `commit_rejected_${errorCode}`
1030
+ );
1031
+ return;
1032
+ }
1033
+ } catch {
1034
+ this._retryEngine.schedule(reservationId, commitBody);
1035
+ }
1036
+ }
1037
+ async _handleRelease(reservationId, reason) {
1038
+ try {
1039
+ const body = buildReleaseBody(reason);
1040
+ await this._client.releaseReservation(reservationId, body);
1041
+ } catch {
1042
+ }
1043
+ }
1044
+ _startHeartbeat(reservationId, ttlMs, ctx) {
1045
+ if (ttlMs <= 0) return void 0;
1046
+ const intervalMs = Math.max(ttlMs / 2, 1e3);
1047
+ let stopped = false;
1048
+ let currentTimer;
1049
+ const tick = () => {
1050
+ if (stopped) return;
1051
+ currentTimer = setTimeout(() => {
1052
+ if (stopped) return;
1053
+ const body = buildExtendBody(ttlMs);
1054
+ void this._client.extendReservation(reservationId, body).then((response) => {
1055
+ if (response.isSuccess) {
1056
+ const newExpires = response.getBodyAttribute("expires_at_ms");
1057
+ if (typeof newExpires === "number") {
1058
+ ctx.expiresAtMs = newExpires;
1059
+ }
1060
+ }
1061
+ }).catch(() => {
1062
+ }).finally(() => {
1063
+ tick();
1064
+ });
1065
+ }, intervalMs);
1066
+ };
1067
+ tick();
1068
+ return {
1069
+ stop: () => {
1070
+ stopped = true;
1071
+ clearTimeout(currentTimer);
1072
+ }
1073
+ };
1074
+ }
1075
+ };
1076
+
1077
+ // src/retry.ts
1078
+ function delay(ms) {
1079
+ return new Promise((resolve) => setTimeout(resolve, ms));
1080
+ }
1081
+ var CommitRetryEngine = class {
1082
+ _enabled;
1083
+ _maxAttempts;
1084
+ _initialDelay;
1085
+ _multiplier;
1086
+ _maxDelay;
1087
+ _client;
1088
+ constructor(config) {
1089
+ this._enabled = config.retryEnabled;
1090
+ this._maxAttempts = config.retryMaxAttempts;
1091
+ this._initialDelay = config.retryInitialDelay;
1092
+ this._multiplier = config.retryMultiplier;
1093
+ this._maxDelay = config.retryMaxDelay;
1094
+ }
1095
+ setClient(client) {
1096
+ this._client = client;
1097
+ }
1098
+ schedule(reservationId, commitBody) {
1099
+ if (!this._enabled) {
1100
+ return;
1101
+ }
1102
+ void this._retryLoop(reservationId, commitBody);
1103
+ }
1104
+ async _retryLoop(reservationId, commitBody) {
1105
+ for (let attempt = 0; attempt < this._maxAttempts; attempt++) {
1106
+ const backoff = Math.min(
1107
+ this._initialDelay * this._multiplier ** attempt,
1108
+ this._maxDelay
1109
+ );
1110
+ await delay(backoff);
1111
+ try {
1112
+ if (!this._client) {
1113
+ return;
1114
+ }
1115
+ const response = await this._client.commitReservation(
1116
+ reservationId,
1117
+ commitBody
1118
+ );
1119
+ if (response.isSuccess) {
1120
+ return;
1121
+ }
1122
+ if (response.isClientError) {
1123
+ return;
1124
+ }
1125
+ } catch {
1126
+ }
1127
+ }
1128
+ }
1129
+ };
1130
+
1131
+ // src/withCycles.ts
1132
+ var _defaultClient;
1133
+ var _defaultConfig;
1134
+ function setDefaultClient(client) {
1135
+ _defaultClient = client;
1136
+ }
1137
+ function setDefaultConfig(config) {
1138
+ _defaultConfig = config;
1139
+ }
1140
+ function getEffectiveClient(explicitClient) {
1141
+ if (explicitClient) return explicitClient;
1142
+ if (_defaultClient) return _defaultClient;
1143
+ if (_defaultConfig) {
1144
+ _defaultClient = new CyclesClient(_defaultConfig);
1145
+ return _defaultClient;
1146
+ }
1147
+ throw new Error(
1148
+ "No Cycles client available. Either pass client in options, call setDefaultClient(), or call setDefaultConfig()."
1149
+ );
1150
+ }
1151
+ function withCycles(options, fn) {
1152
+ return async (...args) => {
1153
+ const client = getEffectiveClient(options.client);
1154
+ const config = client.config;
1155
+ const defaultSubject = {
1156
+ tenant: config.tenant,
1157
+ workspace: config.workspace,
1158
+ app: config.app,
1159
+ workflow: config.workflow,
1160
+ agent: config.agent,
1161
+ toolset: config.toolset
1162
+ };
1163
+ const retryEngine = new CommitRetryEngine(config);
1164
+ const lifecycle = new AsyncCyclesLifecycle(
1165
+ client,
1166
+ retryEngine,
1167
+ defaultSubject
1168
+ );
1169
+ return lifecycle.execute(
1170
+ fn,
1171
+ args,
1172
+ options
1173
+ );
1174
+ };
1175
+ }
1176
+
1177
+ // src/streaming.ts
1178
+ var import_node_crypto2 = require("crypto");
1179
+
1180
+ // src/models.ts
1181
+ var Unit = /* @__PURE__ */ ((Unit2) => {
1182
+ Unit2["USD_MICROCENTS"] = "USD_MICROCENTS";
1183
+ Unit2["TOKENS"] = "TOKENS";
1184
+ Unit2["CREDITS"] = "CREDITS";
1185
+ Unit2["RISK_POINTS"] = "RISK_POINTS";
1186
+ return Unit2;
1187
+ })(Unit || {});
1188
+ var CommitOveragePolicy = /* @__PURE__ */ ((CommitOveragePolicy2) => {
1189
+ CommitOveragePolicy2["REJECT"] = "REJECT";
1190
+ CommitOveragePolicy2["ALLOW_IF_AVAILABLE"] = "ALLOW_IF_AVAILABLE";
1191
+ CommitOveragePolicy2["ALLOW_WITH_OVERDRAFT"] = "ALLOW_WITH_OVERDRAFT";
1192
+ return CommitOveragePolicy2;
1193
+ })(CommitOveragePolicy || {});
1194
+ var Decision = /* @__PURE__ */ ((Decision2) => {
1195
+ Decision2["ALLOW"] = "ALLOW";
1196
+ Decision2["ALLOW_WITH_CAPS"] = "ALLOW_WITH_CAPS";
1197
+ Decision2["DENY"] = "DENY";
1198
+ return Decision2;
1199
+ })(Decision || {});
1200
+ var ReservationStatus = /* @__PURE__ */ ((ReservationStatus2) => {
1201
+ ReservationStatus2["ACTIVE"] = "ACTIVE";
1202
+ ReservationStatus2["COMMITTED"] = "COMMITTED";
1203
+ ReservationStatus2["RELEASED"] = "RELEASED";
1204
+ ReservationStatus2["EXPIRED"] = "EXPIRED";
1205
+ return ReservationStatus2;
1206
+ })(ReservationStatus || {});
1207
+ var CommitStatus = /* @__PURE__ */ ((CommitStatus2) => {
1208
+ CommitStatus2["COMMITTED"] = "COMMITTED";
1209
+ return CommitStatus2;
1210
+ })(CommitStatus || {});
1211
+ var ReleaseStatus = /* @__PURE__ */ ((ReleaseStatus2) => {
1212
+ ReleaseStatus2["RELEASED"] = "RELEASED";
1213
+ return ReleaseStatus2;
1214
+ })(ReleaseStatus || {});
1215
+ var ExtendStatus = /* @__PURE__ */ ((ExtendStatus2) => {
1216
+ ExtendStatus2["ACTIVE"] = "ACTIVE";
1217
+ return ExtendStatus2;
1218
+ })(ExtendStatus || {});
1219
+ var EventStatus = /* @__PURE__ */ ((EventStatus2) => {
1220
+ EventStatus2["APPLIED"] = "APPLIED";
1221
+ return EventStatus2;
1222
+ })(EventStatus || {});
1223
+ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
1224
+ ErrorCode2["INVALID_REQUEST"] = "INVALID_REQUEST";
1225
+ ErrorCode2["UNAUTHORIZED"] = "UNAUTHORIZED";
1226
+ ErrorCode2["FORBIDDEN"] = "FORBIDDEN";
1227
+ ErrorCode2["NOT_FOUND"] = "NOT_FOUND";
1228
+ ErrorCode2["BUDGET_EXCEEDED"] = "BUDGET_EXCEEDED";
1229
+ ErrorCode2["RESERVATION_EXPIRED"] = "RESERVATION_EXPIRED";
1230
+ ErrorCode2["RESERVATION_FINALIZED"] = "RESERVATION_FINALIZED";
1231
+ ErrorCode2["IDEMPOTENCY_MISMATCH"] = "IDEMPOTENCY_MISMATCH";
1232
+ ErrorCode2["UNIT_MISMATCH"] = "UNIT_MISMATCH";
1233
+ ErrorCode2["OVERDRAFT_LIMIT_EXCEEDED"] = "OVERDRAFT_LIMIT_EXCEEDED";
1234
+ ErrorCode2["DEBT_OUTSTANDING"] = "DEBT_OUTSTANDING";
1235
+ ErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
1236
+ ErrorCode2["UNKNOWN"] = "UNKNOWN";
1237
+ return ErrorCode2;
1238
+ })(ErrorCode || {});
1239
+ function isAllowed(decision) {
1240
+ return decision === "ALLOW" /* ALLOW */ || decision === "ALLOW_WITH_CAPS" /* ALLOW_WITH_CAPS */;
1241
+ }
1242
+ function isDenied(decision) {
1243
+ return decision === "DENY" /* DENY */;
1244
+ }
1245
+ function isRetryableErrorCode(code) {
1246
+ return code === "INTERNAL_ERROR" /* INTERNAL_ERROR */ || code === "UNKNOWN" /* UNKNOWN */;
1247
+ }
1248
+ function errorCodeFromString(value) {
1249
+ if (value === void 0) return void 0;
1250
+ if (Object.values(ErrorCode).includes(value)) {
1251
+ return value;
1252
+ }
1253
+ return "UNKNOWN" /* UNKNOWN */;
1254
+ }
1255
+ function isToolAllowed(caps, tool) {
1256
+ if (caps.toolAllowlist && caps.toolAllowlist.length > 0) {
1257
+ return caps.toolAllowlist.includes(tool);
1258
+ }
1259
+ if (caps.toolDenylist && caps.toolDenylist.length > 0) {
1260
+ return !caps.toolDenylist.includes(tool);
1261
+ }
1262
+ return true;
1263
+ }
1264
+ function isMetricsEmpty2(metrics) {
1265
+ return metrics.tokensInput === void 0 && metrics.tokensOutput === void 0 && metrics.latencyMs === void 0 && metrics.modelVersion === void 0 && !metrics.custom;
1266
+ }
1267
+
1268
+ // src/streaming.ts
1269
+ async function reserveForStream(options) {
1270
+ const {
1271
+ client,
1272
+ estimate,
1273
+ unit = "USD_MICROCENTS",
1274
+ actionKind = "unknown",
1275
+ actionName = "unknown",
1276
+ actionTags,
1277
+ ttlMs = 6e4,
1278
+ gracePeriodMs,
1279
+ overagePolicy = "REJECT",
1280
+ dimensions
1281
+ } = options;
1282
+ validateNonNegative(estimate, "estimate");
1283
+ validateTtlMs(ttlMs);
1284
+ validateGracePeriodMs(gracePeriodMs);
1285
+ const configDefaults = client.config;
1286
+ const subject = {};
1287
+ for (const field of ["tenant", "workspace", "app", "workflow", "agent", "toolset"]) {
1288
+ const val = options[field] ?? configDefaults[field];
1289
+ if (val) {
1290
+ subject[field] = val;
1291
+ }
1292
+ }
1293
+ if (dimensions) {
1294
+ subject.dimensions = dimensions;
1295
+ }
1296
+ validateSubject(subject);
1297
+ const action = { kind: actionKind, name: actionName };
1298
+ if (actionTags) {
1299
+ action.tags = actionTags;
1300
+ }
1301
+ const body = {
1302
+ idempotency_key: (0, import_node_crypto2.randomUUID)(),
1303
+ subject,
1304
+ action,
1305
+ estimate: { unit, amount: estimate },
1306
+ ttl_ms: ttlMs,
1307
+ overage_policy: overagePolicy
1308
+ };
1309
+ if (gracePeriodMs !== void 0) {
1310
+ body.grace_period_ms = gracePeriodMs;
1311
+ }
1312
+ const response = await client.createReservation(body);
1313
+ if (!response.isSuccess) {
1314
+ throw buildProtocolException("Failed to create reservation", response);
1315
+ }
1316
+ const parsed = reservationCreateResponseFromWire(
1317
+ response.body
1318
+ );
1319
+ if (parsed.decision === "DENY") {
1320
+ throw buildProtocolException("Reservation denied", response);
1321
+ }
1322
+ const reservationId = parsed.reservationId;
1323
+ if (!reservationId) {
1324
+ throw new CyclesProtocolError(
1325
+ "Reservation successful but reservation_id missing",
1326
+ { status: response.status }
1327
+ );
1328
+ }
1329
+ let heartbeatStopped = false;
1330
+ let finalized = false;
1331
+ let currentTimer;
1332
+ const stopHeartbeat = () => {
1333
+ if (!heartbeatStopped) {
1334
+ heartbeatStopped = true;
1335
+ clearTimeout(currentTimer);
1336
+ }
1337
+ };
1338
+ const startHeartbeat = () => {
1339
+ if (ttlMs <= 0) return;
1340
+ const intervalMs = Math.max(ttlMs / 2, 1e3);
1341
+ const tick = () => {
1342
+ if (heartbeatStopped) return;
1343
+ currentTimer = setTimeout(() => {
1344
+ if (heartbeatStopped) return;
1345
+ validateExtendByMs(ttlMs);
1346
+ const extendBody = { idempotency_key: (0, import_node_crypto2.randomUUID)(), extend_by_ms: ttlMs };
1347
+ void client.extendReservation(reservationId, extendBody).catch(() => {
1348
+ }).finally(() => {
1349
+ tick();
1350
+ });
1351
+ }, intervalMs);
1352
+ };
1353
+ tick();
1354
+ };
1355
+ startHeartbeat();
1356
+ return {
1357
+ reservationId,
1358
+ decision: parsed.decision,
1359
+ caps: parsed.caps,
1360
+ get finalized() {
1361
+ return finalized;
1362
+ },
1363
+ async commit(actual, metrics, metadata) {
1364
+ if (finalized) {
1365
+ throw new CyclesError("StreamReservation already finalized");
1366
+ }
1367
+ finalized = true;
1368
+ stopHeartbeat();
1369
+ const commitBody = {
1370
+ idempotency_key: (0, import_node_crypto2.randomUUID)(),
1371
+ actual: { unit, amount: actual }
1372
+ };
1373
+ if (metrics && !isMetricsEmpty2(metrics)) {
1374
+ commitBody.metrics = metricsToWire(metrics);
1375
+ }
1376
+ if (metadata) {
1377
+ commitBody.metadata = metadata;
1378
+ }
1379
+ await client.commitReservation(reservationId, commitBody);
1380
+ },
1381
+ async release(reason) {
1382
+ if (finalized) return;
1383
+ finalized = true;
1384
+ stopHeartbeat();
1385
+ try {
1386
+ const releaseBody = { idempotency_key: (0, import_node_crypto2.randomUUID)(), reason: reason ?? "stream_aborted" };
1387
+ await client.releaseReservation(reservationId, releaseBody);
1388
+ } catch {
1389
+ }
1390
+ },
1391
+ dispose() {
1392
+ if (finalized) return;
1393
+ finalized = true;
1394
+ stopHeartbeat();
1395
+ }
1396
+ };
1397
+ }
1398
+ // Annotate the CommonJS export names for ESM import in node:
1399
+ 0 && (module.exports = {
1400
+ BudgetExceededError,
1401
+ CommitOveragePolicy,
1402
+ CommitStatus,
1403
+ CyclesClient,
1404
+ CyclesConfig,
1405
+ CyclesError,
1406
+ CyclesProtocolError,
1407
+ CyclesResponse,
1408
+ CyclesTransportError,
1409
+ DebtOutstandingError,
1410
+ Decision,
1411
+ ErrorCode,
1412
+ EventStatus,
1413
+ ExtendStatus,
1414
+ OverdraftLimitExceededError,
1415
+ ReleaseStatus,
1416
+ ReservationExpiredError,
1417
+ ReservationFinalizedError,
1418
+ ReservationStatus,
1419
+ Unit,
1420
+ balanceResponseFromWire,
1421
+ capsFromWire,
1422
+ commitRequestToWire,
1423
+ commitResponseFromWire,
1424
+ decisionRequestToWire,
1425
+ decisionResponseFromWire,
1426
+ errorCodeFromString,
1427
+ errorResponseFromWire,
1428
+ eventCreateRequestToWire,
1429
+ eventCreateResponseFromWire,
1430
+ getCyclesContext,
1431
+ isAllowed,
1432
+ isDenied,
1433
+ isMetricsEmpty,
1434
+ isRetryableErrorCode,
1435
+ isToolAllowed,
1436
+ metricsToWire,
1437
+ releaseRequestToWire,
1438
+ releaseResponseFromWire,
1439
+ reservationCreateRequestToWire,
1440
+ reservationCreateResponseFromWire,
1441
+ reservationDetailFromWire,
1442
+ reservationExtendRequestToWire,
1443
+ reservationExtendResponseFromWire,
1444
+ reservationListResponseFromWire,
1445
+ reservationSummaryFromWire,
1446
+ reserveForStream,
1447
+ setDefaultClient,
1448
+ setDefaultConfig,
1449
+ withCycles
1450
+ });