runcycles 0.1.0 → 0.1.1

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.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/constants.ts
2
2
  var API_KEY_HEADER = "X-Cycles-API-Key";
3
3
  var IDEMPOTENCY_KEY_HEADER = "X-Idempotency-Key";
4
+ var DEFAULT_TTL_MS = 6e4;
4
5
  var RESERVATIONS_PATH = "/v1/reservations";
5
6
  var DECIDE_PATH = "/v1/decide";
6
7
  var BALANCES_PATH = "/v1/balances";
@@ -460,6 +461,8 @@ var CyclesClient = class {
460
461
  );
461
462
  }
462
463
  }
464
+ async [Symbol.asyncDispose]() {
465
+ }
463
466
  async _handleResponse(resp) {
464
467
  let body;
465
468
  try {
@@ -645,14 +648,6 @@ var ReservationFinalizedError = class extends CyclesProtocolError {
645
648
  this.name = "ReservationFinalizedError";
646
649
  }
647
650
  };
648
- var CyclesTransportError = class extends CyclesError {
649
- cause;
650
- constructor(message, options) {
651
- super(message);
652
- this.name = "CyclesTransportError";
653
- this.cause = options?.cause;
654
- }
655
- };
656
651
 
657
652
  // src/errors.ts
658
653
  function buildProtocolException(prefix, response) {
@@ -708,6 +703,94 @@ function buildProtocolException(prefix, response) {
708
703
  }
709
704
  }
710
705
 
706
+ // src/models.ts
707
+ var Unit = /* @__PURE__ */ ((Unit2) => {
708
+ Unit2["USD_MICROCENTS"] = "USD_MICROCENTS";
709
+ Unit2["TOKENS"] = "TOKENS";
710
+ Unit2["CREDITS"] = "CREDITS";
711
+ Unit2["RISK_POINTS"] = "RISK_POINTS";
712
+ return Unit2;
713
+ })(Unit || {});
714
+ var CommitOveragePolicy = /* @__PURE__ */ ((CommitOveragePolicy2) => {
715
+ CommitOveragePolicy2["REJECT"] = "REJECT";
716
+ CommitOveragePolicy2["ALLOW_IF_AVAILABLE"] = "ALLOW_IF_AVAILABLE";
717
+ CommitOveragePolicy2["ALLOW_WITH_OVERDRAFT"] = "ALLOW_WITH_OVERDRAFT";
718
+ return CommitOveragePolicy2;
719
+ })(CommitOveragePolicy || {});
720
+ var Decision = /* @__PURE__ */ ((Decision2) => {
721
+ Decision2["ALLOW"] = "ALLOW";
722
+ Decision2["ALLOW_WITH_CAPS"] = "ALLOW_WITH_CAPS";
723
+ Decision2["DENY"] = "DENY";
724
+ return Decision2;
725
+ })(Decision || {});
726
+ var ReservationStatus = /* @__PURE__ */ ((ReservationStatus2) => {
727
+ ReservationStatus2["ACTIVE"] = "ACTIVE";
728
+ ReservationStatus2["COMMITTED"] = "COMMITTED";
729
+ ReservationStatus2["RELEASED"] = "RELEASED";
730
+ ReservationStatus2["EXPIRED"] = "EXPIRED";
731
+ return ReservationStatus2;
732
+ })(ReservationStatus || {});
733
+ var CommitStatus = /* @__PURE__ */ ((CommitStatus2) => {
734
+ CommitStatus2["COMMITTED"] = "COMMITTED";
735
+ return CommitStatus2;
736
+ })(CommitStatus || {});
737
+ var ReleaseStatus = /* @__PURE__ */ ((ReleaseStatus2) => {
738
+ ReleaseStatus2["RELEASED"] = "RELEASED";
739
+ return ReleaseStatus2;
740
+ })(ReleaseStatus || {});
741
+ var ExtendStatus = /* @__PURE__ */ ((ExtendStatus2) => {
742
+ ExtendStatus2["ACTIVE"] = "ACTIVE";
743
+ return ExtendStatus2;
744
+ })(ExtendStatus || {});
745
+ var EventStatus = /* @__PURE__ */ ((EventStatus2) => {
746
+ EventStatus2["APPLIED"] = "APPLIED";
747
+ return EventStatus2;
748
+ })(EventStatus || {});
749
+ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
750
+ ErrorCode2["INVALID_REQUEST"] = "INVALID_REQUEST";
751
+ ErrorCode2["UNAUTHORIZED"] = "UNAUTHORIZED";
752
+ ErrorCode2["FORBIDDEN"] = "FORBIDDEN";
753
+ ErrorCode2["NOT_FOUND"] = "NOT_FOUND";
754
+ ErrorCode2["BUDGET_EXCEEDED"] = "BUDGET_EXCEEDED";
755
+ ErrorCode2["RESERVATION_EXPIRED"] = "RESERVATION_EXPIRED";
756
+ ErrorCode2["RESERVATION_FINALIZED"] = "RESERVATION_FINALIZED";
757
+ ErrorCode2["IDEMPOTENCY_MISMATCH"] = "IDEMPOTENCY_MISMATCH";
758
+ ErrorCode2["UNIT_MISMATCH"] = "UNIT_MISMATCH";
759
+ ErrorCode2["OVERDRAFT_LIMIT_EXCEEDED"] = "OVERDRAFT_LIMIT_EXCEEDED";
760
+ ErrorCode2["DEBT_OUTSTANDING"] = "DEBT_OUTSTANDING";
761
+ ErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
762
+ ErrorCode2["UNKNOWN"] = "UNKNOWN";
763
+ return ErrorCode2;
764
+ })(ErrorCode || {});
765
+ function isAllowed(decision) {
766
+ return decision === "ALLOW" /* ALLOW */ || decision === "ALLOW_WITH_CAPS" /* ALLOW_WITH_CAPS */;
767
+ }
768
+ function isDenied(decision) {
769
+ return decision === "DENY" /* DENY */;
770
+ }
771
+ function isRetryableErrorCode(code) {
772
+ return code === "INTERNAL_ERROR" /* INTERNAL_ERROR */ || code === "UNKNOWN" /* UNKNOWN */;
773
+ }
774
+ function errorCodeFromString(value) {
775
+ if (value === void 0) return void 0;
776
+ if (Object.values(ErrorCode).includes(value)) {
777
+ return value;
778
+ }
779
+ return "UNKNOWN" /* UNKNOWN */;
780
+ }
781
+ function isToolAllowed(caps, tool) {
782
+ if (caps.toolAllowlist && caps.toolAllowlist.length > 0) {
783
+ return caps.toolAllowlist.includes(tool);
784
+ }
785
+ if (caps.toolDenylist && caps.toolDenylist.length > 0) {
786
+ return !caps.toolDenylist.includes(tool);
787
+ }
788
+ return true;
789
+ }
790
+ function isMetricsEmpty(metrics) {
791
+ return metrics.tokensInput === void 0 && metrics.tokensOutput === void 0 && metrics.latencyMs === void 0 && metrics.modelVersion === void 0 && !metrics.custom;
792
+ }
793
+
711
794
  // src/validation.ts
