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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: xapi
3
- description: Access real-time external data via the xapi CLI — Twitter/X (including resolving and downloading tweet videos), Douyin/TikTok, Reddit, LinkedIn, Weibo, on-chain crypto data (price, holders, wallets, DEX, CEX), web/news/image/video/scholar search, AI text/image/video/speech generation and transcription, and SMS verification. Configure the Anthropic/OpenAI-compatible xAPI AI Gateway or the xAPI WebSocket Gateway for realtime voice, streaming ASR/TTS, and provider-native bidirectional sessions. Use when the user mentions xapi, wants to call a third-party API, asks what external services are available, or needs to connect an AI or realtime client to xAPI.
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
- This ensures correct types (strings, numbers, booleans) are preserved.
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 API
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.