@oxygen-agent/cli 1.142.4 → 1.152.15

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 CHANGED
@@ -34,4 +34,4 @@ oxygen update
34
34
 
35
35
  For product documentation and support, visit https://oxygen-agent.com.
36
36
 
37
- Version: 1.142.4
37
+ Version: 1.152.15
@@ -1,4 +1,4 @@
1
- import { OXYGEN_VERSION, OxygenError } from "@oxygen/shared";
1
+ import { OXYGEN_VERSION, OxygenError, isVersionGreater } from "@oxygen/shared";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { defaultApiUrl, loadCredentials } from "./credentials.js";
4
4
  import { resolveCliUpdateGuidance } from "./runtime.js";
@@ -24,6 +24,9 @@ path, options = {}) {
24
24
  const headers = {
25
25
  Accept: "application/json",
26
26
  "X-Oxygen-Trace-Id": traceId,
27
+ // Advertise the CLI version so the server can enforce its minimum-CLI floor
28
+ // even against clients that predate the client-side envelope gate.
29
+ "X-Oxygen-Client-Version": OXYGEN_VERSION,
27
30
  };
28
31
  addVercelProtectionBypassHeader(apiUrl, headers);
29
32
  if (credentials?.token) {
@@ -83,9 +86,10 @@ path, options = {}) {
83
86
  if (!response.ok || !envelope.ok) {
84
87
  const failure = envelope;
85
88
  const responseTraceId = response.headers.get("x-oxygen-trace-id") ?? traceId;
89
+ const details = withRetryAfterDetails(failure.error.details, response);
86
90
  throw new OxygenError(failure.error.code, failure.error.message, {
87
- details: withTraceDetails(failure.error.details, responseTraceId, compatibility, apiUrl),
88
- exitCode: response.status >= 500 ? 1 : 1,
91
+ details: withTraceDetails(details, responseTraceId, compatibility, apiUrl),
92
+ exitCode: 1,
89
93
  });
90
94
  }
91
95
  return envelope.data;
@@ -197,6 +201,43 @@ function readEnvelopeCompatibility(envelope) {
197
201
  }
198
202
  return compatibility;
199
203
  }
204
+ // Surface the server's 429 backoff as a first-class `retry_after_seconds` detail
205
+ // so loop-driving callers can wait the right amount of time instead of dead-
206
+ // reckoning. Prefers a value the API already put in details; otherwise derives
207
+ // it from the RFC 6585 Retry-After header (or reset_at). Non-429 responses and
208
+ // unparseable values pass through untouched.
209
+ function withRetryAfterDetails(details, response) {
210
+ if (response.status !== 429)
211
+ return details;
212
+ const record = details && typeof details === "object" && !Array.isArray(details)
213
+ ? details
214
+ : null;
215
+ if (record && typeof record.retry_after_seconds === "number")
216
+ return details;
217
+ const retryAfterSeconds = retryAfterSecondsFromResponse(response, record);
218
+ if (retryAfterSeconds === null)
219
+ return details;
220
+ if (record)
221
+ return { ...record, retry_after_seconds: retryAfterSeconds };
222
+ if (details === undefined)
223
+ return { retry_after_seconds: retryAfterSeconds };
224
+ return { details, retry_after_seconds: retryAfterSeconds };
225
+ }
226
+ function retryAfterSecondsFromResponse(response, details) {
227
+ const header = response.headers.get("retry-after");
228
+ if (header) {
229
+ const seconds = Number(header);
230
+ if (Number.isFinite(seconds) && seconds >= 0)
231
+ return Math.ceil(seconds);
232
+ }
233
+ const resetAt = details?.reset_at;
234
+ if (typeof resetAt === "string") {
235
+ const resetMs = Date.parse(resetAt);
236
+ if (!Number.isNaN(resetMs))
237
+ return Math.max(0, Math.ceil((resetMs - Date.now()) / 1000));
238
+ }
239
+ return null;
240
+ }
200
241
  function withTraceDetails(details, traceId, compatibility, apiUrl) {
201
242
  const serverVersion = compatibility.version;
202
243
  const fields = {
@@ -225,31 +266,6 @@ function withTraceDetails(details, traceId, compatibility, apiUrl) {
225
266
  ...fields,
226
267
  };
227
268
  }
228
- function isVersionGreater(left, right) {
229
- const leftParts = parseSemver(left);
230
- const rightParts = parseSemver(right);
231
- if (!leftParts || !rightParts)
232
- return false;
233
- for (let index = 0; index < leftParts.length; index += 1) {
234
- const leftPart = leftParts[index] ?? 0;
235
- const rightPart = rightParts[index] ?? 0;
236
- if (leftPart > rightPart)
237
- return true;
238
- if (leftPart < rightPart)
239
- return false;
240
- }
241
- return false;
242
- }
243
- function parseSemver(value) {
244
- const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(value);
245
- if (!match)
246
- return null;
247
- return [
248
- Number(match[1]),
249
- Number(match[2]),
250
- Number(match[3]),
251
- ];
252
- }
253
269
  function addVercelProtectionBypassHeader(apiUrl, headers) {
254
270
  const secret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET?.trim();
255
271
  if (!secret)