712
795
  function validateSubject(subject) {
713
796
  if (subject === void 0) return;
@@ -762,7 +845,7 @@ function evaluateActual(expr, result, estimate, useEstimateFallback) {
762
845
  }
763
846
  function buildReservationBody(cfg, estimate, defaultSubject) {
764
847
  validateNonNegative(estimate, "estimate");
765
- const ttlMs = cfg.ttlMs ?? 6e4;
848
+ const ttlMs = cfg.ttlMs ?? DEFAULT_TTL_MS;
766
849
  validateTtlMs(ttlMs);
767
850
  const subject = {};
768
851
  for (const field of [
@@ -820,9 +903,6 @@ function buildCommitBody(actual, unit, metrics, metadata) {
820
903
  }
821
904
  return body;
822
905
  }
823
- function isMetricsEmpty(metrics) {
824
- return metrics.tokensInput === void 0 && metrics.tokensOutput === void 0 && metrics.latencyMs === void 0 && metrics.modelVersion === void 0 && !metrics.custom;
825
- }
826
906
  function buildReleaseBody(reason) {
827
907
  return { idempotency_key: randomUUID(), reason };
828
908
  }
@@ -843,7 +923,6 @@ var AsyncCyclesLifecycle = class {
843
923
  async execute(fn, args, cfg) {
844
924
  const estimate = evaluateAmount(cfg.estimate, args);
845
925
  const createBody = buildReservationBody(cfg, estimate, this._defaultSubject);
846
- const resT1 = performance.now();
847
926
  const resResponse = await this._client.createReservation(createBody);
848
927
  if (!resResponse.isSuccess) {
849
928
  throw buildProtocolException("Failed to create reservation", resResponse);
@@ -880,7 +959,7 @@ var AsyncCyclesLifecycle = class {
880
959
  );
881
960
  }
882
961
  const unit = cfg.unit ?? "USD_MICROCENTS";
883
- const ttlMs = cfg.ttlMs ?? 6e4;
962
+ const ttlMs = cfg.ttlMs ?? DEFAULT_TTL_MS;
884
963
  const ctx = {
885
964
  reservationId,
886
965
  estimate,
@@ -941,7 +1020,13 @@ var AsyncCyclesLifecycle = class {
941
1020
  return;
942
1021
  }
943
1022
  const errorResp = response.getErrorResponse();
944
- const errorCode = errorResp?.error;
1023
+ let errorCode = errorResp?.error;
1024
+ if (errorCode === void 0) {
1025
+ const rawError = response.getBodyAttribute("error");
1026
+ if (typeof rawError === "string") {
1027
+ errorCode = rawError;
1028
+ }
1029
+ }
945
1030
  if (errorCode === "RESERVATION_FINALIZED" || errorCode === "RESERVATION_EXPIRED") {
946
1031
  return;
947
1032
  }
@@ -951,7 +1036,7 @@ var AsyncCyclesLifecycle = class {
951
1036
  if (response.isClientError) {
952
1037
  await this._handleRelease(
953
1038
  reservationId,
954
- `commit_rejected_${errorCode}`
1039
+ `commit_rejected_${errorCode ?? "unknown"}`
955
1040
  );
956
1041
  return;
957
1042
  }
@@ -1050,6 +1135,9 @@ var CommitRetryEngine = class {
1050
1135
  } catch {
1051
1136
  }
1052
1137
  }
1138
+ console.warn(
1139
+ `[runcycles] Commit retry exhausted after ${this._maxAttempts} attempts for reservation ${reservationId}`
1140
+ );
1053
1141
  }
1054
1142
  };
1055
1143
 
@@ -1074,24 +1162,26 @@ function getEffectiveClient(explicitClient) {
1074
1162
  );
1075
1163
  }
1076
1164
  function withCycles(options, fn) {
1165
+ let lifecycle;
1166
+ function ensureLifecycle() {
1167
+ if (!lifecycle) {
1168
+ const client = getEffectiveClient(options.client);
1169
+ const config = client.config;
1170
+ const defaultSubject = {
1171
+ tenant: config.tenant,
1172
+ workspace: config.workspace,
1173
+ app: config.app,
1174
+ workflow: config.workflow,
1175
+ agent: config.agent,
1176
+ toolset: config.toolset
1177
+ };
1178
+ const retryEngine = new CommitRetryEngine(config);
1179
+ lifecycle = new AsyncCyclesLifecycle(client, retryEngine, defaultSubject);
1180
+ }
1181
+ return lifecycle;
1182
+ }
1077
1183
  return async (...args) => {
1078
- const client = getEffectiveClient(options.client);
1079
- const config = client.config;
1080
- const defaultSubject = {
1081
- tenant: config.tenant,
1082
- workspace: config.workspace,
1083
- app: config.app,
1084
- workflow: config.workflow,
1085
- agent: config.agent,
1086
- toolset: config.toolset
1087
- };
1088
- const retryEngine = new CommitRetryEngine(config);
1089
- const lifecycle = new AsyncCyclesLifecycle(
1090
- client,
1091
- retryEngine,
1092
- defaultSubject
1093
- );
1094
- return lifecycle.execute(
1184
+ return ensureLifecycle().execute(
1095
1185
  fn,
1096
1186
  args,
1097
1187
  options
@@ -1101,96 +1191,6 @@ function withCycles(options, fn) {
1101
1191
 
1102
1192
  // src/streaming.ts
1103
1193
  import { randomUUID as randomUUID2 } from "crypto";
1104
-
1105
- // src/models.ts
1106
- var Unit = /* @__PURE__ */ ((Unit2) => {
1107
- Unit2["USD_MICROCENTS"] = "USD_MICROCENTS";
1108
- Unit2["TOKENS"] = "TOKENS";
1109
- Unit2["CREDITS"] = "CREDITS";
1110
- Unit2["RISK_POINTS"] = "RISK_POINTS";
1111
- return Unit2;
1112
- })(Unit || {});
1113
- var CommitOveragePolicy = /* @__PURE__ */ ((CommitOveragePolicy2) => {
1114
- CommitOveragePolicy2["REJECT"] = "REJECT";
1115
- CommitOveragePolicy2["ALLOW_IF_AVAILABLE"] = "ALLOW_IF_AVAILABLE";
1116
- CommitOveragePolicy2["ALLOW_WITH_OVERDRAFT"] = "ALLOW_WITH_OVERDRAFT";
1117
- return CommitOveragePolicy2;
1118
- })(CommitOveragePolicy || {});
1119
- var Decision = /* @__PURE__ */ ((Decision2) => {
1120
- Decision2["ALLOW"] = "ALLOW";
1121
- Decision2["ALLOW_WITH_CAPS"] = "ALLOW_WITH_CAPS";
1122
- Decision2["DENY"] = "DENY";
1123
- return Decision2;
1124
- })(Decision || {});
1125
- var ReservationStatus = /* @__PURE__ */ ((ReservationStatus2) => {
1126
- ReservationStatus2["ACTIVE"] = "ACTIVE";
1127
- ReservationStatus2["COMMITTED"] = "COMMITTED";
1128
- ReservationStatus2["RELEASED"] = "RELEASED";
1129
- ReservationStatus2["EXPIRED"] = "EXPIRED";
1130
- return ReservationStatus2;
1131
- })(ReservationStatus || {});
1132
- var CommitStatus = /* @__PURE__ */ ((CommitStatus2) => {
1133
- CommitStatus2["COMMITTED"] = "COMMITTED";
1134
- return CommitStatus2;
1135
- })(CommitStatus || {});
1136
- var ReleaseStatus = /* @__PURE__ */ ((ReleaseStatus2) => {
1137
- ReleaseStatus2["RELEASED"] = "RELEASED";
1138
- return ReleaseStatus2;
1139
- })(ReleaseStatus || {});
1140
- var ExtendStatus = /* @__PURE__ */ ((ExtendStatus2) => {
1141
- ExtendStatus2["ACTIVE"] = "ACTIVE";
1142
- return ExtendStatus2;
1143
- })(ExtendStatus || {});
1144
- var EventStatus = /* @__PURE__ */ ((EventStatus2) => {
1145
- EventStatus2["APPLIED"] = "APPLIED";
1146
- return EventStatus2;
1147
- })(EventStatus || {});
1148
- var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
1149
- ErrorCode2["INVALID_REQUEST"] = "INVALID_REQUEST";
1150
- ErrorCode2["UNAUTHORIZED"] = "UNAUTHORIZED";
1151
- ErrorCode2["FORBIDDEN"] = "FORBIDDEN";
1152
- ErrorCode2["NOT_FOUND"] = "NOT_FOUND";
1153
- ErrorCode2["BUDGET_EXCEEDED"] = "BUDGET_EXCEEDED";
1154
- ErrorCode2["RESERVATION_EXPIRED"] = "RESERVATION_EXPIRED";
1155
- ErrorCode2["RESERVATION_FINALIZED"] = "RESERVATION_FINALIZED";
1156
- ErrorCode2["IDEMPOTENCY_MISMATCH"] = "IDEMPOTENCY_MISMATCH";
1157
- ErrorCode2["UNIT_MISMATCH"] = "UNIT_MISMATCH";
1158
- ErrorCode2["OVERDRAFT_LIMIT_EXCEEDED"] = "OVERDRAFT_LIMIT_EXCEEDED";
1159
- ErrorCode2["DEBT_OUTSTANDING"] = "DEBT_OUTSTANDING";
1160
- ErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
1161
- ErrorCode2["UNKNOWN"] = "UNKNOWN";
1162
- return ErrorCode2;
1163
- })(ErrorCode || {});
1164
- function isAllowed(decision) {
1165
- return decision === "ALLOW" /* ALLOW */ || decision === "ALLOW_WITH_CAPS" /* ALLOW_WITH_CAPS */;
1166
- }
1167
- function isDenied(decision) {
1168
- return decision === "DENY" /* DENY */;
1169
- }
1170
- function isRetryableErrorCode(code) {
1171
- return code === "INTERNAL_ERROR" /* INTERNAL_ERROR */ || code === "UNKNOWN" /* UNKNOWN */;
1172
- }
1173
- function errorCodeFromString(value) {
1174
- if (value === void 0) return void 0;
1175
- if (Object.values(ErrorCode).includes(value)) {
1176
- return value;
1177
- }
1178
- return "UNKNOWN" /* UNKNOWN */;
1179
- }
1180
- function isToolAllowed(caps, tool) {
1181
- if (caps.toolAllowlist && caps.toolAllowlist.length > 0) {
1182
- return caps.toolAllowlist.includes(tool);
1183
- }
1184
- if (caps.toolDenylist && caps.toolDenylist.length > 0) {
1185
- return !caps.toolDenylist.includes(tool);
1186
- }
1187
- return true;
1188
- }
1189
- function isMetricsEmpty2(metrics) {
1190
- return metrics.tokensInput === void 0 && metrics.tokensOutput === void 0 && metrics.latencyMs === void 0 && metrics.modelVersion === void 0 && !metrics.custom;
1191
- }
1192
-
1193
- // src/streaming.ts
1194
1194
  async function reserveForStream(options) {
1195
1195
  const {
1196
1196
  client,
@@ -1199,7 +1199,7 @@ async function reserveForStream(options) {
1199
1199
  actionKind = "unknown",
1200
1200
  actionName = "unknown",
1201
1201
  actionTags,
1202
- ttlMs = 6e4,
1202
+ ttlMs = DEFAULT_TTL_MS,
1203
1203
  gracePeriodMs,
1204
1204
  overagePolicy = "REJECT",
1205
1205
  dimensions
@@ -1295,13 +1295,23 @@ async function reserveForStream(options) {
1295
1295
  idempotency_key: randomUUID2(),
1296
1296
  actual: { unit, amount: actual }
1297
1297
  };
1298
- if (metrics && !isMetricsEmpty2(metrics)) {
1298
+ if (metrics && !isMetricsEmpty(metrics)) {
1299
1299
  commitBody.metrics = metricsToWire(metrics);
1300
1300
  }
1301
1301
  if (metadata) {
1302
1302
  commitBody.metadata = metadata;
1303
1303
  }
1304
- await client.commitReservation(reservationId, commitBody);
1304
+ try {
1305
+ const response2 = await client.commitReservation(reservationId, commitBody);
1306
+ if (!response2.isSuccess) {
1307
+ throw new CyclesError(
1308
+ `Commit failed with status ${response2.status}: ${response2.errorMessage ?? "unknown error"}`
1309
+ );
1310
+ }
1311
+ } catch (err) {
1312
+ finalized = false;
1313
+ throw err;
1314
+ }
1305
1315
  },
1306
1316
  async release(reason) {
1307
1317
  if (finalized) return;
@@ -1329,7 +1339,6 @@ export {
1329
1339
  CyclesError,
1330
1340
  CyclesProtocolError,
1331
1341
  CyclesResponse,
1332
- CyclesTransportError,
1333
1342
  DebtOutstandingError,
1334
1343
  Decision,
1335
1344
  ErrorCode,
@@ -1354,7 +1363,7 @@ export {
1354
1363
  getCyclesContext,
1355
1364
  isAllowed,
1356
1365
  isDenied,
1357
- isMetricsEmpty2 as isMetricsEmpty,
1366
+ isMetricsEmpty,
1358
1367
  isRetryableErrorCode,
1359
1368
  isToolAllowed,
1360
1369
  metricsToWire,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcycles",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "TypeScript client for the Cycles budget-management protocol",
5
5
  "license": "Apache-2.0",
6
6
  "author": "runcycles",
@@ -48,14 +48,19 @@
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsup",
51
+ "lint": "eslint src/",
51
52
  "test": "vitest run",
52
53
  "test:watch": "vitest",
54
+ "test:coverage": "vitest run --coverage",
53
55
  "typecheck": "tsc --noEmit",
54
- "prepublishOnly": "npm run build"
56
+ "prepublishOnly": "npm run lint && npm run build"
55
57
  },
56
58
  "devDependencies": {
57
59
  "@types/node": "^22.19.15",
60
+ "@typescript-eslint/eslint-plugin": "^8.57.0",
61
+ "@typescript-eslint/parser": "^8.57.0",
58
62
  "@vitest/coverage-v8": "^4.1.0",
63
+ "eslint": "^10.0.3",
59
64
  "tsup": "^8.0.0",
60
65
  "typescript": "^5.9.3",
61
66
  "vite": "^6.4.1",