@gemmein/sdk 0.8.0 → 0.9.0
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/CHANGELOG.md +30 -0
- package/README.md +3 -1
- package/REFERENCE.md +121 -38
- package/dist/index.cjs +108 -12
- package/dist/index.d.cts +102 -14
- package/dist/index.d.ts +102 -14
- package/dist/index.js +108 -12
- package/llms.txt +146 -50
- package/migrations/README.md +1 -0
- package/migrations/raw-calls-off.md +51 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,36 @@ the same release.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.9.0] — 2026-09-07
|
|
16
|
+
|
|
17
|
+
### Keys that know what they are, production sync, and AI tools defined on the server (engine 0.10.0, sdk 0.9.0, mcp 0.6.0)
|
|
18
|
+
|
|
19
|
+
- **`g.ai.run(tool, inputs, { stream? })` / `g.ai.runText(tool, inputs)`** — call a
|
|
20
|
+
named AI tool with inputs only; the server composes the provider request
|
|
21
|
+
from the tool's own instructions and template, gates it, spends its
|
|
22
|
+
credits and streams the answer back in the provider's own shape.
|
|
23
|
+
Refusals: `invalid_inputs` (400), `tool_incomplete` (409), plus the tool
|
|
24
|
+
codes `g.ai.chat` already answered.
|
|
25
|
+
- **`g.ai.calls()`** — the signed-in person's own AI calls, newest first,
|
|
26
|
+
keyset-paged (`AiCallRecord`): tool, provider, model, tokens, credits,
|
|
27
|
+
outcome, latency, and the prompt and answer where the tool keeps them.
|
|
28
|
+
- **Raw calls are off by default.** `g.ai.chat(body)` answers
|
|
29
|
+
`403 raw_calls_off` unless the owner switches raw calls on for that
|
|
30
|
+
provider's key on the AI tools page. Migration note: an app built on
|
|
31
|
+
`g.ai.chat` needs that switch, or a named tool through `g.ai.run`.
|
|
32
|
+
- **Server keys cannot sign in as the account owner or admin.**
|
|
33
|
+
`gemmeinServer(sk).testSession(email)` refuses those two emails with
|
|
34
|
+
`scope_denied`; test people sign in as before. Linking and dev sync use
|
|
35
|
+
the app's CLI key (`sk_cli_…`, Setup page); production sync uses a
|
|
36
|
+
one-hour Sync key (`sk_sync_…`). `secret_key_expired` (403) names an
|
|
37
|
+
expired sync key's instant.
|
|
38
|
+
- CLI: `npx gemmein sync` links with ONE paste (the CLI key); `npx gemmein
|
|
39
|
+
sync --live` carries relays and AI tools into production behind a sync
|
|
40
|
+
key and the typed word `live`, never deletes, asks before replacing a
|
|
41
|
+
row edited in the dashboard (`--overwrite`). A relay's name is fixed once
|
|
42
|
+
created. Tool files gain `kind`, `instructions`, `promptTemplate`,
|
|
43
|
+
`inputs`, `recordCalls`.
|
|
44
|
+
|
|
15
45
|
## [0.8.0] — 2026-09-05
|
|
16
46
|
|
|
17
47
|
### Products are provider-neutral (engine 0.9.0, sdk 0.8.0, mcp 0.5.0)
|
package/README.md
CHANGED
|
@@ -292,7 +292,9 @@ try {
|
|
|
292
292
|
|--------|------------|----------------|
|
|
293
293
|
| `pk_test_...` | Development | Frontend code — safe to expose |
|
|
294
294
|
| `pk_live_...` | Production | Frontend code — safe to expose |
|
|
295
|
-
| `sk_dev_...` / `sk_live_...` | Dev / Prod | Server env vars only |
|
|
295
|
+
| `sk_dev_...` / `sk_live_...` | Dev / Prod | Server env vars only — runs your app; never links or syncs |
|
|
296
|
+
| `sk_cli_...` | Development | The CLI key, shown on Setup: what `gemmein sync` and `gemmein go-live` act with. Lives in `gemmein/.data/`, never in git |
|
|
297
|
+
| `sk_sync_...` | Production | A one-hour sync key (Secret keys → production): `gemmein sync --live` carries relays and AI tools into production. Pasted, used, never saved |
|
|
296
298
|
|
|
297
299
|
Environments are fully isolated: different data, different users, different collections.
|
|
298
300
|
|
package/REFERENCE.md
CHANGED
|
@@ -353,30 +353,79 @@ connected.
|
|
|
353
353
|
|
|
354
354
|
---
|
|
355
355
|
|
|
356
|
-
## AI — `g.ai.chat` / `g.ai.text`
|
|
356
|
+
## AI — `g.ai.run` / `g.ai.runText` / `g.ai.calls` (and the raw `g.ai.chat` / `g.ai.text`)
|
|
357
357
|
|
|
358
358
|
Your app talks to **OpenAI, Anthropic or Google** through Gemmein, on the
|
|
359
359
|
**owner's own provider key**, which never reaches the browser, through a
|
|
360
|
-
**named AI tool
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
the
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
360
|
+
**named AI tool whose definition lives on the server** (W9.6): the
|
|
361
|
+
instructions (system prompt), the prompt template, the inputs it accepts,
|
|
362
|
+
the pinned model, the caps — beside the price in credits and the
|
|
363
|
+
entitlement gate. The app sends a **name and inputs**; the server composes
|
|
364
|
+
the provider request in the provider's own grammar, gates it, spends the
|
|
365
|
+
tool's credits, runs it and streams the answer back. The owner prices and
|
|
366
|
+
gates the tool on the dashboard's AI tools page; the implementation is
|
|
367
|
+
written there too, or as a file at `gemmein/ai/tools/<name>.json` that
|
|
368
|
+
`npx gemmein sync` carries (`--live` into production with a sync key). A
|
|
369
|
+
file carries at least `label`, `provider` and `credits` (its name is the
|
|
370
|
+
file name). On every sync the file's implementation (`provider`, `model`,
|
|
371
|
+
`kind`, `instructions`, `promptTemplate`, `inputs`, `bounds`) applies; its
|
|
372
|
+
commerce values (`label`, `credits`, `requires`, `enabled`, `recordCalls`)
|
|
373
|
+
apply once, at creation — after that the dashboard owns them. In
|
|
374
|
+
development, sync also writes a tool that exists only in the cloud back to
|
|
375
|
+
`gemmein/ai/tools/` as a file; in production such a row is left as it is.
|
|
376
|
+
A tool's name is fixed once created. Removing a tool is a step-up action, like
|
|
377
|
+
removing a key.
|
|
378
|
+
|
|
379
|
+
**Raw calls are off by default.** `g.ai.chat(body)` — the browser sending
|
|
380
|
+
the provider's own request body — answers `403 raw_calls_off` unless the
|
|
381
|
+
owner switches **raw calls** on for that provider's key on the AI tools page. Behind the switch, the raw route is what it always was: the body is
|
|
382
|
+
forwarded as sent (minus `provider`, and for Google minus `model` and
|
|
383
|
+
`stream`, which ride the URL), the provider's own status and bytes pass
|
|
384
|
+
straight back, a stream stays a stream, and a call that names no `tool`
|
|
385
|
+
runs as the **default tool** (one credit, the configured provider, any
|
|
386
|
+
allowed model). `gemmein dev` with no key keeps raw calls open — the fake
|
|
387
|
+
answers and there is no switch locally. Both routes are for the browser
|
|
388
|
+
only: a server key is refused (`403 scope_denied`) — a server calls the
|
|
389
|
+
provider directly.
|
|
390
|
+
|
|
391
|
+
**Every call lands on `ai_calls`**, the managed collection: who, which tool,
|
|
392
|
+
kind, provider, model, tokens in/out (when the provider said), credits,
|
|
393
|
+
outcome, refusal code, latency, when. The **prompt and answer** ride the row
|
|
394
|
+
only when the owner switches *record calls* on for that tool — off by
|
|
395
|
+
default. A person reads their own (`g.ai.calls()`), the owner reads by app
|
|
396
|
+
in the back office, erasure removes a person's rows.
|
|
375
397
|
|
|
376
398
|
| Method | Signature | Returns |
|
|
377
399
|
|--------|-----------|---------|
|
|
378
|
-
| `ai.
|
|
379
|
-
| `ai.
|
|
400
|
+
| `ai.run` | `(tool: string, inputs?: Record<string, string \| number \| boolean>, options?: { stream?: boolean; signal?: AbortSignal })` | `Promise<Response>` — the tool's provider's own answer, untouched (SSE when `stream`). A non-2xx from the provider is returned as-is; a Gemmein refusal throws `GemmeinError` |
|
|
401
|
+
| `ai.runText` | `(tool: string, inputs?: …, options?: { signal?: AbortSignal })` | `Promise<string>` — a non-stream run collected to one string, whichever provider answered; a provider's non-2xx throws `provider_error` |
|
|
402
|
+
| `ai.calls` | `(options?: { limit?: number; before?: string })` | `Promise<{ calls: AiCallRecord[]; nextCursor: string \| null }>` — the signed-in person's own calls, newest first |
|
|
403
|
+
| `ai.chat` | `(body: object, options?: { tool?: string; provider?: "openai" \| "anthropic" \| "google"; signal?: AbortSignal })` | `Promise<Response>` — RAW (needs the owner's switch): the fetch `Response`, untouched |
|
|
404
|
+
| `ai.text` | `(body: object, options?: { tool?: string; provider?: …; signal?: AbortSignal })` | `Promise<string>` — RAW, non-stream, one string |
|
|
405
|
+
|
|
406
|
+
- **The tool file.** `name` (the file name; fixed once created), `label`,
|
|
407
|
+
`provider`, `model` (pin it — a composed call needs one; with none, the
|
|
408
|
+
owner's first allowed model, else the provider's small default),
|
|
409
|
+
`credits`, `requires`, `enabled`, `bounds`, plus the implementation:
|
|
410
|
+
`kind` (`chat`, the first), `instructions` (the system turn, ≤ 20,000
|
|
411
|
+
chars — never leaves the server), `promptTemplate` (the user turn with
|
|
412
|
+
`{{input}}` placeholders naming declared inputs; ≤ 20,000; absent = the
|
|
413
|
+
inputs rendered one per line as `name: value`), `inputs` (≤ 20 of
|
|
414
|
+
`{ name, type: text|number|boolean, required?, maxLength? }` — names
|
|
415
|
+
1–40 lowercase letters/digits/underscores, starting with a letter; a
|
|
416
|
+
text input ≤ 4,000 chars
|
|
417
|
+
unless it says, ≤ 20,000), `recordCalls` (off by default). A tool with
|
|
418
|
+
neither a template nor inputs composes nothing: `409 tool_incomplete`.
|
|
419
|
+
- **Inputs are checked by name** before any spend: an unknown input, a
|
|
420
|
+
missing required one, a wrong type or a value over its cap is
|
|
421
|
+
`400 invalid_inputs` — the message names the input and the rule. The run
|
|
422
|
+
body is `{ inputs, stream? }` and at most 64 KB.
|
|
423
|
+
- **Composition** per provider: OpenAI `messages` (system + user),
|
|
424
|
+
`max_completion_tokens`; Anthropic `system` + `messages`, `max_tokens`;
|
|
425
|
+
Google `systemInstruction` + `contents`, `generationConfig.maxOutputTokens`.
|
|
426
|
+
The output ceiling is `bounds.maxOutputTokens` or 4,096 — a composed
|
|
427
|
+
call is never "as long as the model likes". `bounds.stream: false`
|
|
428
|
+
forces a non-stream answer.
|
|
380
429
|
|
|
381
430
|
- `tool` names an `ai_tools` row by its slug; omit it for the default tool.
|
|
382
431
|
An unknown name is `404 unknown_tool`; a tool the owner switched off is
|
|
@@ -397,7 +446,7 @@ calls the provider directly.
|
|
|
397
446
|
OpenAI chat completions, Anthropic messages, Google generateContent. Its
|
|
398
447
|
`model` field, when present, must match `^[A-Za-z0-9._:-]{1,80}$`; Google
|
|
399
448
|
needs it (it rides the URL); when the owner lists allowed models (up to
|
|
400
|
-
20 — the
|
|
449
|
+
20 — the AI tools page's test call uses the first), any other answers
|
|
401
450
|
`403 model_not_allowed`.
|
|
402
451
|
- `provider` is optional when one key is configured and required when more
|
|
403
452
|
than one is (`400 provider_required`); a named tool fixes its own
|
|
@@ -418,31 +467,46 @@ calls the provider directly.
|
|
|
418
467
|
- Limits: 20 calls per person per minute (`429 ai_capped`, `resetAt`), 256 KB
|
|
419
468
|
body by default (`413 payload_too_large`, unless a tool sets a smaller
|
|
420
469
|
`bounds.maxBodyBytes`) nested at most 32 levels (`400 invalid_body`),
|
|
421
|
-
170 s in all and, on a stream, 10 s to the first response headers.
|
|
470
|
+
170 s in all and, on a stream, 10 s to the first response headers. A
|
|
471
|
+
tool's `credits` are 1–10,000; `bounds.maxOutputTokens` is 1–100,000,
|
|
472
|
+
4,096 when the tool sets none. Every
|
|
422
473
|
`/ai/chat` call counts toward the app's `api_requests` band like any other
|
|
423
474
|
request.
|
|
424
475
|
|
|
425
476
|
```ts
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
477
|
+
// gemmein/ai/tools/deep-research.json
|
|
478
|
+
// { "label": "Deep Research", "provider": "openai", "model": "gpt-4o", "credits": 20,
|
|
479
|
+
// "requires": "access:pro-max",
|
|
480
|
+
// "instructions": "You are a careful research assistant. Answer with sources.",
|
|
481
|
+
// "promptTemplate": "Research this for a {{audience}} reader:\n\n{{question}}",
|
|
482
|
+
// "inputs": [{ "name": "question", "type": "text", "required": true, "maxLength": 2000 },
|
|
483
|
+
// { "name": "audience", "type": "text" }],
|
|
484
|
+
// "bounds": { "maxOutputTokens": 4000 } }
|
|
485
|
+
|
|
486
|
+
const res = await g.ai.run("deep-research", { question: text, audience: "beginner" }, { stream: true })
|
|
430
487
|
for await (const chunk of res.body) render(chunk) // the provider's SSE, byte for byte
|
|
431
488
|
|
|
432
|
-
const answer = await g.ai.
|
|
489
|
+
const answer = await g.ai.runText("deep-research", { question: text })
|
|
490
|
+
const { calls } = await g.ai.calls() // the person's own history
|
|
491
|
+
|
|
492
|
+
// RAW — only behind the owner's "raw calls" switch for that provider key:
|
|
493
|
+
const raw = await g.ai.chat({ model: "gpt-4o-mini", stream: true, messages: [{ role: "user", content: text }] })
|
|
433
494
|
```
|
|
434
495
|
|
|
435
496
|
| code | status | meaning · do |
|
|
436
497
|
|------|--------|--------------|
|
|
498
|
+
| `raw_calls_off` | 403 | The browser may not compose provider requests for this provider — call a named tool with `g.ai.run`, or the owner switches raw calls on for the key on the AI tools page |
|
|
499
|
+
| `invalid_inputs` | 400 | An input is unknown, missing, the wrong type or over its cap — the message names it |
|
|
500
|
+
| `tool_incomplete` | 409 | The tool composes nothing (no template, no inputs) — give it one; the owner's or the file's fix |
|
|
437
501
|
| `unknown_tool` | 404 | No AI tool by this name in this environment — check the slug or the environment |
|
|
438
502
|
| `tool_disabled` | 403 | The owner switched this tool off — turn it on, or use another |
|
|
439
503
|
| `entitlement_required` | 403 | The tool's `requires` names a plan or product this person lacks — the message names it |
|
|
440
504
|
| `model_pinned` | 403 | This tool's model is fixed — leave `model` out of the body |
|
|
441
|
-
| `provider_not_configured` | 409 | Creating or updating a tool: no key is set for that provider yet — add one on the
|
|
505
|
+
| `provider_not_configured` | 409 | Creating or updating a tool: no key is set for that provider yet — add one on the AI tools page first |
|
|
442
506
|
| `too_many_tools` | 409 | This environment already holds 50 AI tools — delete one before adding another |
|
|
443
507
|
| `invalid_tool` | 400 | Creating or updating a tool with a bad field — the message names which one and its rule |
|
|
444
508
|
| `credits_exhausted` | 402 | The person's balance is below the tool's price — the message names the tool, the price and the balance ("Deep Research costs 20 credits. You have 7.", singular for 1). Show the pack |
|
|
445
|
-
| `ai_not_configured` | 409 | No provider key on this app and environment — the owner pastes one
|
|
509
|
+
| `ai_not_configured` | 409 | No provider key on this app and environment — the owner pastes one on the AI tools page |
|
|
446
510
|
| `provider_required` | 400 | More than one provider key is set — pass `provider` |
|
|
447
511
|
| `model_not_allowed` | 403 | The owner's allowlist names the models this app may call; the message lists them (only for a tool with no pinned model) |
|
|
448
512
|
| `ai_capped` | 429 | 20 calls per person per minute — wait for `resetAt` |
|
|
@@ -451,8 +515,9 @@ const answer = await g.ai.text({ messages: [{ role: "user", content: text }] },
|
|
|
451
515
|
| `session_required` | 401 | No signed-in person — sign in first |
|
|
452
516
|
| `scope_denied` | 403 | A secret key called the route — the route is for the browser; a server calls the provider directly |
|
|
453
517
|
| `provider_unreachable` | 502 | The provider did not answer before the first byte — nothing was charged (the tool's credits are refunded); retry |
|
|
454
|
-
| `provider_error` | the provider's | `g.ai.text`
|
|
455
|
-
| `
|
|
518
|
+
| `provider_error` | the provider's | `g.ai.text` and `g.ai.runText` (client-side): the provider's own non-2xx, its message in `err.message` |
|
|
519
|
+
| `invalid_response` | 0 (client-side) | `g.ai.text` and `g.ai.runText`: a non-stream answer with no text to lift out — for a streaming body read the stream with `g.ai.chat` or `g.ai.run` |
|
|
520
|
+
| `ai_test_capped` | 429 | The AI tools page's test call — one a minute per app |
|
|
456
521
|
|
|
457
522
|
`gemmein dev` answers a fake provider without a key (header `x-gemmein-ai:
|
|
458
523
|
fake`, an echo stream), so the loop runs locally; set
|
|
@@ -569,8 +634,10 @@ provider's webhook arriving, a clock, a record changing — and one to ten of
|
|
|
569
634
|
Gemmein's **own** verbs, run in order: write a record, grant or revoke access,
|
|
570
635
|
email the person, call your URL. Gemmein runs no code of yours inside one;
|
|
571
636
|
compute lives on your host, behind `call_url`. The definition is a JSON file
|
|
572
|
-
your AI writes
|
|
573
|
-
|
|
637
|
+
your AI writes, or the owner writes in the dashboard, which creates and edits
|
|
638
|
+
it too, pauses, resumes, rotates and deletes it, and shows where it came from
|
|
639
|
+
("from file, synced <when>" / "edited here <when>"), its receiver URL, its
|
|
640
|
+
secrets (shown once), every event with each action's result, and a replay
|
|
574
641
|
button. There is no SDK method: the surface is the file and the dashboard.
|
|
575
642
|
Stripe stays built in; any provider that signs its webhooks — GoCardless, Paddle,
|
|
576
643
|
Lemon Squeezy among them — drives access the same way through a relay, and the
|
|
@@ -760,7 +827,16 @@ receiver's local URL), schedules and data changes with the same runner and
|
|
|
760
827
|
prints `RELAY · <name> · <trigger> · <n actions> · ok|failed`;
|
|
761
828
|
`npx gemmein sync` carries the files to the cloud app's development
|
|
762
829
|
environment with the collections — contract, never data; the cloud mints its
|
|
763
|
-
own secrets.
|
|
830
|
+
own secrets. After go-live, `npx gemmein sync --live` carries relays and AI
|
|
831
|
+
tools into production: it asks for a **sync key** (Secret keys → production →
|
|
832
|
+
Sync key — step-up to mint, lives one hour, shown once, never saved by the
|
|
833
|
+
CLI), prints what would change, waits for the word `live`, never deletes, and
|
|
834
|
+
refuses collections (they move by promotion). A row edited in the dashboard
|
|
835
|
+
since the last sync is asked about, per item (`--overwrite` answers yes);
|
|
836
|
+
every relay says where its definition came from — from file, or edited in the
|
|
837
|
+
dashboard — and when. A relay's name is fixed once created: the receiver URL
|
|
838
|
+
is built from it, so a rename is refused (`invalid_definition`) — create the
|
|
839
|
+
new one, delete the old in the dashboard once the provider has moved.
|
|
764
840
|
|
|
765
841
|
---
|
|
766
842
|
|
|
@@ -793,7 +869,10 @@ line; a wrong-but-well-formed name surfaces as `unknown_collection` and exits
|
|
|
793
869
|
|
|
794
870
|
### `gemmeinServer(sk).testSession(email) → { token, expiresAt, user }`
|
|
795
871
|
**Dev only** — throws `test_session_forbidden_live` on an `sk_live` key, and
|
|
796
|
-
the server refuses it on a live environment too.
|
|
872
|
+
the server refuses it on a live environment too. Test **people** only: a
|
|
873
|
+
server key is refused the account owner's or an admin's email
|
|
874
|
+
(`scope_denied`) — the app's CLI key (Setup page) is the credential that
|
|
875
|
+
acts as the founder, and it is the only key that links or syncs. Pass the `token` to
|
|
797
876
|
`gemmein(pk, { tokenStore })` to act as that user. Dev and live enforce the
|
|
798
877
|
*same* rules, so what is proven in dev holds in live.
|
|
799
878
|
|
|
@@ -844,6 +923,9 @@ Branch on `err.code`. The gate's own codes (`session_invalid`,
|
|
|
844
923
|
| `entitlement_required` | 403 — signed in, but not on a plan (or holding a product) this collection is unlocked by; `err.requires` is that plan's key (`access:<slug of its name>`) | show your upgrade screen and send them to checkout — the one 403 that succeeds later |
|
|
845
924
|
| `not_found` | record you can't see (existence not leaked) | treat as absent |
|
|
846
925
|
| `denied` | 401 (sign in first) or 429 (rate limit — see `resetAt`) | re-auth or wait+retry |
|
|
926
|
+
| `invalid_body` | 400 — the request body is not a JSON object (an array or a bare value was sent) | send an object |
|
|
927
|
+
| `missing_params` | 400 — a required field is absent or EMPTY; the message names it (`missing email`, `missing code`). The SDK passes what you give it, so a blank form field reaches the API as missing | check the field before calling — a blank email is not a sign-in attempt |
|
|
928
|
+
| `field_too_long` | 400 — a sign-in field (`email`, `code`, the test-session fields) is over 500 characters; the message names the field | shorten it |
|
|
847
929
|
| `conflict` | a keyed create / floor / stale `ifVersion` | it's the mechanism — tell the user it's taken |
|
|
848
930
|
| `html_not_allowed` | HTML in a community/addressed/direct field | store plain text |
|
|
849
931
|
| `invalid_publish` | `{ published }` on a non-public rule | drop it |
|
|
@@ -861,24 +943,25 @@ Branch on `err.code`. The gate's own codes (`session_invalid`,
|
|
|
861
943
|
| `unknown_plan` | no plan by that name | use a name from the list in the message |
|
|
862
944
|
| `plan_not_purchasable` | tried to check out the free default plan | nothing to buy — gate on the paid plan's name |
|
|
863
945
|
| `invalid_expand` | `expand` on a field/rule with no link shape | join in memory instead (private/public_read/admin_write have no links) |
|
|
864
|
-
| `scope_denied` | secret key used outside its dashboard-configured scope (or on auth/management routes) | scope the key to that collection, or use the right surface |
|
|
946
|
+
| `scope_denied` | secret key used outside its dashboard-configured scope (or on auth/management routes); a server key asked to sign in as the account owner or an admin, or to link/sync; a sync key asked for anything but relays and AI tools | scope the key to that collection, or use the right surface — linking and dev sync use the CLI key (Setup), production sync a Sync key (Secret keys → production) |
|
|
947
|
+
| `secret_key_expired` (403) | a sync key past its hour — the message names the instant | mint a new Sync key (Secret keys → production) and run `npx gemmein sync --live` again |
|
|
865
948
|
| `unsupported_file_type` (415) | upload isn't an allowed type | images (JPEG/PNG/WebP/GIF/HEIC) or documents (PDF/ZIP/EPUB) |
|
|
866
949
|
| `invalid_key` | a keyed create's `key` breaks the charset/length law — or `spendCredits`' `key` is not text of up to 200 chars | 1-120 chars of letters, numbers, `: _ . @ / -` (a create); text ≤ 200 (a spend) |
|
|
867
950
|
| `invalid_amount` / `invalid_reason` (400) | `spendCredits`: `amount` outside 1..10,000, or `reason` missing / over 200 chars | fix the field the code names |
|
|
868
951
|
| `dedupe_conflict` (409) | `spendCredits`: the `key` already names a different movement (another kind or another person) | a key is one movement — reuse it only to retry that same one |
|
|
869
952
|
| `credits_ceiling` (409) | a credit would carry the balance past 1,000,000,000 (a comp, a pack, a relay grant) — nothing was added | the balance is at its most |
|
|
870
953
|
| `invalid_body` (400) | on `g.ai.chat`: the body is not the provider's JSON request object, or is nested deeper than 32 levels | send the provider's own request object |
|
|
871
|
-
| `provider_error` (client-side, the provider's status) | `g.ai.text`: the provider answered a non-2xx; `err.message` is the provider's own reason | read it — the credit was refunded when the provider failed before its first byte |
|
|
872
|
-
| `ai_test_capped` (429) | the
|
|
954
|
+
| `provider_error` (client-side, the provider's status) | `g.ai.text` and `g.ai.runText`: the provider answered a non-2xx; `err.message` is the provider's own reason | read it — the credit was refunded when the provider failed before its first byte |
|
|
955
|
+
| `ai_test_capped` (429) | the AI tools page's test call — one a minute per app | wait a minute |
|
|
873
956
|
| `invalid_secret_key` (client-side) | `gemmeinServer()` got a missing/`pk_` key | pass the `sk_` key from a server env var |
|
|
874
|
-
| `invalid_response` (client-side, status 0) | the server answered 200 to `verifyEmailCode` without a session token — a proxy or mock in the path, not Gemmein | check `apiUrl` and anything rewriting responses; the call is safe to retry |
|
|
957
|
+
| `invalid_response` (client-side, status 0) | the server answered 200 to `verifyEmailCode` without a session token — a proxy or mock in the path, not Gemmein; or `g.ai.text` / `g.ai.runText` got a non-stream provider answer with no text to lift out | check `apiUrl` and anything rewriting responses; the call is safe to retry — for AI, read the stream with `g.ai.chat` or `g.ai.run` |
|
|
875
958
|
| `authentication_required` (401) | checkout/subscription/pay without a signed-in user | sign the user in first |
|
|
876
959
|
| `plan_has_no_link` (409) | the paid plan has no Payment Link pasted yet | ask the owner to paste it in their dashboard |
|
|
877
960
|
| `product_not_sellable` (409) | `g.payments.buy` on a product sold via a relay or not yet — there is no Payment Link to open | tell the buyer how the product is sold, or ask the owner to wire a road |
|
|
878
961
|
| `relay_missing` (400) | saving a relay action (or a product's `sold.relay`) names a relay that does not exist in this environment | create that relay first, or name one that exists |
|
|
879
962
|
| `account_suspended` (403) | the app owner's account is suspended (billing) | the owner fixes payment at app.gemmein.com |
|
|
880
963
|
| `credits_exhausted` (402) | the person's balance is below the spend — the message carries the balance ("this person has {balance} credits — the spend needs {amount}") | show the pack; never retry the same spend |
|
|
881
|
-
| `ai_not_configured` (409) | no provider key on this app and environment | the owner pastes one
|
|
964
|
+
| `ai_not_configured` (409) | no provider key on this app and environment | the owner pastes one on the AI tools page |
|
|
882
965
|
| `provider_required` (400) | more than one provider key is set and the call named none | pass `provider` |
|
|
883
966
|
| `model_not_allowed` (403) | the owner's allowlist does not name this model | use one the message lists |
|
|
884
967
|
| `ai_capped` (429) | 20 AI calls per person per minute | wait for `resetAt` |
|
package/dist/index.cjs
CHANGED
|
@@ -9,7 +9,7 @@ exports.gemmeinServer = gemmeinServer;
|
|
|
9
9
|
* second module). `scripts/sync-version.mjs` rewrites the literal from
|
|
10
10
|
* package.json before every build (`prebuild`), and a test pins the two
|
|
11
11
|
* equal, so a bump can never ship with a stale header. */
|
|
12
|
-
exports.SDK_VERSION = "0.
|
|
12
|
+
exports.SDK_VERSION = "0.9.0"; // synced from package.json — do not edit by hand
|
|
13
13
|
/** W9.1 / CLIENT-INFO-1: every request the SDK makes to Gemmein carries
|
|
14
14
|
* `x-client-info: gemmein-sdk/<version>`. The server records it on the
|
|
15
15
|
* secret-key usage ledger ("last seen from gemmein-sdk/0.5.0"), so a
|
|
@@ -391,16 +391,24 @@ class CreditsClient {
|
|
|
391
391
|
}
|
|
392
392
|
exports.CreditsClient = CreditsClient;
|
|
393
393
|
/**
|
|
394
|
-
* The AI route.
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* `
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
394
|
+
* The AI route. The primary path is a NAMED TOOL defined on the server:
|
|
395
|
+
* `run(name, inputs)` sends a name and inputs, the server composes the
|
|
396
|
+
* provider request from the tool's own instructions and template (never
|
|
397
|
+
* the browser), gates it, spends the tool's credits and streams the
|
|
398
|
+
* answer back; `runText` is the same call collected to one string;
|
|
399
|
+
* `calls()` is the signed-in person's own history. `chat` is the RAW
|
|
400
|
+
* call: it takes the provider's own request body — exactly what you would
|
|
401
|
+
* POST to OpenAI's /v1/chat/completions, Anthropic's /v1/messages or
|
|
402
|
+
* Google's generateContent — and answers with the fetch `Response`
|
|
403
|
+
* untouched, streaming intact (SSE stays SSE). Raw calls are off by
|
|
404
|
+
* default for every provider key (`raw_calls_off`, 403) until the founder
|
|
405
|
+
* switches them on for that key on the AI tools page. Gemmein spends a
|
|
406
|
+
* credit, adds the owner's key, forwards, and passes status and bytes
|
|
407
|
+
* back. Pass `tool` (W9.3b) to a raw call to price and gate it as a named
|
|
408
|
+
* tool instead of the implicit default (one credit, any allowed model, no
|
|
409
|
+
* gate). Response headers: `x-gemmein-credits-remaining` on every answer
|
|
410
|
+
* that passed the spend; `x-gemmein-credit: refunded` when the provider
|
|
411
|
+
* failed before its first byte.
|
|
404
412
|
*/
|
|
405
413
|
class AiClient {
|
|
406
414
|
constructor(config) {
|
|
@@ -411,7 +419,10 @@ class AiClient {
|
|
|
411
419
|
* for await (const chunk of res.body) { … }
|
|
412
420
|
*
|
|
413
421
|
* Browser sessions only — a server key is refused (`scope_denied`, 403).
|
|
414
|
-
* Refusals, all `GemmeinError`: `
|
|
422
|
+
* Refusals, all `GemmeinError`: `raw_calls_off` (403 — raw calls are off
|
|
423
|
+
* for this provider until the founder switches them on for its key on
|
|
424
|
+
* the AI tools page; call a named tool with `run` instead) ·
|
|
425
|
+
* `session_required` (401) ·
|
|
415
426
|
* `credits_exhausted` (402 — the message carries the balance; show your
|
|
416
427
|
* own "buy more" door, which is a product checkout) · `ai_not_configured`
|
|
417
428
|
* (409 — the owner has set no key) · `provider_required` (400) ·
|
|
@@ -457,6 +468,91 @@ class AiClient {
|
|
|
457
468
|
}
|
|
458
469
|
return response;
|
|
459
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* W9.6: run a named tool with INPUTS — the server composes the provider
|
|
473
|
+
* request from the tool's own instructions and template (never the
|
|
474
|
+
* browser), gates it, spends its credits and streams the answer back.
|
|
475
|
+
* The answer is the provider's own shape for the tool's provider (SSE
|
|
476
|
+
* when `stream`), so read it as you would `chat()`'s.
|
|
477
|
+
*
|
|
478
|
+
* const res = await g.ai.run("summarise", { text }, { stream: true });
|
|
479
|
+
*
|
|
480
|
+
* Browser sessions only — a server key is refused (`scope_denied`, 403).
|
|
481
|
+
* Refusals, all `GemmeinError`: `session_required` (401 — sign in
|
|
482
|
+
* first) · `ai_capped` (429 — 20 calls a minute per person;
|
|
483
|
+
* `err.resetAt`) · `unknown_tool` (404 — no tool by that name in this
|
|
484
|
+
* environment) · `tool_disabled` (403 — the owner switched it off) ·
|
|
485
|
+
* `entitlement_required` (403 — the message names the plan or product
|
|
486
|
+
* it needs) · `payload_too_large` (413 — inputs over 64 KB) ·
|
|
487
|
+
* `invalid_body` (400 — the body must be a JSON object
|
|
488
|
+
* `{ inputs, stream? }`) · `invalid_inputs` (400 — the message names the
|
|
489
|
+
* input and the rule) · `tool_incomplete` (409 — the tool composes
|
|
490
|
+
* nothing; a founder's fix) · `ai_not_configured` (409 — the tool's
|
|
491
|
+
* provider has no key set; the owner pastes one) · `credits_exhausted`
|
|
492
|
+
* (402 — the message names the tool, its price and the balance) ·
|
|
493
|
+
* `provider_unreachable` (502 — no answer before the first byte; the
|
|
494
|
+
* tool's credits are refunded, header `x-gemmein-credit: refunded`).
|
|
495
|
+
* The provider's own answer — 2xx or not — is returned as it came; read
|
|
496
|
+
* `res.ok` yourself. `x-gemmein-tool` names the tool.
|
|
497
|
+
*/
|
|
498
|
+
async run(tool, inputs = {}, options = {}) {
|
|
499
|
+
const url = new URL(`/ai/run/${encodeURIComponent(tool)}`, this.config.apiUrl);
|
|
500
|
+
const response = await fetch(url, {
|
|
501
|
+
method: "POST",
|
|
502
|
+
body: JSON.stringify({ inputs, ...(options.stream ? { stream: true } : {}) }),
|
|
503
|
+
headers: await runtimeHeaders(this.config, { "content-type": "application/json" }),
|
|
504
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
505
|
+
});
|
|
506
|
+
if (!response.ok) {
|
|
507
|
+
if (isForwardedAnswer(response)) {
|
|
508
|
+
const peek = (await response.clone().json().catch(() => null));
|
|
509
|
+
if (peek?.code !== "provider_unreachable")
|
|
510
|
+
return response;
|
|
511
|
+
}
|
|
512
|
+
const errorBody = await readErrorBody(response);
|
|
513
|
+
if (errorBody.code === "auth_expired")
|
|
514
|
+
await this.config.tokenStore.clear();
|
|
515
|
+
throw new GemmeinError({ status: response.status, ...errorBody });
|
|
516
|
+
}
|
|
517
|
+
return response;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* W9.6: `run()` without a stream, as one string — the text lifted out
|
|
521
|
+
* of the tool's provider's answer (the same readers `text()` uses).
|
|
522
|
+
* `run()`'s refusals, plus: a provider's own non-2xx throws
|
|
523
|
+
* `provider_error` with the provider's status and message; an answer
|
|
524
|
+
* with no text to lift out throws `invalid_response` (status 0).
|
|
525
|
+
*
|
|
526
|
+
* const summary = await g.ai.runText("summarise", { text });
|
|
527
|
+
*/
|
|
528
|
+
async runText(tool, inputs = {}, options = {}) {
|
|
529
|
+
const response = await this.run(tool, inputs, options);
|
|
530
|
+
if (!response.ok) {
|
|
531
|
+
throw new GemmeinError({ status: response.status, code: "provider_error", message: await providerErrorMessage(response) });
|
|
532
|
+
}
|
|
533
|
+
const data = (await response.json());
|
|
534
|
+
const text = extractAiText(data);
|
|
535
|
+
if (text === null) {
|
|
536
|
+
throw new GemmeinError({ status: 0, code: "invalid_response", message: "the provider answered without any text — use g.ai.run() with { stream: true } and read the stream" });
|
|
537
|
+
}
|
|
538
|
+
return text;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* W9.6 §16: the signed-in person's OWN AI calls, newest first — what they
|
|
542
|
+
* ran, when, what it cost, how it ended; the prompt and answer only where
|
|
543
|
+
* the tool keeps them. Session required.
|
|
544
|
+
*
|
|
545
|
+
* const { calls, nextCursor } = await g.ai.calls();
|
|
546
|
+
*/
|
|
547
|
+
async calls(options = {}) {
|
|
548
|
+
const params = new URLSearchParams();
|
|
549
|
+
if (options.limit)
|
|
550
|
+
params.set("limit", String(options.limit));
|
|
551
|
+
if (options.before)
|
|
552
|
+
params.set("before", options.before);
|
|
553
|
+
const query = params.toString();
|
|
554
|
+
return runtimeRequest(this.config, `/auth/ai-calls${query ? `?${query}` : ""}`);
|
|
555
|
+
}
|
|
460
556
|
/**
|
|
461
557
|
* The non-streaming convenience: one call, one string. Pass a body that
|
|
462
558
|
* does NOT stream (`stream` unset or false); the provider's JSON answer is
|
package/dist/index.d.cts
CHANGED
|
@@ -120,13 +120,13 @@ export type AuthSession = {
|
|
|
120
120
|
* second module). `scripts/sync-version.mjs` rewrites the literal from
|
|
121
121
|
* package.json before every build (`prebuild`), and a test pins the two
|
|
122
122
|
* equal, so a bump can never ship with a stale header. */
|
|
123
|
-
export declare const SDK_VERSION = "0.
|
|
123
|
+
export declare const SDK_VERSION = "0.9.0";
|
|
124
124
|
/** W9.1 / CLIENT-INFO-1: every request the SDK makes to Gemmein carries
|
|
125
125
|
* `x-client-info: gemmein-sdk/<version>`. The server records it on the
|
|
126
126
|
* secret-key usage ledger ("last seen from gemmein-sdk/0.5.0"), so a
|
|
127
127
|
* misbehaving integration can be attributed to an SDK version from day
|
|
128
128
|
* one. It is a report, not a proof — any caller can set it. */
|
|
129
|
-
export declare const CLIENT_INFO = "gemmein-sdk/0.
|
|
129
|
+
export declare const CLIENT_INFO = "gemmein-sdk/0.9.0";
|
|
130
130
|
export declare class GemmeinError extends Error {
|
|
131
131
|
readonly status: number;
|
|
132
132
|
readonly code: string;
|
|
@@ -387,6 +387,23 @@ export declare class AccountClient {
|
|
|
387
387
|
delete(): Promise<unknown>;
|
|
388
388
|
}
|
|
389
389
|
export type AiProvider = "openai" | "anthropic" | "google";
|
|
390
|
+
/** W9.6 §16: one of the person's own AI calls, as `g.ai.calls()` lists them. */
|
|
391
|
+
export type AiCallRecord = {
|
|
392
|
+
id: string;
|
|
393
|
+
tool: string;
|
|
394
|
+
kind: string;
|
|
395
|
+
provider: string;
|
|
396
|
+
model: string | null;
|
|
397
|
+
tokensIn: number | null;
|
|
398
|
+
tokensOut: number | null;
|
|
399
|
+
credits: number;
|
|
400
|
+
outcome: "ok" | "refused" | "provider_error" | "unreachable" | "client_closed" | "stream_ended";
|
|
401
|
+
refusalCode: string | null;
|
|
402
|
+
latencyMs: number | null;
|
|
403
|
+
prompt: string | null;
|
|
404
|
+
answer: string | null;
|
|
405
|
+
createdAt: string;
|
|
406
|
+
};
|
|
390
407
|
export type AiChatOptions = {
|
|
391
408
|
/** Which configured provider answers. Optional when exactly one key is
|
|
392
409
|
* set; refused `provider_required` (400) when it is ambiguous. Refused
|
|
@@ -396,7 +413,10 @@ export type AiChatOptions = {
|
|
|
396
413
|
/** W9.3b: a named AI tool (owner-configured in the console — credits,
|
|
397
414
|
* gate and provider/model are the tool's, not this call's). Sent as
|
|
398
415
|
* `?tool=`, never in the body. Omitted → the implicit default tool: one
|
|
399
|
-
* credit, any allowed model, no gate.
|
|
416
|
+
* credit, any allowed model, no gate. With or without `tool`, `chat` is
|
|
417
|
+
* a RAW call — off by default (`raw_calls_off`, 403) until the founder
|
|
418
|
+
* switches raw calls on for that provider's key on the AI tools page;
|
|
419
|
+
* the normal path to a named tool is `run(name, inputs)`. */
|
|
400
420
|
tool?: string;
|
|
401
421
|
/** Abort the call — the stream closes; a call that dies mid-stream is
|
|
402
422
|
* not refunded. */
|
|
@@ -433,16 +453,24 @@ export declare class CreditsClient {
|
|
|
433
453
|
}>;
|
|
434
454
|
}
|
|
435
455
|
/**
|
|
436
|
-
* The AI route.
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* `
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
456
|
+
* The AI route. The primary path is a NAMED TOOL defined on the server:
|
|
457
|
+
* `run(name, inputs)` sends a name and inputs, the server composes the
|
|
458
|
+
* provider request from the tool's own instructions and template (never
|
|
459
|
+
* the browser), gates it, spends the tool's credits and streams the
|
|
460
|
+
* answer back; `runText` is the same call collected to one string;
|
|
461
|
+
* `calls()` is the signed-in person's own history. `chat` is the RAW
|
|
462
|
+
* call: it takes the provider's own request body — exactly what you would
|
|
463
|
+
* POST to OpenAI's /v1/chat/completions, Anthropic's /v1/messages or
|
|
464
|
+
* Google's generateContent — and answers with the fetch `Response`
|
|
465
|
+
* untouched, streaming intact (SSE stays SSE). Raw calls are off by
|
|
466
|
+
* default for every provider key (`raw_calls_off`, 403) until the founder
|
|
467
|
+
* switches them on for that key on the AI tools page. Gemmein spends a
|
|
468
|
+
* credit, adds the owner's key, forwards, and passes status and bytes
|
|
469
|
+
* back. Pass `tool` (W9.3b) to a raw call to price and gate it as a named
|
|
470
|
+
* tool instead of the implicit default (one credit, any allowed model, no
|
|
471
|
+
* gate). Response headers: `x-gemmein-credits-remaining` on every answer
|
|
472
|
+
* that passed the spend; `x-gemmein-credit: refunded` when the provider
|
|
473
|
+
* failed before its first byte.
|
|
446
474
|
*/
|
|
447
475
|
export declare class AiClient {
|
|
448
476
|
private readonly config;
|
|
@@ -452,7 +480,10 @@ export declare class AiClient {
|
|
|
452
480
|
* for await (const chunk of res.body) { … }
|
|
453
481
|
*
|
|
454
482
|
* Browser sessions only — a server key is refused (`scope_denied`, 403).
|
|
455
|
-
* Refusals, all `GemmeinError`: `
|
|
483
|
+
* Refusals, all `GemmeinError`: `raw_calls_off` (403 — raw calls are off
|
|
484
|
+
* for this provider until the founder switches them on for its key on
|
|
485
|
+
* the AI tools page; call a named tool with `run` instead) ·
|
|
486
|
+
* `session_required` (401) ·
|
|
456
487
|
* `credits_exhausted` (402 — the message carries the balance; show your
|
|
457
488
|
* own "buy more" door, which is a product checkout) · `ai_not_configured`
|
|
458
489
|
* (409 — the owner has set no key) · `provider_required` (400) ·
|
|
@@ -472,6 +503,63 @@ export declare class AiClient {
|
|
|
472
503
|
* on the implicit default.
|
|
473
504
|
*/
|
|
474
505
|
chat(body: Record<string, unknown>, options?: AiChatOptions): Promise<Response>;
|
|
506
|
+
/**
|
|
507
|
+
* W9.6: run a named tool with INPUTS — the server composes the provider
|
|
508
|
+
* request from the tool's own instructions and template (never the
|
|
509
|
+
* browser), gates it, spends its credits and streams the answer back.
|
|
510
|
+
* The answer is the provider's own shape for the tool's provider (SSE
|
|
511
|
+
* when `stream`), so read it as you would `chat()`'s.
|
|
512
|
+
*
|
|
513
|
+
* const res = await g.ai.run("summarise", { text }, { stream: true });
|
|
514
|
+
*
|
|
515
|
+
* Browser sessions only — a server key is refused (`scope_denied`, 403).
|
|
516
|
+
* Refusals, all `GemmeinError`: `session_required` (401 — sign in
|
|
517
|
+
* first) · `ai_capped` (429 — 20 calls a minute per person;
|
|
518
|
+
* `err.resetAt`) · `unknown_tool` (404 — no tool by that name in this
|
|
519
|
+
* environment) · `tool_disabled` (403 — the owner switched it off) ·
|
|
520
|
+
* `entitlement_required` (403 — the message names the plan or product
|
|
521
|
+
* it needs) · `payload_too_large` (413 — inputs over 64 KB) ·
|
|
522
|
+
* `invalid_body` (400 — the body must be a JSON object
|
|
523
|
+
* `{ inputs, stream? }`) · `invalid_inputs` (400 — the message names the
|
|
524
|
+
* input and the rule) · `tool_incomplete` (409 — the tool composes
|
|
525
|
+
* nothing; a founder's fix) · `ai_not_configured` (409 — the tool's
|
|
526
|
+
* provider has no key set; the owner pastes one) · `credits_exhausted`
|
|
527
|
+
* (402 — the message names the tool, its price and the balance) ·
|
|
528
|
+
* `provider_unreachable` (502 — no answer before the first byte; the
|
|
529
|
+
* tool's credits are refunded, header `x-gemmein-credit: refunded`).
|
|
530
|
+
* The provider's own answer — 2xx or not — is returned as it came; read
|
|
531
|
+
* `res.ok` yourself. `x-gemmein-tool` names the tool.
|
|
532
|
+
*/
|
|
533
|
+
run(tool: string, inputs?: Record<string, string | number | boolean>, options?: {
|
|
534
|
+
stream?: boolean;
|
|
535
|
+
signal?: AbortSignal;
|
|
536
|
+
}): Promise<Response>;
|
|
537
|
+
/**
|
|
538
|
+
* W9.6: `run()` without a stream, as one string — the text lifted out
|
|
539
|
+
* of the tool's provider's answer (the same readers `text()` uses).
|
|
540
|
+
* `run()`'s refusals, plus: a provider's own non-2xx throws
|
|
541
|
+
* `provider_error` with the provider's status and message; an answer
|
|
542
|
+
* with no text to lift out throws `invalid_response` (status 0).
|
|
543
|
+
*
|
|
544
|
+
* const summary = await g.ai.runText("summarise", { text });
|
|
545
|
+
*/
|
|
546
|
+
runText(tool: string, inputs?: Record<string, string | number | boolean>, options?: {
|
|
547
|
+
signal?: AbortSignal;
|
|
548
|
+
}): Promise<string>;
|
|
549
|
+
/**
|
|
550
|
+
* W9.6 §16: the signed-in person's OWN AI calls, newest first — what they
|
|
551
|
+
* ran, when, what it cost, how it ended; the prompt and answer only where
|
|
552
|
+
* the tool keeps them. Session required.
|
|
553
|
+
*
|
|
554
|
+
* const { calls, nextCursor } = await g.ai.calls();
|
|
555
|
+
*/
|
|
556
|
+
calls(options?: {
|
|
557
|
+
limit?: number;
|
|
558
|
+
before?: string | null;
|
|
559
|
+
}): Promise<{
|
|
560
|
+
calls: AiCallRecord[];
|
|
561
|
+
nextCursor: string | null;
|
|
562
|
+
}>;
|
|
475
563
|
/**
|
|
476
564
|
* The non-streaming convenience: one call, one string. Pass a body that
|
|
477
565
|
* does NOT stream (`stream` unset or false); the provider's JSON answer is
|