xapi-to 0.1.20 → 0.1.21
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 +41 -0
- package/dist/{chunk-TYY6JR6O.js → chunk-UEQCIJ7T.js} +73 -21
- package/dist/index.js +787 -39
- package/dist/openai-sandbox-client.js +1 -1
- package/package.json +1 -1
- package/skills/xapi/SKILL.md +1 -2
- package/skills/xapi/guides/provider.md +198 -0
- package/skills/xapi/guides/sandbox.md +100 -46
- package/src/client.ts +56 -5
- package/src/sandbox-client.ts +36 -16
package/README.md
CHANGED
|
@@ -327,11 +327,52 @@ xapi-to register --referral-code xapito # register with an invit
|
|
|
327
327
|
xapi-to register xapito # positional shorthand for --referral-code
|
|
328
328
|
xapi-to register --force # replace an existing file-based key
|
|
329
329
|
xapi-to balance # show USD balance
|
|
330
|
+
xapi-to usage <request-id> # finalized cost + balance-after receipt
|
|
331
|
+
xapi-to usage wait <request-id> --timeout 1m # poll until a streaming receipt is finalized
|
|
332
|
+
xapi-to earnings # spendable balance + provider earnings
|
|
333
|
+
xapi-to earnings list --status SETTLED --limit 20 # provider earning records
|
|
334
|
+
xapi-to earnings transfer 1 --idempotency-key reinvest-001 # reinvest settled earnings
|
|
330
335
|
xapi-to topup # generate payment URL
|
|
331
336
|
xapi-to topup --method stripe --amount 10 # stripe, $10
|
|
332
337
|
xapi-to topup --method x402 # x402 (USDC on Base)
|
|
333
338
|
```
|
|
334
339
|
|
|
340
|
+
`earnings` summary/list require the `earnings:read` scope on the current key;
|
|
341
|
+
`earnings transfer` requires `earnings:transfer`. Transfers are one-way and
|
|
342
|
+
idempotent: reuse a key only when retrying the same amount.
|
|
343
|
+
|
|
344
|
+
### Provider Management
|
|
345
|
+
|
|
346
|
+
Provider commands use scoped `XAPI-KEY` routes for service and release
|
|
347
|
+
management without a JWT exchange:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
xapi-to provider list
|
|
351
|
+
xapi-to provider create --file ./service.json
|
|
352
|
+
xapi-to provider update <service-id> --about-file ./ABOUT.md --website https://example.com
|
|
353
|
+
xapi-to provider versions <service-id>
|
|
354
|
+
xapi-to provider revision start <service-id> 1
|
|
355
|
+
xapi-to provider version update <service-id> <version-id> --file ./contract.json
|
|
356
|
+
xapi-to provider diff <service-id> 1
|
|
357
|
+
xapi-to provider publish <service-id> <revision-id> --changelog-file ./CHANGELOG.md
|
|
358
|
+
xapi-to provider metrics <service-id> --days 7
|
|
359
|
+
xapi-to provider events --after '<opaque-next-cursor>'
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Service usage tutorials are Skill packages. Scaffold one from the serving
|
|
363
|
+
contract, submit it for review, wait for publication, then link it:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
xapi-to provider skill scaffold <service-id> --output ./my-skill/SKILL.md
|
|
367
|
+
xapi-to skill submit --dir ./my-skill
|
|
368
|
+
xapi-to skill wait <submission-id> --timeout 10m
|
|
369
|
+
xapi-to provider skill link <service-id> <skill-id>
|
|
370
|
+
xapi-to provider skill fingerprint <service-id> --skill-version-id <version-id>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Run `xapi-to provider --help` and `xapi-to skill --help` for the complete
|
|
374
|
+
lifecycle, rollback, deletion, GitHub import, scope, and safety options.
|
|
375
|
+
|
|
335
376
|
### Config
|
|
336
377
|
|
|
337
378
|
```bash
|
|
@@ -265,8 +265,25 @@ function parseRetryAfterMs(res) {
|
|
|
265
265
|
const at = Date.parse(header);
|
|
266
266
|
return Number.isFinite(at) ? Math.max(0, at - Date.now()) : void 0;
|
|
267
267
|
}
|
|
268
|
-
function
|
|
269
|
-
|
|
268
|
+
function abortError(signal) {
|
|
269
|
+
const reason = signal?.reason;
|
|
270
|
+
return reason instanceof Error ? reason : new DOMException("The operation was aborted", "AbortError");
|
|
271
|
+
}
|
|
272
|
+
function sleep(ms, signal) {
|
|
273
|
+
if (signal?.aborted) return Promise.reject(abortError(signal));
|
|
274
|
+
return new Promise((resolve2, reject) => {
|
|
275
|
+
let timer;
|
|
276
|
+
const onAbort = () => {
|
|
277
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
278
|
+
signal?.removeEventListener("abort", onAbort);
|
|
279
|
+
reject(abortError(signal));
|
|
280
|
+
};
|
|
281
|
+
timer = setTimeout(() => {
|
|
282
|
+
signal?.removeEventListener("abort", onAbort);
|
|
283
|
+
resolve2();
|
|
284
|
+
}, ms);
|
|
285
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
286
|
+
});
|
|
270
287
|
}
|
|
271
288
|
async function request(url, options, timeoutMs = DEFAULT_TIMEOUT_MS, retries = 0) {
|
|
272
289
|
assertAllowedHost(url);
|
|
@@ -294,7 +311,7 @@ async function request(url, options, timeoutMs = DEFAULT_TIMEOUT_MS, retries = 0
|
|
|
294
311
|
if (isRetryableStatus(res.status) && attempt < retries) {
|
|
295
312
|
await res.text().catch(() => "");
|
|
296
313
|
clearTimeout(timer);
|
|
297
|
-
await sleep(backoffDelayMs(attempt, retryAfterMs));
|
|
314
|
+
await sleep(backoffDelayMs(attempt, retryAfterMs), callerSignal);
|
|
298
315
|
attempt++;
|
|
299
316
|
continue;
|
|
300
317
|
}
|
|
@@ -327,7 +344,7 @@ async function request(url, options, timeoutMs = DEFAULT_TIMEOUT_MS, retries = 0
|
|
|
327
344
|
if (timedOut) {
|
|
328
345
|
const timeoutError = new RequestTimeoutError(timeoutMs);
|
|
329
346
|
if (attempt < retries) {
|
|
330
|
-
await sleep(backoffDelayMs(attempt));
|
|
347
|
+
await sleep(backoffDelayMs(attempt), callerSignal);
|
|
331
348
|
attempt++;
|
|
332
349
|
continue;
|
|
333
350
|
}
|
|
@@ -335,7 +352,7 @@ async function request(url, options, timeoutMs = DEFAULT_TIMEOUT_MS, retries = 0
|
|
|
335
352
|
}
|
|
336
353
|
if (isRetryableNetworkError(e) && attempt < retries) {
|
|
337
354
|
clearTimeout(timer);
|
|
338
|
-
await sleep(backoffDelayMs(attempt));
|
|
355
|
+
await sleep(backoffDelayMs(attempt), callerSignal);
|
|
339
356
|
attempt++;
|
|
340
357
|
continue;
|
|
341
358
|
}
|
|
@@ -351,6 +368,21 @@ function headers(apiKey) {
|
|
|
351
368
|
if (apiKey) h["XAPI-Key"] = apiKey;
|
|
352
369
|
return h;
|
|
353
370
|
}
|
|
371
|
+
function apiKeyApiRequest(apiHost, apiKey, path, options = {}) {
|
|
372
|
+
const method = options.method ?? "GET";
|
|
373
|
+
const requestHeaders = { "XAPI-KEY": apiKey };
|
|
374
|
+
if (options.body !== void 0) requestHeaders["Content-Type"] = "application/json";
|
|
375
|
+
return request(
|
|
376
|
+
`${scheme(apiHost)}://${apiHost}${path}`,
|
|
377
|
+
{
|
|
378
|
+
method,
|
|
379
|
+
headers: requestHeaders,
|
|
380
|
+
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {}
|
|
381
|
+
},
|
|
382
|
+
options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
383
|
+
options.retries ?? 0
|
|
384
|
+
);
|
|
385
|
+
}
|
|
354
386
|
function baseUrl(opts) {
|
|
355
387
|
return `${scheme(opts.actionHost)}://${opts.actionHost}`;
|
|
356
388
|
}
|
|
@@ -794,24 +826,43 @@ var sandboxAudit = (opts, id, kind, page = 1, pageSize = 100) => sandboxRequest(
|
|
|
794
826
|
);
|
|
795
827
|
async function sandboxWait(opts, id, wanted, timeoutMs = 3e5, intervalMs = 2e3, signal) {
|
|
796
828
|
const deadline = Date.now() + timeoutMs;
|
|
829
|
+
const deadlineController = new AbortController();
|
|
830
|
+
const abortFromCaller = () => deadlineController.abort();
|
|
831
|
+
const deadlineTimer = setTimeout(() => deadlineController.abort(), Math.max(0, timeoutMs));
|
|
832
|
+
if (signal?.aborted) deadlineController.abort();
|
|
833
|
+
else signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
797
834
|
let last;
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
835
|
+
try {
|
|
836
|
+
while (Date.now() < deadline) {
|
|
837
|
+
if (signal?.aborted) throw new Error(`sandbox wait interrupted while waiting for ${wanted.join(" or ")}`);
|
|
838
|
+
try {
|
|
839
|
+
last = await sandboxGet(opts, id, deadlineController.signal);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
if (signal?.aborted) {
|
|
842
|
+
throw new Error(`sandbox wait interrupted while waiting for ${wanted.join(" or ")}`);
|
|
843
|
+
}
|
|
844
|
+
if (Date.now() >= deadline) break;
|
|
845
|
+
throw error;
|
|
846
|
+
}
|
|
847
|
+
if (Date.now() >= deadline) break;
|
|
848
|
+
const state = String(last.observedState || "");
|
|
849
|
+
if (wanted.includes(state)) return last;
|
|
850
|
+
if (["FAILED", "TERMINATED"].includes(state) && !wanted.includes(state)) {
|
|
851
|
+
throw new Error(`sandbox ${id} entered ${state} while waiting for ${wanted.join(" or ")}`);
|
|
852
|
+
}
|
|
853
|
+
await new Promise((resolve2) => {
|
|
854
|
+
const done = () => {
|
|
855
|
+
clearTimeout(timer);
|
|
856
|
+
signal?.removeEventListener("abort", done);
|
|
857
|
+
resolve2();
|
|
858
|
+
};
|
|
859
|
+
const timer = setTimeout(done, Math.min(intervalMs, Math.max(0, deadline - Date.now())));
|
|
860
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
861
|
+
});
|
|
805
862
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
signal?.removeEventListener("abort", done);
|
|
810
|
-
resolve2();
|
|
811
|
-
};
|
|
812
|
-
const timer = setTimeout(done, Math.min(intervalMs, Math.max(0, deadline - Date.now())));
|
|
813
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
814
|
-
});
|
|
863
|
+
} finally {
|
|
864
|
+
clearTimeout(deadlineTimer);
|
|
865
|
+
signal?.removeEventListener("abort", abortFromCaller);
|
|
815
866
|
}
|
|
816
867
|
throw new Error(
|
|
817
868
|
`sandbox ${id} did not enter ${wanted.join(" or ")} within ${timeoutMs}ms (last state: ${last?.observedState || "unknown"})`
|
|
@@ -835,6 +886,7 @@ export {
|
|
|
835
886
|
HttpError,
|
|
836
887
|
isRetryableRequestError,
|
|
837
888
|
request,
|
|
889
|
+
apiKeyApiRequest,
|
|
838
890
|
actionList,
|
|
839
891
|
actionSearch,
|
|
840
892
|
actionCategories,
|