xapi-to 0.1.19 → 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.
@@ -0,0 +1,520 @@
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
+ - [Product boundary](#product-boundary)
11
+ - [Choose the shortest safe lifecycle](#choose-the-shortest-safe-lifecycle)
12
+ - [Authentication and gateway selection](#authentication-and-gateway-selection)
13
+ - [Inspect offerings and quote first](#inspect-offerings-and-quote-first)
14
+ - [One-shot execution](#one-shot-execution)
15
+ - [Multi-step client lifecycle](#multi-step-client-lifecycle)
16
+ - [Files and artifacts](#files-and-artifacts)
17
+ - [Web preview and background processes](#web-preview-and-background-processes)
18
+ - [Suspend and resume](#suspend-and-resume)
19
+ - [GPU jobs](#gpu-jobs)
20
+ - [Parallel isolated instances](#parallel-isolated-instances)
21
+ - [OpenAI SandboxAgent integration example](#openai-sandboxagent-integration-example)
22
+ - [Audit, history, and billing](#audit-history-and-billing)
23
+ - [Run the real Playground recipe acceptance suite](#run-the-real-playground-recipe-acceptance-suite)
24
+ - [Failure and interruption recovery](#failure-and-interruption-recovery)
25
+ - [AI operating rules](#ai-operating-rules)
26
+
27
+ ## Product boundary
28
+
29
+ xAPI is the Sandbox resource and capability provider. The CLI is a thin client
30
+ for discovery, quote, lifecycle, exec, files, ports, provider extensions,
31
+ history, audit, and billing. It does not implement prompts, model loops, memory,
32
+ multi-agent orchestration, job DAGs, queues, or human approval workflows.
33
+
34
+ Agent, CI, browser, and data examples in this guide are client-side recipes that
35
+ compose Sandbox primitives. They are not additional Gateway workflow APIs.
36
+ Provider-native features remain valid Sandbox extensions when the live Offering
37
+ declares their schemas, limits, state effects, and billing behavior.
38
+
39
+ ## Choose the shortest safe lifecycle
40
+
41
+ | Need | Preferred command | Cleanup behavior |
42
+ | ------------------------------ | ------------------------------ | ------------------------ |
43
+ | Run one command and get stdout | `sandbox run` | Terminates automatically |
44
+ | Several exec/file calls | `create` + primitives | Agent must terminate |
45
+ | Inspect price/capabilities | `offerings`, `quote` | No instance created |
46
+ | Publish a temporary port | `port` after starting a server | Terminate afterward |
47
+ | Pause a reusable workspace | `suspend` | Storage may keep billing |
48
+ | Inspect prior work/cost | `history`, `get`, `audit` | Read-only |
49
+
50
+ Prefer `sandbox run` whenever the task fits one remote shell command. A shorter
51
+ lifecycle reduces orphan risk and returns one machine-readable JSON result.
52
+
53
+ ## Authentication and gateway selection
54
+
55
+ The CLI reads `XAPI_KEY`, then `XAPI_API_KEY`, then `~/.xapi/config.json`.
56
+ Do not print, interpolate into a URL, or pass the key inside the remote command.
57
+ The CLI sends Sandbox credentials only to `*.xapi.to` or localhost.
58
+
59
+ Production uses `sandbox.xapi.to`. The test service is selected explicitly:
60
+
61
+ ```bash
62
+ export XAPI_SANDBOX_HOST=sandbox.test.xapi.to
63
+ ```
64
+
65
+ Omit `--provider` (or use `--provider auto`) for lowest-price compatible
66
+ selection. Pin only when the task or test requires a particular provider:
67
+
68
+ ```bash
69
+ npx xapi-to sandbox offerings --provider cf-edge --format table
70
+ npx xapi-to sandbox quote --provider daytona --capabilities exec,files
71
+ ```
72
+
73
+ Provider pinning derives a controlled hostname such as
74
+ `cf-edge.sandbox.test.xapi.to`; it does not accept arbitrary provider URLs.
75
+ For production, canonical `--provider daytona` and `--provider e2b` are mapped
76
+ to the deployed `daytona-sandbox.sandbox.xapi.to` and
77
+ `e2b-sandbox.sandbox.xapi.to` aliases; their test hosts remain
78
+ `daytona.sandbox.test.xapi.to` and `e2b.sandbox.test.xapi.to`.
79
+ Available providers and capabilities can change, so inspect `offerings` rather
80
+ than assuming a static capability matrix.
81
+
82
+ ## Inspect offerings and quote first
83
+
84
+ `offerings` shows provider-declared resources, capabilities, lifecycle support,
85
+ and hourly estimates. `quote` applies requirements without creating or billing
86
+ an instance:
87
+
88
+ ```bash
89
+ npx xapi-to sandbox offerings --format table
90
+
91
+ npx xapi-to sandbox quote \
92
+ --capabilities exec,files \
93
+ --cpu 2 \
94
+ --memory 4 \
95
+ --min-runtime 24h \
96
+ --max-hourly-usd 0.20 \
97
+ --format pretty
98
+ ```
99
+
100
+ Use `--format table` for a compact comparison and JSON when a complete quote ID
101
+ or nested rate card must be copied. Table truncation is marked with `…`.
102
+
103
+ Use `--requirements '<json>'` for fields that do not have a shortcut. Treat
104
+ `--max-hourly-usd` as a hard guardrail chosen before creation. A quote is
105
+ short-lived; create promptly or quote again.
106
+
107
+ ## One-shot execution
108
+
109
+ `sandbox run` performs quote → create → wait for `RUNNING` → exec → terminate →
110
+ read final cost. Its default price ceiling is `$0.20/hour`:
111
+
112
+ ```bash
113
+ npx xapi-to sandbox run \
114
+ --capabilities exec \
115
+ --command 'python3 -c "print(sum(range(1000)))"' \
116
+ --format pretty
117
+ ```
118
+
119
+ Arguments after a bare `--` are joined into the remote command:
120
+
121
+ ```bash
122
+ npx xapi-to sandbox run -- node --version
123
+ ```
124
+
125
+ Read these output fields first:
126
+
127
+ - `result.exitCode`, `result.stdout`, `result.stderr`: remote result;
128
+ - `cleanup.operationStatus`, `cleanup.state`: teardown result;
129
+ - `finalState`: should be `TERMINATED` (or provider-terminal `FAILED`);
130
+ - `totalCost`: service-calculated cost, not a client estimate.
131
+
132
+ A remote non-zero exit code becomes the local CLI exit code after cleanup, so
133
+ shells and AI runners can detect failure without parsing stdout.
134
+
135
+ `--keep` suppresses automatic termination. Use it only after the user explicitly
136
+ asks to retain the instance and understands that billing continues.
137
+
138
+ ## Multi-step client lifecycle
139
+
140
+ Use granular commands when a client must alternate between files and commands.
141
+ Capture the instance ID without logging credentials:
142
+
143
+ ```bash
144
+ box_json="$(npx xapi-to sandbox create \
145
+ --capabilities exec,files \
146
+ --idempotency-key "job-${JOB_ID}" \
147
+ --wait)"
148
+ box_id="$(printf '%s' "$box_json" | jq -r '.id')"
149
+
150
+ npx xapi-to sandbox wait "$box_id" --state RUNNING --wait-timeout 5m
151
+
152
+ cleanup() {
153
+ npx xapi-to sandbox terminate "$box_id" --wait-timeout 5m || true
154
+ }
155
+ trap cleanup EXIT INT TERM
156
+
157
+ npx xapi-to sandbox file write "$box_id" task.md --file ./task.md
158
+ npx xapi-to sandbox exec "$box_id" --command 'npm test' --timeout 120
159
+ npx xapi-to sandbox file read "$box_id" report.json --output ./report.json
160
+ ```
161
+
162
+ Use a stable business `--idempotency-key` when the caller might repeat create
163
+ after a lost response. Do not blindly repeat mutations with a new key: the first
164
+ request may already have created a billable instance.
165
+
166
+ The CLI rejects unknown Sandbox flags before making a request. Exact
167
+ `--offering-id` selection cannot be combined with `--max-hourly-usd`; select by
168
+ requirements under a ceiling or create from a previously checked quote instead.
169
+ Successful create output returns `clientIdempotencyKey`. If `create --wait`
170
+ fails after acceptance, retain the structured `instanceId`, `observedState`,
171
+ `clientIdempotencyKey`, and recovery commands from stderr, then inspect and
172
+ terminate the instance as appropriate.
173
+
174
+ ## Files and artifacts
175
+
176
+ Write inline text or a local file:
177
+
178
+ ```bash
179
+ npx xapi-to sandbox file write <id> instructions.txt --content 'Run tests.'
180
+ npx xapi-to sandbox file write <id> input.csv --file ./input.csv
181
+ ```
182
+
183
+ Read and list artifacts:
184
+
185
+ ```bash
186
+ npx xapi-to sandbox file list <id> --path . --depth 3
187
+ npx xapi-to sandbox file read <id> output.json
188
+ npx xapi-to sandbox file read <id> output.zip --output ./output.zip
189
+ ```
190
+
191
+ Local `--output` uses create-new semantics and refuses to overwrite an existing
192
+ file. The CLI base64-encodes local input bytes so binary files survive transfer.
193
+
194
+ ## Web preview and background processes
195
+
196
+ For providers that declare both `backgroundExec` and `ports`, use the explicit
197
+ provider-managed background command. Daytona needs this mode because deleting a
198
+ foreground command session also kills shell-backgrounded child processes:
199
+
200
+ ```bash
201
+ box_json="$(npx xapi-to sandbox create \
202
+ --provider daytona \
203
+ --capabilities exec,backgroundExec,ports \
204
+ --wait)"
205
+ box_id="$(printf '%s' "$box_json" | jq -r '.id')"
206
+ port=25319
207
+
208
+ cleanup() { npx xapi-to sandbox terminate "$box_id" --provider daytona || true; }
209
+ trap cleanup EXIT INT TERM
210
+
211
+ npx xapi-to sandbox exec "$box_id" --provider daytona --background --command \
212
+ "python3 -m http.server $port --bind 0.0.0.0"
213
+ npx xapi-to sandbox port "$box_id" "$port" --provider daytona
214
+ ```
215
+
216
+ `--background` returning a session/command ID is only launch acknowledgement.
217
+ Poll the public URL with bounded retries and verify an expected marker. If the
218
+ port response contains `headers`, include them in external requests; they can
219
+ carry a provider preview token. Do not emulate this mode with `nohup ... &` on
220
+ an Offering that does not declare `backgroundExec`.
221
+
222
+ Cloudflare declares the standard background command surface and maps it to its
223
+ native managed process API. Pin `cf-edge` only when the user explicitly wants
224
+ Cloudflare. For a one-day workspace, require `--min-runtime 24h` and explicitly
225
+ enable `cloudflare.set_keep_alive`; the runtime requirement filters offerings,
226
+ while keepAlive prevents the default ten-minute idle reset:
227
+
228
+ ```bash
229
+ box_json="$(npx xapi-to sandbox create \
230
+ --provider cf-edge \
231
+ --capabilities exec,backgroundExec,files,ports,lifecycle.keep_alive \
232
+ --min-runtime 24h \
233
+ --wait)"
234
+ box_id="$(printf '%s' "$box_json" | jq -r '.id')"
235
+ port=8080
236
+
237
+ cleanup() { npx xapi-to sandbox terminate "$box_id" --provider cf-edge || true; }
238
+ trap cleanup EXIT INT TERM
239
+
240
+ npx xapi-to sandbox file write "$box_id" index.html \
241
+ --provider cf-edge \
242
+ --content '<!doctype html><h1>xAPI preview</h1>'
243
+
244
+ npx xapi-to sandbox extension "$box_id" cloudflare.set_keep_alive \
245
+ --provider cf-edge --input '{"keepAlive":true}'
246
+
247
+ npx xapi-to sandbox exec "$box_id" --provider cf-edge --background --command \
248
+ "python3 -m http.server $port --directory /workspace"
249
+
250
+ npx xapi-to sandbox port "$box_id" "$port" --provider cf-edge
251
+ ```
252
+
253
+ Validate that the returned public URL serves the expected marker before calling
254
+ the workflow successful. Quick Tunnel DNS/TLS readiness can be intermittent, so
255
+ use bounded retries (for example, one request every two seconds for up to two
256
+ minutes). If it still fails, verify localhost again, record the URL/error, and
257
+ terminate instead of leaving the instance billing. The URL stops working after
258
+ termination. Quick Tunnels are for previews; use a stable, supported named
259
+ tunnel or application deployment for production traffic.
260
+
261
+ Cloudflare extensions also expose managed process logs/readiness, persistent
262
+ shell sessions, stateful Python/JavaScript/TypeScript code contexts, Git
263
+ checkout, file-change cursors, bucket mounts, and R2 backup/restore. Inspect
264
+ `capabilities.extensionIds` before calling them. A default idle reset starts a
265
+ fresh container and does not retain files, processes, sessions, or interpreter
266
+ state. `keepAlive` removes that idle cutoff but does not guarantee that platform
267
+ maintenance can never restart the host. Use R2 backup or external storage for
268
+ state that must survive restarts, disable keepAlive in `finally`, and terminate.
269
+
270
+ If the Offering declares `cloudflare.browser.*`, Cloudflare Browser Run can be
271
+ used through the same generic extension command. Prefer a Quick Action for
272
+ read-only page understanding before paying for a multi-step browser session:
273
+
274
+ ```bash
275
+ npx xapi-to sandbox extension "$box_id" cloudflare.browser.snapshot \
276
+ --provider cf-edge \
277
+ --input '{"url":"https://example.com/","formats":["screenshot","markdown","accessibilityTree"]}'
278
+
279
+ npx xapi-to sandbox extension "$box_id" cloudflare.browser.automate \
280
+ --provider cf-edge \
281
+ --input '{"url":"https://demo.playwright.dev/todomvc/","actions":[{"type":"fill","selector":".new-todo","value":"xAPI task"},{"type":"press","selector":".new-todo","key":"Enter"}],"extract":[{"name":"todos","selector":".todo-list li label","all":true}],"screenshot":{"type":"png","fullPage":true}}'
282
+ ```
283
+
284
+ Browser Run and the Sandbox container do not share a filesystem. Write the
285
+ returned page data into the sandbox through `sandbox file write` if a later
286
+ container command must analyze it. Browser operations have separate operation
287
+ prices; inspect the quote instead of assuming the container hourly cap includes
288
+ them. The current automation extension is one-shot and closes the browser.
289
+ Persistent CDP/Live View/HITL are not supported until xAPI owns and authorizes
290
+ the session and proxies its WebSocket without exposing Cloudflare credentials.
291
+
292
+ ## Suspend and resume
293
+
294
+ Check offering lifecycle fields first because not every provider supports an
295
+ explicit suspend operation:
296
+
297
+ ```bash
298
+ npx xapi-to sandbox offerings --format pretty
299
+ npx xapi-to sandbox suspend <id>
300
+ npx xapi-to sandbox get <id>
301
+ npx xapi-to sandbox resume <id>
302
+ ```
303
+
304
+ The CLI waits for `SUSPENDED` and `RUNNING` by default. Files may persist while
305
+ memory/processes do not; rely on the selected offering's declared lifecycle
306
+ semantics. Suspension can reduce compute cost but storage may still accrue cost.
307
+ If `lifecycle.suspension.supported` is false (as with a current cf-edge
308
+ offering), do not call suspend/resume; terminate and create a new instance.
309
+
310
+ ## GPU jobs
311
+
312
+ Request GPU resources instead of assuming a provider or model. The current
313
+ RunPod offering is a managed GPU resource without standard `exec`/`files`, so
314
+ inspect its declared extension and obtain connection details instead of sending
315
+ an impossible shell command:
316
+
317
+ ```bash
318
+ npx xapi-to sandbox quote \
319
+ --gpu-count 1 \
320
+ --gpu-model L4 \
321
+ --capabilities exec \
322
+ --max-hourly-usd 2.00
323
+
324
+ npx xapi-to sandbox create \
325
+ --provider runpod \
326
+ --gpu-count 1 \
327
+ --max-hourly-usd 2.00 \
328
+ --wait
329
+
330
+ npx xapi-to sandbox extension <id> runpod.connection_info \
331
+ --provider runpod \
332
+ --input '{}'
333
+
334
+ npx xapi-to sandbox terminate <id> --provider runpod
335
+ ```
336
+
337
+ GPU work is usually more expensive. Quote first, set a deliberate ceiling, use
338
+ a command timeout, and terminate immediately after artifacts are retrieved.
339
+
340
+ ## Parallel isolated instances
341
+
342
+ Give each concurrent worker or agent a separate instance. Do not share a mutable
343
+ workspace when the goal is isolation. Use unique idempotency keys and record every instance ID.
344
+ Run cleanup for all IDs even if one agent fails; then verify `sandbox list` has
345
+ no active instance from the job.
346
+
347
+ Limit concurrency based on budget. Parallel creation multiplies reservation and
348
+ running cost, even when the individual hourly quote is small.
349
+
350
+ ## OpenAI SandboxAgent integration example
351
+
352
+ The OpenAI Agents SDK keeps the model provider and sandbox provider separate.
353
+ Use the SDK's OpenAI-compatible model provider for DeepSeek through
354
+ `https://ai.xapi.to/v1`, and the xAPI adapter for Sandbox compute:
355
+
356
+ The Agents SDK owns the model loop, tool choice, prompt, and conversation state.
357
+ xAPI owns only the Sandbox resource, execution, lifecycle, audit, and billing.
358
+
359
+ ```ts
360
+ import { OpenAIProvider, Runner } from "@openai/agents";
361
+ import { Manifest, SandboxAgent, shell } from "@openai/agents/sandbox";
362
+ import { XapiAgentsSandboxClient } from "xapi-to/openai-sandbox";
363
+
364
+ const sandboxApiKey = process.env.XAPI_SANDBOX_KEY;
365
+ const aiApiKey = process.env.XAPI_AI_KEY;
366
+ if (!sandboxApiKey) throw new Error("XAPI_SANDBOX_KEY is required");
367
+ if (!aiApiKey) throw new Error("XAPI_AI_KEY is required");
368
+
369
+ const sandbox = new XapiAgentsSandboxClient({
370
+ apiKey: sandboxApiKey,
371
+ sandboxHost: "sandbox.test.xapi.to",
372
+ provider: "daytona",
373
+ model: "deepseek-v4-pro",
374
+ });
375
+ const modelProvider = new OpenAIProvider({
376
+ apiKey: aiApiKey,
377
+ baseURL: "https://ai.xapi.to/v1",
378
+ useResponses: false,
379
+ strictFeatureValidation: true,
380
+ });
381
+ const runner = new Runner({ modelProvider, tracingDisabled: true });
382
+ const agent = new SandboxAgent({
383
+ name: "xAPI DeepSeek sandbox agent",
384
+ model: "deepseek-v4-pro",
385
+ defaultManifest: new Manifest({ root: sandbox.workspaceRoot }),
386
+ capabilities: [shell()],
387
+ instructions: "Use shell to complete and verify the task.",
388
+ });
389
+
390
+ try {
391
+ const result = await runner.run(
392
+ agent,
393
+ "Write SDK_OK=42 to result.txt and read it.",
394
+ {
395
+ maxTurns: 8,
396
+ sandbox: { client: sandbox },
397
+ },
398
+ );
399
+ console.log(result.finalOutput);
400
+ } finally {
401
+ await sandbox.lastSession?.close();
402
+ }
403
+ ```
404
+
405
+ Use `useResponses: false` because `ai.xapi.to` currently implements the OpenAI
406
+ Chat Completions-compatible protocol. Disable tracing unless a separate OpenAI
407
+ telemetry credential is configured; do not send an xAPI key to OpenAI tracing.
408
+ Keep `XAPI_AI_KEY` and `XAPI_SANDBOX_KEY` separate for a mixed environment:
409
+ the former is sent only to production `ai.xapi.to`, while the latter is sent
410
+ only to `sandbox.test.xapi.to`. A production key with both permissions may be
411
+ injected into both variables, but a Sandbox test key must not be assumed to
412
+ have production AI Gateway access.
413
+ The current adapter honestly supports an empty Manifest and Shell capability.
414
+ It rejects Manifest file/mount/environment materialization until those mappings
415
+ are implemented and tested.
416
+
417
+ Run the real SDK + DeepSeek + Daytona acceptance test from the CLI repository:
418
+
419
+ ```bash
420
+ XAPI_SANDBOX_KEY='<sandbox-test-key>' \
421
+ XAPI_AI_KEY='<ai-production-key>' \
422
+ npm run test:sandbox:openai -- \
423
+ --host sandbox.test.xapi.to \
424
+ --provider daytona \
425
+ --model deepseek-v4-pro
426
+ ```
427
+
428
+ The script writes a redacted report, audits operations/events/usage/billing,
429
+ terminates its instance, and fails if any active test instance remains.
430
+
431
+ ## Audit, history, and billing
432
+
433
+ Inspect current state and service-calculated total:
434
+
435
+ ```bash
436
+ npx xapi-to sandbox get <id> --format pretty
437
+ ```
438
+
439
+ Read individual audit streams:
440
+
441
+ ```bash
442
+ npx xapi-to sandbox audit <id> --kind operations
443
+ npx xapi-to sandbox audit <id> --kind events
444
+ npx xapi-to sandbox audit <id> --kind usageSegments
445
+ npx xapi-to sandbox audit <id> --kind billingPeriods
446
+ npx xapi-to sandbox history --state HISTORY --page-size 100
447
+ ```
448
+
449
+ `history` is a separate paginated endpoint for prior instances; it is not an
450
+ `audit --kind`. Filter it with `--search`, `--from`, and `--to` when reconciling
451
+ a specific agent run.
452
+
453
+ For acceptance, verify:
454
+
455
+ 1. create/exec/file/port/terminate operations have terminal success statuses;
456
+ 2. state events reach `TERMINATED`;
457
+ 3. no usage segment or billing period remains open;
458
+ 4. `totalCost` agrees with settled billing periods;
459
+ 5. `sandbox list` shows no active instance from the test.
460
+
461
+ Use the returned billing data rather than recomputing cost from wall-clock time.
462
+
463
+ ## Run the real Playground recipe acceptance suite
464
+
465
+ From an xapi-cli development checkout, run the same nine client recipes shown
466
+ in the Web Playground. The suite uses normal CLI configuration, never accepts a
467
+ key on argv, records audit/billing evidence, terminates every tracked instance
468
+ in `finally`, and fails if any instance created after its baseline remains
469
+ ACTIVE (unrelated pre-existing account instances are still reported):
470
+
471
+ ```bash
472
+ npm run test:sandbox:playground -- --host sandbox.test.xapi.to
473
+
474
+ # Focus a rerun or avoid the higher-cost GPU reservation
475
+ npm run test:sandbox:playground -- --host sandbox.test.xapi.to --only 8,9
476
+ npm run test:sandbox:playground -- --host sandbox.test.xapi.to --skip-gpu
477
+ ```
478
+
479
+ The JSON report path is printed at completion. A provider capacity or HTTP 402
480
+ balance error is an external test precondition failure, not proof that the
481
+ scenario works; retain the error and a previous successful provider-specific
482
+ report separately. For Cloudflare, success requires an external HTTP 200 with
483
+ the expected page marker, not merely a returned Quick Tunnel hostname.
484
+
485
+ ## Failure and interruption recovery
486
+
487
+ `sandbox run` handles ordinary exceptions, remote non-zero exits, `SIGINT`, and
488
+ `SIGTERM` by attempting termination before it exits. `SIGKILL`, machine loss, or
489
+ a network partition cannot run local cleanup.
490
+
491
+ After an uncertain interruption:
492
+
493
+ ```bash
494
+ npx xapi-to sandbox list --format table
495
+ npx xapi-to sandbox get <suspected-id>
496
+ npx xapi-to sandbox terminate <suspected-id> --wait-timeout 5m
497
+ ```
498
+
499
+ If terminate returns a state-change conflict, inspect state and retry after the
500
+ in-flight transition finishes. Do not treat an accepted operation response as
501
+ completion; wait for the instance's observed terminal state.
502
+
503
+ ## AI operating rules
504
+
505
+ When exposing Sandbox to an AI agent:
506
+
507
+ 1. Inject the xAPI key in the tool execution layer; never place it in prompts,
508
+ files, environment dumps, remote commands, logs, or model-visible output.
509
+ 2. Start with `offerings`/`quote` when selection or budget is uncertain.
510
+ 3. Prefer `sandbox run` for one-shot work and granular primitives only when the
511
+ task needs persistent state across calls.
512
+ 4. Set capabilities and a price ceiling narrowly enough for the task.
513
+ 5. Use `--background` only when the selected Offering declares
514
+ `backgroundExec`; then verify the listening port independently.
515
+ 6. Treat instance IDs as cleanup obligations and keep them in structured state.
516
+ 7. Put termination in `finally`; on interruption, enumerate and reconcile any
517
+ uncertain instances.
518
+ 8. Report stdout, exit code, final state, cost, and cleanup outcome separately.
519
+ 9. Never claim success from page/API structure alone—execute the relevant path,
520
+ verify its artifact or public URL, then check audit and residual instances.
@@ -0,0 +1,124 @@
1
+ # Serper Guide
2
+
3
+ Use the direct `serper.*` API actions when the task needs provider-native
4
+ Google results, several searches in one mini-batch, or Serper surfaces that the
5
+ built-in `web.search.*` capabilities do not expose. For a simple single search
6
+ with a normalized xAPI response, prefer `web.search.*` and read
7
+ `google_search.md` instead.
8
+
9
+ The current `serper` service exposes 12 v7 actions. They are third-party API
10
+ actions, so parameters go inside `body`:
11
+
12
+ ```bash
13
+ npx xapi-to get serper.search
14
+ npx xapi-to call serper.search --input '{"body":{"q":"OpenAI","gl":"us","hl":"en"}}'
15
+ ```
16
+
17
+ Run `get` before relying on optional parameters or response fields. Serper
18
+ responses are passed through in provider-native form and can gain fields that
19
+ are not declared in the xAPI output schema.
20
+
21
+ ## Mini-batch
22
+
23
+ Eleven actions accept either one request object or an array of request objects
24
+ in `body`. The response is respectively one result object or an array of result
25
+ objects in request order:
26
+
27
+ ```bash
28
+ npx xapi-to call serper.search --input \
29
+ '{"body":[{"q":"OpenAI","gl":"us","hl":"en"},{"q":"Cloudflare","gl":"us","hl":"en"}]}'
30
+ ```
31
+
32
+ Each array member has the same shape as a single request. Do not wrap the
33
+ members in `queries`, and do not confuse this with `xapi-to get-batch`, which
34
+ retrieves several Action schemas without executing them.
35
+
36
+ `serper.reviews` is the only current `serper.*` action that does not support
37
+ mini-batch. Send exactly one object in its `body`.
38
+
39
+ ## Billing
40
+
41
+ All 12 actions use dynamic xAPI billing at **$0.002 per Serper credit**. For a
42
+ single request, the charge is `response.credits * $0.002`; for a mini-batch it
43
+ is `sum(response[*].credits) * $0.002`.
44
+
45
+ The `cost: 0` placeholder shown in discovery output does not mean the call is
46
+ free; dynamic prices are not comparable as a fixed per-call price. Inspect the
47
+ Action's `meta.description` and `meta.pricing`, and keep returned `credits` when
48
+ auditing usage.
49
+
50
+ ## Current Actions
51
+
52
+ | Action | Use it for | Primary input |
53
+ |---|---|---|
54
+ | `serper.search` | General Google web results | `q` |
55
+ | `serper.images` | Google Images; current schema accepts `num` 10 or 100 | `q` |
56
+ | `serper.news` | Google News results | `q` |
57
+ | `serper.videos` | Google video results | `q` |
58
+ | `serper.shopping` | Product and shopping results | `q` |
59
+ | `serper.scholar` | Academic publications and citations | `q` |
60
+ | `serper.patents` | Patent search | `q` |
61
+ | `serper.autocomplete` | Suggestions for a partial query | `q` |
62
+ | `serper.places` | Local businesses and place search | `q` |
63
+ | `serper.maps` | Map search or lookup by Google Place ID/CID | `q`, `placeId`, or `cid` |
64
+ | `serper.lens` | Reverse image search from a public image URL | `url` |
65
+ | `serper.reviews` | Place reviews and cursor pagination | `placeId`, `cid`, or `fid` |
66
+
67
+ The common search-family controls are `gl`, `hl`, `location`, `page`, `num`,
68
+ `tbs`, and `autocorrect`, but not every action exposes every control. Use the
69
+ current `get` schema instead of copying parameters between actions.
70
+
71
+ ## Focused Examples
72
+
73
+ ### News with a Google time filter
74
+
75
+ ```bash
76
+ npx xapi-to call serper.news --input \
77
+ '{"body":{"q":"AI regulation","gl":"us","hl":"en","tbs":"qdr:d"}}'
78
+ ```
79
+
80
+ ### Maps by coordinates
81
+
82
+ ```bash
83
+ npx xapi-to call serper.maps --input \
84
+ '{"body":{"q":"coffee","ll":"@40.7455096,-74.0083012,14z","hl":"en"}}'
85
+ ```
86
+
87
+ Use `placeId` or `cid` instead of `q` when resolving a known Google place.
88
+
89
+ ### Google Lens
90
+
91
+ ```bash
92
+ npx xapi-to call serper.lens --input \
93
+ '{"body":{"url":"https://example.com/public-image.jpg","gl":"us","hl":"en"}}'
94
+ ```
95
+
96
+ The image must be reachable through a public URL; a local filesystem path is
97
+ not a valid Lens input.
98
+
99
+ ### Reviews and pagination
100
+
101
+ ```bash
102
+ # First page; body must be an object, not an array
103
+ npx xapi-to call serper.reviews --input \
104
+ '{"body":{"placeId":"ChIJ...","sortBy":"newest","gl":"us","hl":"en"}}'
105
+
106
+ # Continue with the provider's cursor
107
+ npx xapi-to call serper.reviews --input \
108
+ '{"body":{"placeId":"ChIJ...","nextPageToken":"<token>","sortBy":"newest","gl":"us","hl":"en"}}'
109
+ ```
110
+
111
+ Current `sortBy` values are `mostRelevant`, `newest`, `highestRating`, and
112
+ `lowestRating`.
113
+
114
+ ## Service Boundary
115
+
116
+ Serper's upstream product also advertises webpage extraction, but the current
117
+ xAPI service directory exposes only the 12 `serper.*` actions above. Do not
118
+ invent or call `serper.webpage`. Search the live registry first; if a Webpage
119
+ Action is added later, use its own discovered Action ID and schema because the
120
+ upstream scraper is a separate surface from Google search.
121
+
122
+ For provider details that are not exposed by `xapi-to get`, consult the current
123
+ official Serper documentation at <https://serper.dev/>. xAPI's `body` wrapper,
124
+ Action IDs, and billing metadata remain authoritative for calls through xAPI.