xapi-to 0.1.19 → 0.1.20
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 +208 -1
- package/dist/chunk-TYY6JR6O.js +870 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +998 -670
- package/dist/openai-sandbox-client.d.ts +85 -0
- package/dist/openai-sandbox-client.js +285 -0
- package/examples/openai-agents-sandbox-local.ts +131 -0
- package/examples/sandbox-api-cli-openai.mjs +450 -0
- package/package.json +25 -3
- package/scripts/openai-sandbox-agent-e2e.ts +219 -0
- package/scripts/sandbox-playground-e2e.mjs +463 -0
- package/skills/xapi/SKILL.md +18 -5
- package/skills/xapi/guides/linkedin.md +55 -0
- package/skills/xapi/guides/sandbox.md +466 -0
- package/skills/xapi/guides/serper.md +124 -0
- package/src/client.ts +664 -0
- package/src/config.ts +160 -0
- package/src/openai-sandbox-client.ts +349 -0
- package/src/sandbox-client.ts +289 -0
package/skills/xapi/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: xapi
|
|
3
|
-
description: Access real-time external data via the xapi CLI — Twitter/X
|
|
3
|
+
description: Access real-time external data and managed cloud sandboxes via the xapi CLI — Twitter/X, social platforms, crypto, web/news search, AI generation, SMS verification, and auditable ephemeral compute. Configure the xAPI AI or WebSocket Gateways, or use sandbox run for automatic quote/create/execute/cleanup. Use when the user mentions xapi, external services, or sandbox compute.
|
|
4
4
|
metadata: {"openclaw":{"emoji":"x","requires":{"anyBins":["npx"]},"primaryEnv":"XAPI_KEY"}}
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -62,6 +62,17 @@ xapi offers two types of APIs under a unified interface:
|
|
|
62
62
|
|
|
63
63
|
Both types use the same discovery and call workflow. Use `--source capability` or `--source api` on commands that expose source filtering.
|
|
64
64
|
|
|
65
|
+
## Managed Sandbox Compute
|
|
66
|
+
Read `guides/sandbox.md` before creating a billable instance. For a one-shot
|
|
67
|
+
command, prefer `sandbox run`; it quotes, applies a price ceiling, waits,
|
|
68
|
+
executes, and terminates in `finally`:
|
|
69
|
+
```bash
|
|
70
|
+
npx xapi-to sandbox run --command 'python3 -c "print(6 * 7)"'
|
|
71
|
+
```
|
|
72
|
+
Use granular commands only for multi-step work. Keep the instance ID, terminate
|
|
73
|
+
in cleanup, and verify terminal state/cost afterward. Do not use `--keep` unless
|
|
74
|
+
the user explicitly wants a reusable, continuing-to-bill instance.
|
|
75
|
+
|
|
65
76
|
## Usage Workflow
|
|
66
77
|
|
|
67
78
|
**Critical rule:** Before calling any API, always use `get` to understand the required parameters.
|
|
@@ -362,7 +373,7 @@ npx xapi-to call web.search --input '{"q":"hello world"}'
|
|
|
362
373
|
npx xapi-to call serper.search --input '{"body":{"q":"hello world"}}'
|
|
363
374
|
```
|
|
364
375
|
|
|
365
|
-
|
|
376
|
+
Read `guides/serper.md` before using direct `serper.*` actions; it covers all current v7 actions, mini-batches, Reviews, billing, and the `web.search.*` boundary.
|
|
366
377
|
|
|
367
378
|
## Code Generation (`--code`)
|
|
368
379
|
|
|
@@ -436,10 +447,10 @@ Beyond built-in capabilities, xapi proxies **dozens** of third-party API service
|
|
|
436
447
|
- **Douyin** (`douyin`) — Douyin/TikTok API (videos, users, trending, comments)
|
|
437
448
|
- **Twitter API** (`twitter`) — Alternative Twitter data API
|
|
438
449
|
- **Reddit** (`reddit`) — Reddit API (posts, comments, subreddits, search)
|
|
439
|
-
- **LinkedIn** (`linkedin`) — LinkedIn API (person profiles & career history, company pages, posts & comments, job search)
|
|
450
|
+
- **LinkedIn** (`linkedin`) — LinkedIn API (person profiles & career history, company pages, posts & comments, job search). For career history, see `guides/linkedin.md` first — the profile endpoint silently omits `experience`/`education` for ordinary profiles
|
|
440
451
|
- **Weibo** (`weibo-app`) — Weibo API (user profiles, feeds, search, trending)
|
|
441
452
|
- **5SIM SMS** (`5sim-sms`) — SMS verification (virtual numbers, activation codes)
|
|
442
|
-
- **Serper API** (`serper`) — Google Search
|
|
453
|
+
- **Serper API** (`serper`) — 12 provider-native Google Search actions including web, images, news, maps, places, video, shopping, scholar, patents, autocomplete, Lens, and reviews. Eleven support mini-batch; Reviews does not. Read `guides/serper.md` before calling them
|
|
443
454
|
- **OpenRouter API** (`openrouter`) — Multi-model AI gateway (chat, embeddings, audio transcription/speech, video)
|
|
444
455
|
|
|
445
456
|
The full catalog also spans many other categories — crypto/on-chain data, CEX market data, stocks & macro, social platforms, news, weather, and more. Discover them with `search` / `services`.
|
|
@@ -465,16 +476,18 @@ When the user's task involves these workflows, read the corresponding guide file
|
|
|
465
476
|
|
|
466
477
|
- **`guides/twitter.md`** — Twitter/X (推特): read and paginate tweets/replies/media, download the highest-quality MP4 from a video tweet, advanced search, read long-form X Articles directly from `tweet_detail`, post tweets, reply, quote, like, retweet, OAuth binding
|
|
467
478
|
- **`guides/reddit.md`** — Reddit: user profiles, posts, comments, subreddit feeds, popular/news/games feeds, trending, search
|
|
468
|
-
- **`guides/linkedin.md`** — LinkedIn (领英): person profiles with career history, company pages, posts & comments, job search & job detail — every endpoint but job search is addressed by an ordinary LinkedIn page URL, and post comments need a numeric `urn` alongside `url`
|
|
479
|
+
- **`guides/linkedin.md`** — LinkedIn (领英): person profiles with career history, company pages, posts & comments, job search & job detail — every endpoint but job search is addressed by an ordinary LinkedIn page URL, and post comments need a numeric `urn` alongside `url`. **Read this guide before summarizing anyone's background:** `get__user__profile` returns `experience: null` / `education: null` for ordinary (non-creator) profiles because it reads the logged-out page, and the guide gives the fallback that recovers the real career history
|
|
469
480
|
- **`guides/tiktok.md`** — TikTok: user profiles, videos, comments, search, hashtags, music, live rooms, feed
|
|
470
481
|
- **`guides/douyin.md`** — Douyin (抖音): user profiles, videos, comments, hot search, hashtags, music, video mix/series
|
|
471
482
|
- **`guides/xiaohongshu.md`** — 小红书 (Xiaohongshu): user profiles, notes, comments, search, topics, products, creator inspiration
|
|
472
483
|
- **`guides/weibo.md`** — Weibo (微博): hot search, content search, user profiles, post details, comments, reposts, media
|
|
473
484
|
- **`guides/google_search.md`** — Google Search: web, realtime, news, image, video, scholar, maps, places, shopping
|
|
485
|
+
- **`guides/serper.md`** — direct Serper v7 API: 12 provider-native actions, object-or-array mini-batches, Reviews pagination and batch exception, Lens, dynamic per-credit billing, and the current Webpage service boundary
|
|
474
486
|
- **`guides/crypto.md`** — Crypto (加密货币): on-chain token price/overview/holders/security/OHLCV, wallet analytics, DEX pairs, CEX spot prices by symbol, news — covers contract-address vs symbol addressing and multi-chain
|
|
475
487
|
- **`guides/ai.md`** — AI (人工智能): synchronous or SSE-streamed text, embeddings, asynchronous image/video generation with `task wait`, text-to-speech, and speech-to-text
|
|
476
488
|
- **`guides/ai_gateway.md`** — xAPI AI Gateway: Claude Code and Anthropic/OpenAI SDK setup, model discovery, routing strategies, streaming, fallback, routing/billing headers, direct media endpoints, and known limitations
|
|
477
489
|
- **`guides/ws_gateway.md`** — xAPI WebSocket Gateway: OpenAI Realtime, streaming ASR/TTS, simultaneous interpretation, podcast generation, service/path routing, browser authentication, native binary protocols, limits, billing, close codes, and reconnects
|
|
490
|
+
- **`guides/sandbox.md`** — managed Sandbox compute: AI tool selection, one-shot and multi-step lifecycles, provider pinning, files, Cloudflare Web previews, suspension, GPU jobs, parallel agents, cleanup recovery, audit/history, and billing verification
|
|
478
491
|
- **`guides/sms.md`** — SMS verification: buy virtual phone numbers, receive verification codes, finish/cancel orders (5SIM)
|
|
479
492
|
|
|
480
493
|
## Security
|
|
@@ -8,6 +8,7 @@ Complete guide for LinkedIn operations via xAPI — person profiles with career
|
|
|
8
8
|
|
|
9
9
|
- [Key concept: everything is addressed by URL](#key-concept-everything-is-addressed-by-url)
|
|
10
10
|
- [Person data](#person-data)
|
|
11
|
+
- [When `experience` and `education` come back `null`](#when-experience-and-education-come-back-null)
|
|
11
12
|
- [Company data](#company-data)
|
|
12
13
|
- [Posts and comments](#posts-and-comments)
|
|
13
14
|
- [Jobs](#jobs)
|
|
@@ -56,6 +57,48 @@ One call returns the whole profile — no follow-up requests for individual sect
|
|
|
56
57
|
|
|
57
58
|
There are no separate `get__user__experience` / `educations` / `skills` / `honors` / `publications` endpoints in v2 — that was the older `username` → `urn` two-step API. If you were using those IDs, they now return `Action not found`.
|
|
58
59
|
|
|
60
|
+
### When `experience` and `education` come back `null`
|
|
61
|
+
|
|
62
|
+
`get__user__profile` reads the **logged-out** public page. LinkedIn renders the full career and school sections there only for creator/open profiles; on an ordinary member's page it serves a trimmed topcard. The call still succeeds with `code: 200`, so the failure is silent — you get identity, `current_company`, `educations_details`, `followers`, and `people_also_viewed`, but:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{ "position": null, "experience": null, "education": null, "about": "Don't use LinkedIn that much…" }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`null` here means **not rendered to logged-out visitors**, not "this person has no jobs listed". A truncated `about` ending in `…` is the same signal. Never summarize a person's background from a response in this state, and never report the profile as private or empty — fall back:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx xapi-to call icypeas-email.api_scrape_profile \
|
|
72
|
+
--input '{"method":"GET","params":{"url":"https://www.linkedin.com/in/ricky-wang-74b3a0194/"}}'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This returns a differently-shaped payload under `data.result`:
|
|
76
|
+
|
|
77
|
+
| Field | Contents |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `worksFor[]` | Current positions — `jobTitle`, `startDate`, `description`, nested `company` (industry, size, website, HQ) |
|
|
80
|
+
| `alumniOf[]` | Past positions — same shape plus `endDate` |
|
|
81
|
+
| `educations[]` | `name`, `degree`, `fieldsOfStudy[]`, `description` |
|
|
82
|
+
| `headline`, `description` | Headline and the full untruncated About text |
|
|
83
|
+
| `firstname`, `lastname`, `address`, `numOfConnections`, `skills[]`, `languages[]` | Identity and profile detail |
|
|
84
|
+
|
|
85
|
+
Check `data.status` before reading `data.result`: `FOUND` means real data, `NOT_FOUND` means `result` is `null` and this source has nothing for that URL either. Two caveats worth carrying into any summary: the snapshot is cached, so `headline` can lag the person's current role, and `startDate`/`endDate` are year-granular (often stamped to January), so treat them as approximate. `jobTitle` also reflects whatever the member typed, which can disagree with a company's own announcement — prefer cross-checking a role against `web.search` before asserting a title.
|
|
86
|
+
|
|
87
|
+
Do not reach for `contactout-api.v1_linkedin_enrich` as a fallback. Without a separate ContactOut contract it returns a fully-populated **sample profile** (`"Example Person"` at `"Legros, Smitham and Kessler"`) with `status_code: 200`, which is easy to mistake for real data, and it is the most expensive LinkedIn-adjacent action in the catalog.
|
|
88
|
+
|
|
89
|
+
### Person profile decision path
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
get__user__profile
|
|
93
|
+
├─ experience[] present → done, use it
|
|
94
|
+
└─ experience/education == null → icypeas-email.api_scrape_profile
|
|
95
|
+
├─ status == FOUND → read worksFor / alumniOf / educations
|
|
96
|
+
└─ status == NOT_FOUND → say the career history is unavailable;
|
|
97
|
+
fall back to web.search for public
|
|
98
|
+
bios, or twitter.user_by_screen_name
|
|
99
|
+
when the person came from an X handle
|
|
100
|
+
```
|
|
101
|
+
|
|
59
102
|
### Get a person's posts
|
|
60
103
|
|
|
61
104
|
```bash
|
|
@@ -144,6 +187,10 @@ This is the most expensive LinkedIn endpoint — search first, then fetch detail
|
|
|
144
187
|
npx xapi-to call linkedin.api_v1_linkedin_web__v2_get__user__profile \
|
|
145
188
|
--input '{"method":"GET","params":{"url":"https://www.linkedin.com/in/williamhgates/"}}'
|
|
146
189
|
|
|
190
|
+
# 1b. Only if experience/education came back null — career history from the fallback
|
|
191
|
+
npx xapi-to call icypeas-email.api_scrape_profile \
|
|
192
|
+
--input '{"method":"GET","params":{"url":"https://www.linkedin.com/in/williamhgates/"}}'
|
|
193
|
+
|
|
147
194
|
# 2. Their posts (take `urn` from each item for step 3)
|
|
148
195
|
npx xapi-to call linkedin.api_v1_linkedin_web__v2_get__user__posts \
|
|
149
196
|
--input '{"method":"GET","params":{"url":"https://www.linkedin.com/in/williamhgates/","page":1}}'
|
|
@@ -186,6 +233,12 @@ npx xapi-to call linkedin.api_v1_linkedin_web__v2_get__company__profile \
|
|
|
186
233
|
| `search__jobs` | Job search | `keywords` | `location`, `page` |
|
|
187
234
|
| `get__job__detail` | Full job posting | `url` | — |
|
|
188
235
|
|
|
236
|
+
One non-`linkedin` action belongs in this workflow:
|
|
237
|
+
|
|
238
|
+
| Action ID | Purpose | Required params | Notes |
|
|
239
|
+
|---|---|---|---|
|
|
240
|
+
| `icypeas-email.api_scrape_profile` | Career history for profiles the logged-out page hides | `url` | Fallback when `get__user__profile` returns `experience: null`. Check `data.status == "FOUND"`. |
|
|
241
|
+
|
|
189
242
|
Detail-style endpoints (`get__*__profile`, `get__post__detail`) are the cheapest; list-style endpoints (posts, comments, job search) cost more per call, and `get__job__detail` is the most expensive. Run `npx xapi-to get <action-id>` for the current `meta.pricing` rather than assuming these ratios hold.
|
|
190
243
|
|
|
191
244
|
## Error Handling
|
|
@@ -195,4 +248,6 @@ Detail-style endpoints (`get__*__profile`, `get__post__detail`) are the cheapest
|
|
|
195
248
|
- **`must have required property 'urn'`** — `get__post__comments` needs the numeric `urn` alongside `url`. See [Get comments on a post](#get-comments-on-a-post).
|
|
196
249
|
- **`must match pattern "^[0-9]+$"` on `/params/urn`** — you passed the prefixed `urn:li:activity:<id>` form. Send the digits only.
|
|
197
250
|
- **`API Token lacks required permissions`** (upstream `403`) — the account's upstream provider token has no LinkedIn scope. This is an account entitlement, not a parameter problem; enable it in the provider dashboard.
|
|
251
|
+
- **`experience: null` / `education: null` / `about` ending in `…` on `get__user__profile`** — not an error and not a private profile: the logged-out page LinkedIn serves for ordinary members omits those sections. Fall back to `icypeas-email.api_scrape_profile`. See [When `experience` and `education` come back `null`](#when-experience-and-education-come-back-null).
|
|
252
|
+
- **A ContactOut response naming `Example Person` at `Legros, Smitham and Kessler`** — `contactout-api.*` returned its sample payload because the account has no ContactOut entitlement. It carries `status_code: 200` and a `message` pointing at a sales call. Discard it; never summarize from it.
|
|
198
253
|
- **Empty `data[]` on a valid URL** — either the page is private/deleted, or you paginated past the end. LinkedIn also rate-limits aggressively; retry with backoff rather than in a tight loop.
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# Managed Sandbox Compute Guide
|
|
2
|
+
|
|
3
|
+
Use xAPI Sandbox when a user or AI agent needs an isolated cloud computer for
|
|
4
|
+
code execution, file processing, CI reproduction, a temporary Web/API preview,
|
|
5
|
+
GPU work, or a resumable multi-step job. It is a billable lifecycle service,
|
|
6
|
+
not an ordinary per-call action, so cleanup and audit are part of task success.
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- [Choose the shortest safe lifecycle](#choose-the-shortest-safe-lifecycle)
|
|
11
|
+
- [Authentication and gateway selection](#authentication-and-gateway-selection)
|
|
12
|
+
- [Inspect offerings and quote first](#inspect-offerings-and-quote-first)
|
|
13
|
+
- [One-shot execution](#one-shot-execution)
|
|
14
|
+
- [Multi-step agent lifecycle](#multi-step-agent-lifecycle)
|
|
15
|
+
- [Files and artifacts](#files-and-artifacts)
|
|
16
|
+
- [Web preview and background processes](#web-preview-and-background-processes)
|
|
17
|
+
- [Suspend and resume](#suspend-and-resume)
|
|
18
|
+
- [GPU jobs](#gpu-jobs)
|
|
19
|
+
- [Parallel agents](#parallel-agents)
|
|
20
|
+
- [OpenAI SandboxAgent with xAPI DeepSeek](#openai-sandboxagent-with-xapi-deepseek)
|
|
21
|
+
- [Audit, history, and billing](#audit-history-and-billing)
|
|
22
|
+
- [Run the real Playground acceptance suite](#run-the-real-playground-acceptance-suite)
|
|
23
|
+
- [Failure and interruption recovery](#failure-and-interruption-recovery)
|
|
24
|
+
- [AI operating rules](#ai-operating-rules)
|
|
25
|
+
|
|
26
|
+
## Choose the shortest safe lifecycle
|
|
27
|
+
|
|
28
|
+
| Need | Preferred command | Cleanup behavior |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Run one command and get stdout | `sandbox run` | Terminates automatically |
|
|
31
|
+
| Several exec/file calls | `create` + primitives | Agent must terminate |
|
|
32
|
+
| Inspect price/capabilities | `offerings`, `quote` | No instance created |
|
|
33
|
+
| Publish a temporary port | `port` after starting a server | Terminate afterward |
|
|
34
|
+
| Pause a reusable workspace | `suspend` | Storage may keep billing |
|
|
35
|
+
| Inspect prior work/cost | `history`, `get`, `audit` | Read-only |
|
|
36
|
+
|
|
37
|
+
Prefer `sandbox run` whenever the task fits one remote shell command. A shorter
|
|
38
|
+
lifecycle reduces orphan risk and returns one machine-readable JSON result.
|
|
39
|
+
|
|
40
|
+
## Authentication and gateway selection
|
|
41
|
+
|
|
42
|
+
The CLI reads `XAPI_KEY`, then `XAPI_API_KEY`, then `~/.xapi/config.json`.
|
|
43
|
+
Do not print, interpolate into a URL, or pass the key inside the remote command.
|
|
44
|
+
The CLI sends Sandbox credentials only to `*.xapi.to` or localhost.
|
|
45
|
+
|
|
46
|
+
Production uses `sandbox.xapi.to`. The test service is selected explicitly:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export XAPI_SANDBOX_HOST=sandbox.test.xapi.to
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Omit `--provider` (or use `--provider auto`) for lowest-price compatible
|
|
53
|
+
selection. Pin only when the task or test requires a particular provider:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx xapi-to sandbox offerings --provider cf-edge --format table
|
|
57
|
+
npx xapi-to sandbox quote --provider daytona --capabilities exec,files
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Provider pinning derives a controlled hostname such as
|
|
61
|
+
`cf-edge.sandbox.test.xapi.to`; it does not accept arbitrary provider URLs.
|
|
62
|
+
For production, canonical `--provider daytona` and `--provider e2b` are mapped
|
|
63
|
+
to the deployed `daytona-sandbox.sandbox.xapi.to` and
|
|
64
|
+
`e2b-sandbox.sandbox.xapi.to` aliases; their test hosts remain
|
|
65
|
+
`daytona.sandbox.test.xapi.to` and `e2b.sandbox.test.xapi.to`.
|
|
66
|
+
Available providers and capabilities can change, so inspect `offerings` rather
|
|
67
|
+
than assuming a static capability matrix.
|
|
68
|
+
|
|
69
|
+
## Inspect offerings and quote first
|
|
70
|
+
|
|
71
|
+
`offerings` shows provider-declared resources, capabilities, lifecycle support,
|
|
72
|
+
and hourly estimates. `quote` applies requirements without creating or billing
|
|
73
|
+
an instance:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx xapi-to sandbox offerings --format table
|
|
77
|
+
|
|
78
|
+
npx xapi-to sandbox quote \
|
|
79
|
+
--capabilities exec,files \
|
|
80
|
+
--cpu 2 \
|
|
81
|
+
--memory 4 \
|
|
82
|
+
--max-hourly-usd 0.20 \
|
|
83
|
+
--format pretty
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use `--format table` for a compact comparison and JSON when a complete quote ID
|
|
87
|
+
or nested rate card must be copied. Table truncation is marked with `…`.
|
|
88
|
+
|
|
89
|
+
Use `--requirements '<json>'` for fields that do not have a shortcut. Treat
|
|
90
|
+
`--max-hourly-usd` as a hard guardrail chosen before creation. A quote is
|
|
91
|
+
short-lived; create promptly or quote again.
|
|
92
|
+
|
|
93
|
+
## One-shot execution
|
|
94
|
+
|
|
95
|
+
`sandbox run` performs quote → create → wait for `RUNNING` → exec → terminate →
|
|
96
|
+
read final cost. Its default price ceiling is `$0.20/hour`:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx xapi-to sandbox run \
|
|
100
|
+
--capabilities exec \
|
|
101
|
+
--command 'python3 -c "print(sum(range(1000)))"' \
|
|
102
|
+
--format pretty
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Arguments after a bare `--` are joined into the remote command:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx xapi-to sandbox run -- node --version
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Read these output fields first:
|
|
112
|
+
|
|
113
|
+
- `result.exitCode`, `result.stdout`, `result.stderr`: remote result;
|
|
114
|
+
- `cleanup.operationStatus`, `cleanup.state`: teardown result;
|
|
115
|
+
- `finalState`: should be `TERMINATED` (or provider-terminal `FAILED`);
|
|
116
|
+
- `totalCost`: service-calculated cost, not a client estimate.
|
|
117
|
+
|
|
118
|
+
A remote non-zero exit code becomes the local CLI exit code after cleanup, so
|
|
119
|
+
shells and AI runners can detect failure without parsing stdout.
|
|
120
|
+
|
|
121
|
+
`--keep` suppresses automatic termination. Use it only after the user explicitly
|
|
122
|
+
asks to retain the instance and understands that billing continues.
|
|
123
|
+
|
|
124
|
+
## Multi-step agent lifecycle
|
|
125
|
+
|
|
126
|
+
Use granular commands when an agent must alternate between files and commands.
|
|
127
|
+
Capture the instance ID without logging credentials:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
box_json="$(npx xapi-to sandbox create \
|
|
131
|
+
--capabilities exec,files \
|
|
132
|
+
--idempotency-key "job-${JOB_ID}" \
|
|
133
|
+
--wait)"
|
|
134
|
+
box_id="$(printf '%s' "$box_json" | jq -r '.id')"
|
|
135
|
+
|
|
136
|
+
npx xapi-to sandbox wait "$box_id" --state RUNNING --wait-timeout 5m
|
|
137
|
+
|
|
138
|
+
cleanup() {
|
|
139
|
+
npx xapi-to sandbox terminate "$box_id" --wait-timeout 5m || true
|
|
140
|
+
}
|
|
141
|
+
trap cleanup EXIT INT TERM
|
|
142
|
+
|
|
143
|
+
npx xapi-to sandbox file write "$box_id" task.md --file ./task.md
|
|
144
|
+
npx xapi-to sandbox exec "$box_id" --command 'npm test' --timeout 120
|
|
145
|
+
npx xapi-to sandbox file read "$box_id" report.json --output ./report.json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Use a stable business `--idempotency-key` when the caller might repeat create
|
|
149
|
+
after a lost response. Do not blindly repeat mutations with a new key: the first
|
|
150
|
+
request may already have created a billable instance.
|
|
151
|
+
|
|
152
|
+
The CLI rejects unknown Sandbox flags before making a request. Exact
|
|
153
|
+
`--offering-id` selection cannot be combined with `--max-hourly-usd`; select by
|
|
154
|
+
requirements under a ceiling or create from a previously checked quote instead.
|
|
155
|
+
Successful create output returns `clientIdempotencyKey`. If `create --wait`
|
|
156
|
+
fails after acceptance, retain the structured `instanceId`, `observedState`,
|
|
157
|
+
`clientIdempotencyKey`, and recovery commands from stderr, then inspect and
|
|
158
|
+
terminate the instance as appropriate.
|
|
159
|
+
|
|
160
|
+
## Files and artifacts
|
|
161
|
+
|
|
162
|
+
Write inline text or a local file:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npx xapi-to sandbox file write <id> instructions.txt --content 'Run tests.'
|
|
166
|
+
npx xapi-to sandbox file write <id> input.csv --file ./input.csv
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Read and list artifacts:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npx xapi-to sandbox file list <id> --path . --depth 3
|
|
173
|
+
npx xapi-to sandbox file read <id> output.json
|
|
174
|
+
npx xapi-to sandbox file read <id> output.zip --output ./output.zip
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Local `--output` uses create-new semantics and refuses to overwrite an existing
|
|
178
|
+
file. The CLI base64-encodes local input bytes so binary files survive transfer.
|
|
179
|
+
|
|
180
|
+
## Web preview and background processes
|
|
181
|
+
|
|
182
|
+
For providers that declare both `backgroundExec` and `ports`, use the explicit
|
|
183
|
+
provider-managed background command. Daytona needs this mode because deleting a
|
|
184
|
+
foreground command session also kills shell-backgrounded child processes:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
box_json="$(npx xapi-to sandbox create \
|
|
188
|
+
--provider daytona \
|
|
189
|
+
--capabilities exec,backgroundExec,ports \
|
|
190
|
+
--wait)"
|
|
191
|
+
box_id="$(printf '%s' "$box_json" | jq -r '.id')"
|
|
192
|
+
port=25319
|
|
193
|
+
|
|
194
|
+
cleanup() { npx xapi-to sandbox terminate "$box_id" --provider daytona || true; }
|
|
195
|
+
trap cleanup EXIT INT TERM
|
|
196
|
+
|
|
197
|
+
npx xapi-to sandbox exec "$box_id" --provider daytona --background --command \
|
|
198
|
+
"python3 -m http.server $port --bind 0.0.0.0"
|
|
199
|
+
npx xapi-to sandbox port "$box_id" "$port" --provider daytona
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`--background` returning a session/command ID is only launch acknowledgement.
|
|
203
|
+
Poll the public URL with bounded retries and verify an expected marker. If the
|
|
204
|
+
port response contains `headers`, include them in external requests; they can
|
|
205
|
+
carry a provider preview token. Do not emulate this mode with `nohup ... &` on
|
|
206
|
+
an Offering that does not declare `backgroundExec`.
|
|
207
|
+
|
|
208
|
+
Cloudflare currently uses its provider-specific command/preview behavior rather
|
|
209
|
+
than the standard background session capability. Pin `cf-edge` only when the
|
|
210
|
+
user explicitly wants Cloudflare. Port `8080` is the currently verified preview
|
|
211
|
+
path for the deployed bridge:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
box_json="$(npx xapi-to sandbox create \
|
|
215
|
+
--provider cf-edge \
|
|
216
|
+
--capabilities exec,files,ports \
|
|
217
|
+
--wait)"
|
|
218
|
+
box_id="$(printf '%s' "$box_json" | jq -r '.id')"
|
|
219
|
+
port=8080
|
|
220
|
+
|
|
221
|
+
cleanup() { npx xapi-to sandbox terminate "$box_id" --provider cf-edge || true; }
|
|
222
|
+
trap cleanup EXIT INT TERM
|
|
223
|
+
|
|
224
|
+
npx xapi-to sandbox file write "$box_id" index.html \
|
|
225
|
+
--provider cf-edge \
|
|
226
|
+
--content '<!doctype html><h1>xAPI preview</h1>'
|
|
227
|
+
|
|
228
|
+
npx xapi-to sandbox exec "$box_id" --provider cf-edge --command \
|
|
229
|
+
"nohup python3 -m http.server $port >/tmp/server.log 2>&1 & \
|
|
230
|
+
for i in 1 2 3 4 5 6 7 8 9 10; do \
|
|
231
|
+
curl -sf http://127.0.0.1:$port/ && exit 0; sleep 1; done; \
|
|
232
|
+
cat /tmp/server.log >&2; exit 1"
|
|
233
|
+
|
|
234
|
+
npx xapi-to sandbox port "$box_id" "$port" --provider cf-edge
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Validate that the returned public URL serves the expected marker before calling
|
|
238
|
+
the workflow successful. Quick Tunnel DNS/TLS readiness can be intermittent, so
|
|
239
|
+
use bounded retries (for example, one request every two seconds for up to two
|
|
240
|
+
minutes). If it still fails, verify localhost again, record the URL/error, and
|
|
241
|
+
terminate instead of leaving the instance billing. The URL stops working after
|
|
242
|
+
termination. Quick Tunnels are for previews; use a stable, supported named
|
|
243
|
+
tunnel or application deployment for production traffic.
|
|
244
|
+
|
|
245
|
+
## Suspend and resume
|
|
246
|
+
|
|
247
|
+
Check offering lifecycle fields first because not every provider supports an
|
|
248
|
+
explicit suspend operation:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npx xapi-to sandbox offerings --format pretty
|
|
252
|
+
npx xapi-to sandbox suspend <id>
|
|
253
|
+
npx xapi-to sandbox get <id>
|
|
254
|
+
npx xapi-to sandbox resume <id>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The CLI waits for `SUSPENDED` and `RUNNING` by default. Files may persist while
|
|
258
|
+
memory/processes do not; rely on the selected offering's declared lifecycle
|
|
259
|
+
semantics. Suspension can reduce compute cost but storage may still accrue cost.
|
|
260
|
+
If `lifecycle.suspension.supported` is false (as with a current cf-edge
|
|
261
|
+
offering), do not call suspend/resume; terminate and create a new instance.
|
|
262
|
+
|
|
263
|
+
## GPU jobs
|
|
264
|
+
|
|
265
|
+
Request GPU resources instead of assuming a provider or model. The current
|
|
266
|
+
RunPod offering is a managed GPU resource without standard `exec`/`files`, so
|
|
267
|
+
inspect its declared extension and obtain connection details instead of sending
|
|
268
|
+
an impossible shell command:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
npx xapi-to sandbox quote \
|
|
272
|
+
--gpu-count 1 \
|
|
273
|
+
--gpu-model L4 \
|
|
274
|
+
--capabilities exec \
|
|
275
|
+
--max-hourly-usd 2.00
|
|
276
|
+
|
|
277
|
+
npx xapi-to sandbox create \
|
|
278
|
+
--provider runpod \
|
|
279
|
+
--gpu-count 1 \
|
|
280
|
+
--max-hourly-usd 2.00 \
|
|
281
|
+
--wait
|
|
282
|
+
|
|
283
|
+
npx xapi-to sandbox extension <id> runpod.connection_info \
|
|
284
|
+
--provider runpod \
|
|
285
|
+
--input '{}'
|
|
286
|
+
|
|
287
|
+
npx xapi-to sandbox terminate <id> --provider runpod
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
GPU work is usually more expensive. Quote first, set a deliberate ceiling, use
|
|
291
|
+
a command timeout, and terminate immediately after artifacts are retrieved.
|
|
292
|
+
|
|
293
|
+
## Parallel agents
|
|
294
|
+
|
|
295
|
+
Give each agent a separate instance. Do not share a mutable workspace when the
|
|
296
|
+
goal is isolation. Use unique idempotency keys and record every instance ID.
|
|
297
|
+
Run cleanup for all IDs even if one agent fails; then verify `sandbox list` has
|
|
298
|
+
no active instance from the job.
|
|
299
|
+
|
|
300
|
+
Limit concurrency based on budget. Parallel creation multiplies reservation and
|
|
301
|
+
running cost, even when the individual hourly quote is small.
|
|
302
|
+
|
|
303
|
+
## OpenAI SandboxAgent with xAPI DeepSeek
|
|
304
|
+
|
|
305
|
+
The OpenAI Agents SDK keeps the model provider and sandbox provider separate.
|
|
306
|
+
Use the SDK's OpenAI-compatible model provider for DeepSeek through
|
|
307
|
+
`https://ai.xapi.to/v1`, and the xAPI adapter for Sandbox compute:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { OpenAIProvider, Runner } from '@openai/agents';
|
|
311
|
+
import { Manifest, SandboxAgent, shell } from '@openai/agents/sandbox';
|
|
312
|
+
import { XapiAgentsSandboxClient } from 'xapi-to/openai-sandbox';
|
|
313
|
+
|
|
314
|
+
const sandboxApiKey = process.env.XAPI_SANDBOX_KEY;
|
|
315
|
+
const aiApiKey = process.env.XAPI_AI_KEY;
|
|
316
|
+
if (!sandboxApiKey) throw new Error('XAPI_SANDBOX_KEY is required');
|
|
317
|
+
if (!aiApiKey) throw new Error('XAPI_AI_KEY is required');
|
|
318
|
+
|
|
319
|
+
const sandbox = new XapiAgentsSandboxClient({
|
|
320
|
+
apiKey: sandboxApiKey,
|
|
321
|
+
sandboxHost: 'sandbox.test.xapi.to',
|
|
322
|
+
provider: 'daytona',
|
|
323
|
+
model: 'deepseek-v4-pro',
|
|
324
|
+
});
|
|
325
|
+
const modelProvider = new OpenAIProvider({
|
|
326
|
+
apiKey: aiApiKey,
|
|
327
|
+
baseURL: 'https://ai.xapi.to/v1',
|
|
328
|
+
useResponses: false,
|
|
329
|
+
strictFeatureValidation: true,
|
|
330
|
+
});
|
|
331
|
+
const runner = new Runner({ modelProvider, tracingDisabled: true });
|
|
332
|
+
const agent = new SandboxAgent({
|
|
333
|
+
name: 'xAPI DeepSeek sandbox agent',
|
|
334
|
+
model: 'deepseek-v4-pro',
|
|
335
|
+
defaultManifest: new Manifest({ root: sandbox.workspaceRoot }),
|
|
336
|
+
capabilities: [shell()],
|
|
337
|
+
instructions: 'Use shell to complete and verify the task.',
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const result = await runner.run(agent, 'Write SDK_OK=42 to result.txt and read it.', {
|
|
342
|
+
maxTurns: 8,
|
|
343
|
+
sandbox: { client: sandbox },
|
|
344
|
+
});
|
|
345
|
+
console.log(result.finalOutput);
|
|
346
|
+
} finally {
|
|
347
|
+
await sandbox.lastSession?.close();
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Use `useResponses: false` because `ai.xapi.to` currently implements the OpenAI
|
|
352
|
+
Chat Completions-compatible protocol. Disable tracing unless a separate OpenAI
|
|
353
|
+
telemetry credential is configured; do not send an xAPI key to OpenAI tracing.
|
|
354
|
+
Keep `XAPI_AI_KEY` and `XAPI_SANDBOX_KEY` separate for a mixed environment:
|
|
355
|
+
the former is sent only to production `ai.xapi.to`, while the latter is sent
|
|
356
|
+
only to `sandbox.test.xapi.to`. A production key with both permissions may be
|
|
357
|
+
injected into both variables, but a Sandbox test key must not be assumed to
|
|
358
|
+
have production AI Gateway access.
|
|
359
|
+
The current adapter honestly supports an empty Manifest and Shell capability.
|
|
360
|
+
It rejects Manifest file/mount/environment materialization until those mappings
|
|
361
|
+
are implemented and tested.
|
|
362
|
+
|
|
363
|
+
Run the real SDK + DeepSeek + Daytona acceptance test from the CLI repository:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
XAPI_SANDBOX_KEY='<sandbox-test-key>' \
|
|
367
|
+
XAPI_AI_KEY='<ai-production-key>' \
|
|
368
|
+
npm run test:sandbox:openai -- \
|
|
369
|
+
--host sandbox.test.xapi.to \
|
|
370
|
+
--provider daytona \
|
|
371
|
+
--model deepseek-v4-pro
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The script writes a redacted report, audits operations/events/usage/billing,
|
|
375
|
+
terminates its instance, and fails if any active test instance remains.
|
|
376
|
+
|
|
377
|
+
## Audit, history, and billing
|
|
378
|
+
|
|
379
|
+
Inspect current state and service-calculated total:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
npx xapi-to sandbox get <id> --format pretty
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Read individual audit streams:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
npx xapi-to sandbox audit <id> --kind operations
|
|
389
|
+
npx xapi-to sandbox audit <id> --kind events
|
|
390
|
+
npx xapi-to sandbox audit <id> --kind usageSegments
|
|
391
|
+
npx xapi-to sandbox audit <id> --kind billingPeriods
|
|
392
|
+
npx xapi-to sandbox history --state HISTORY --page-size 100
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
`history` is a separate paginated endpoint for prior instances; it is not an
|
|
396
|
+
`audit --kind`. Filter it with `--search`, `--from`, and `--to` when reconciling
|
|
397
|
+
a specific agent run.
|
|
398
|
+
|
|
399
|
+
For acceptance, verify:
|
|
400
|
+
|
|
401
|
+
1. create/exec/file/port/terminate operations have terminal success statuses;
|
|
402
|
+
2. state events reach `TERMINATED`;
|
|
403
|
+
3. no usage segment or billing period remains open;
|
|
404
|
+
4. `totalCost` agrees with settled billing periods;
|
|
405
|
+
5. `sandbox list` shows no active instance from the test.
|
|
406
|
+
|
|
407
|
+
Use the returned billing data rather than recomputing cost from wall-clock time.
|
|
408
|
+
|
|
409
|
+
## Run the real Playground acceptance suite
|
|
410
|
+
|
|
411
|
+
From an xapi-cli development checkout, run the same nine real workflows shown
|
|
412
|
+
in the Web Playground. The suite uses normal CLI configuration, never accepts a
|
|
413
|
+
key on argv, records audit/billing evidence, terminates every tracked instance
|
|
414
|
+
in `finally`, and fails if any instance created after its baseline remains
|
|
415
|
+
ACTIVE (unrelated pre-existing account instances are still reported):
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
npm run test:sandbox:playground -- --host sandbox.test.xapi.to
|
|
419
|
+
|
|
420
|
+
# Focus a rerun or avoid the higher-cost GPU reservation
|
|
421
|
+
npm run test:sandbox:playground -- --host sandbox.test.xapi.to --only 8,9
|
|
422
|
+
npm run test:sandbox:playground -- --host sandbox.test.xapi.to --skip-gpu
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The JSON report path is printed at completion. A provider capacity or HTTP 402
|
|
426
|
+
balance error is an external test precondition failure, not proof that the
|
|
427
|
+
scenario works; retain the error and a previous successful provider-specific
|
|
428
|
+
report separately. For Cloudflare, success requires an external HTTP 200 with
|
|
429
|
+
the expected page marker, not merely a returned Quick Tunnel hostname.
|
|
430
|
+
|
|
431
|
+
## Failure and interruption recovery
|
|
432
|
+
|
|
433
|
+
`sandbox run` handles ordinary exceptions, remote non-zero exits, `SIGINT`, and
|
|
434
|
+
`SIGTERM` by attempting termination before it exits. `SIGKILL`, machine loss, or
|
|
435
|
+
a network partition cannot run local cleanup.
|
|
436
|
+
|
|
437
|
+
After an uncertain interruption:
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
npx xapi-to sandbox list --format table
|
|
441
|
+
npx xapi-to sandbox get <suspected-id>
|
|
442
|
+
npx xapi-to sandbox terminate <suspected-id> --wait-timeout 5m
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
If terminate returns a state-change conflict, inspect state and retry after the
|
|
446
|
+
in-flight transition finishes. Do not treat an accepted operation response as
|
|
447
|
+
completion; wait for the instance's observed terminal state.
|
|
448
|
+
|
|
449
|
+
## AI operating rules
|
|
450
|
+
|
|
451
|
+
When exposing Sandbox to an AI agent:
|
|
452
|
+
|
|
453
|
+
1. Inject the xAPI key in the tool execution layer; never place it in prompts,
|
|
454
|
+
files, environment dumps, remote commands, logs, or model-visible output.
|
|
455
|
+
2. Start with `offerings`/`quote` when selection or budget is uncertain.
|
|
456
|
+
3. Prefer `sandbox run` for one-shot work and granular primitives only when the
|
|
457
|
+
task needs persistent state across calls.
|
|
458
|
+
4. Set capabilities and a price ceiling narrowly enough for the task.
|
|
459
|
+
5. Use `--background` only when the selected Offering declares
|
|
460
|
+
`backgroundExec`; then verify the listening port independently.
|
|
461
|
+
6. Treat instance IDs as cleanup obligations and keep them in structured state.
|
|
462
|
+
7. Put termination in `finally`; on interruption, enumerate and reconcile any
|
|
463
|
+
uncertain instances.
|
|
464
|
+
8. Report stdout, exit code, final state, cost, and cleanup outcome separately.
|
|
465
|
+
9. Never claim success from page/API structure alone—execute the relevant path,
|
|
466
|
+
verify its artifact or public URL, then check audit and residual instances.
|