@gemmein/sdk 0.7.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 +73 -0
- package/README.md +3 -1
- package/REFERENCE.md +191 -55
- package/dist/index.cjs +123 -17
- package/dist/index.d.cts +120 -18
- package/dist/index.d.ts +120 -18
- package/dist/index.js +123 -17
- package/llms.txt +284 -78
- package/migrations/README.md +1 -0
- package/migrations/raw-calls-off.md +51 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* second module). `scripts/sync-version.mjs` rewrites the literal from
|
|
5
5
|
* package.json before every build (`prebuild`), and a test pins the two
|
|
6
6
|
* equal, so a bump can never ship with a stale header. */
|
|
7
|
-
export const SDK_VERSION = "0.
|
|
7
|
+
export const SDK_VERSION = "0.9.0"; // synced from package.json — do not edit by hand
|
|
8
8
|
/** W9.1 / CLIENT-INFO-1: every request the SDK makes to Gemmein carries
|
|
9
9
|
* `x-client-info: gemmein-sdk/<version>`. The server records it on the
|
|
10
10
|
* secret-key usage ledger ("last seen from gemmein-sdk/0.5.0"), so a
|
|
@@ -375,14 +375,24 @@ export class CreditsClient {
|
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
377
|
/**
|
|
378
|
-
* The AI route.
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
378
|
+
* The AI route. The primary path is a NAMED TOOL defined on the server:
|
|
379
|
+
* `run(name, inputs)` sends a name and inputs, the server composes the
|
|
380
|
+
* provider request from the tool's own instructions and template (never
|
|
381
|
+
* the browser), gates it, spends the tool's credits and streams the
|
|
382
|
+
* answer back; `runText` is the same call collected to one string;
|
|
383
|
+
* `calls()` is the signed-in person's own history. `chat` is the RAW
|
|
384
|
+
* call: it takes the provider's own request body — exactly what you would
|
|
385
|
+
* POST to OpenAI's /v1/chat/completions, Anthropic's /v1/messages or
|
|
386
|
+
* Google's generateContent — and answers with the fetch `Response`
|
|
387
|
+
* untouched, streaming intact (SSE stays SSE). Raw calls are off by
|
|
388
|
+
* default for every provider key (`raw_calls_off`, 403) until the founder
|
|
389
|
+
* switches them on for that key on the AI tools page. Gemmein spends a
|
|
390
|
+
* credit, adds the owner's key, forwards, and passes status and bytes
|
|
391
|
+
* back. Pass `tool` (W9.3b) to a raw call to price and gate it as a named
|
|
392
|
+
* tool instead of the implicit default (one credit, any allowed model, no
|
|
393
|
+
* gate). Response headers: `x-gemmein-credits-remaining` on every answer
|
|
394
|
+
* that passed the spend; `x-gemmein-credit: refunded` when the provider
|
|
395
|
+
* failed before its first byte.
|
|
386
396
|
*/
|
|
387
397
|
export class AiClient {
|
|
388
398
|
constructor(config) {
|
|
@@ -393,23 +403,34 @@ export class AiClient {
|
|
|
393
403
|
* for await (const chunk of res.body) { … }
|
|
394
404
|
*
|
|
395
405
|
* Browser sessions only — a server key is refused (`scope_denied`, 403).
|
|
396
|
-
* Refusals, all `GemmeinError`: `
|
|
406
|
+
* Refusals, all `GemmeinError`: `raw_calls_off` (403 — raw calls are off
|
|
407
|
+
* for this provider until the founder switches them on for its key on
|
|
408
|
+
* the AI tools page; call a named tool with `run` instead) ·
|
|
409
|
+
* `session_required` (401) ·
|
|
397
410
|
* `credits_exhausted` (402 — the message carries the balance; show your
|
|
398
411
|
* own "buy more" door, which is a product checkout) · `ai_not_configured`
|
|
399
412
|
* (409 — the owner has set no key) · `provider_required` (400) ·
|
|
400
413
|
* `model_not_allowed` (403 — the owner's models list) · `ai_capped`
|
|
401
414
|
* (429 — 20 calls a minute per person; `err.resetAt`) ·
|
|
402
415
|
* `payload_too_large` (413 — 256 KB) · `provider_unreachable` (502,
|
|
403
|
-
* before the first byte, refunded).
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* is
|
|
416
|
+
* before the first byte, refunded). W9.3b, `tool` only: `unknown_tool`
|
|
417
|
+
* (404 — no tool by that name in this environment) · `tool_disabled`
|
|
418
|
+
* (403) · `entitlement_required` (403 — the message names the plan or
|
|
419
|
+
* product that unlocks it) · `model_pinned` (403 — the tool's model is
|
|
420
|
+
* fixed; leave `model` out of the body). Those are GEMMEIN's refusals.
|
|
421
|
+
* The PROVIDER's own answer — 2xx or not — is returned as it came: an
|
|
422
|
+
* answer that carries `x-gemmein-credits-remaining` passed the spend, so
|
|
423
|
+
* its status and body are the provider's; read `res.ok` / `res.status`
|
|
424
|
+
* yourself (a provider 4xx before the first byte is refunded, header
|
|
425
|
+
* `x-gemmein-credit: refunded`). `x-gemmein-tool` names the tool; absent
|
|
426
|
+
* on the implicit default.
|
|
409
427
|
*/
|
|
410
428
|
async chat(body, options = {}) {
|
|
411
429
|
const payload = options.provider ? { provider: options.provider, ...body } : body;
|
|
412
|
-
const
|
|
430
|
+
const url = new URL("/ai/chat", this.config.apiUrl);
|
|
431
|
+
if (options.tool)
|
|
432
|
+
url.searchParams.set("tool", options.tool);
|
|
433
|
+
const response = await fetch(url, {
|
|
413
434
|
method: "POST",
|
|
414
435
|
body: JSON.stringify(payload),
|
|
415
436
|
headers: await runtimeHeaders(this.config, { "content-type": "application/json" }),
|
|
@@ -431,6 +452,91 @@ export class AiClient {
|
|
|
431
452
|
}
|
|
432
453
|
return response;
|
|
433
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* W9.6: run a named tool with INPUTS — the server composes the provider
|
|
457
|
+
* request from the tool's own instructions and template (never the
|
|
458
|
+
* browser), gates it, spends its credits and streams the answer back.
|
|
459
|
+
* The answer is the provider's own shape for the tool's provider (SSE
|
|
460
|
+
* when `stream`), so read it as you would `chat()`'s.
|
|
461
|
+
*
|
|
462
|
+
* const res = await g.ai.run("summarise", { text }, { stream: true });
|
|
463
|
+
*
|
|
464
|
+
* Browser sessions only — a server key is refused (`scope_denied`, 403).
|
|
465
|
+
* Refusals, all `GemmeinError`: `session_required` (401 — sign in
|
|
466
|
+
* first) · `ai_capped` (429 — 20 calls a minute per person;
|
|
467
|
+
* `err.resetAt`) · `unknown_tool` (404 — no tool by that name in this
|
|
468
|
+
* environment) · `tool_disabled` (403 — the owner switched it off) ·
|
|
469
|
+
* `entitlement_required` (403 — the message names the plan or product
|
|
470
|
+
* it needs) · `payload_too_large` (413 — inputs over 64 KB) ·
|
|
471
|
+
* `invalid_body` (400 — the body must be a JSON object
|
|
472
|
+
* `{ inputs, stream? }`) · `invalid_inputs` (400 — the message names the
|
|
473
|
+
* input and the rule) · `tool_incomplete` (409 — the tool composes
|
|
474
|
+
* nothing; a founder's fix) · `ai_not_configured` (409 — the tool's
|
|
475
|
+
* provider has no key set; the owner pastes one) · `credits_exhausted`
|
|
476
|
+
* (402 — the message names the tool, its price and the balance) ·
|
|
477
|
+
* `provider_unreachable` (502 — no answer before the first byte; the
|
|
478
|
+
* tool's credits are refunded, header `x-gemmein-credit: refunded`).
|
|
479
|
+
* The provider's own answer — 2xx or not — is returned as it came; read
|
|
480
|
+
* `res.ok` yourself. `x-gemmein-tool` names the tool.
|
|
481
|
+
*/
|
|
482
|
+
async run(tool, inputs = {}, options = {}) {
|
|
483
|
+
const url = new URL(`/ai/run/${encodeURIComponent(tool)}`, this.config.apiUrl);
|
|
484
|
+
const response = await fetch(url, {
|
|
485
|
+
method: "POST",
|
|
486
|
+
body: JSON.stringify({ inputs, ...(options.stream ? { stream: true } : {}) }),
|
|
487
|
+
headers: await runtimeHeaders(this.config, { "content-type": "application/json" }),
|
|
488
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
489
|
+
});
|
|
490
|
+
if (!response.ok) {
|
|
491
|
+
if (isForwardedAnswer(response)) {
|
|
492
|
+
const peek = (await response.clone().json().catch(() => null));
|
|
493
|
+
if (peek?.code !== "provider_unreachable")
|
|
494
|
+
return response;
|
|
495
|
+
}
|
|
496
|
+
const errorBody = await readErrorBody(response);
|
|
497
|
+
if (errorBody.code === "auth_expired")
|
|
498
|
+
await this.config.tokenStore.clear();
|
|
499
|
+
throw new GemmeinError({ status: response.status, ...errorBody });
|
|
500
|
+
}
|
|
501
|
+
return response;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* W9.6: `run()` without a stream, as one string — the text lifted out
|
|
505
|
+
* of the tool's provider's answer (the same readers `text()` uses).
|
|
506
|
+
* `run()`'s refusals, plus: a provider's own non-2xx throws
|
|
507
|
+
* `provider_error` with the provider's status and message; an answer
|
|
508
|
+
* with no text to lift out throws `invalid_response` (status 0).
|
|
509
|
+
*
|
|
510
|
+
* const summary = await g.ai.runText("summarise", { text });
|
|
511
|
+
*/
|
|
512
|
+
async runText(tool, inputs = {}, options = {}) {
|
|
513
|
+
const response = await this.run(tool, inputs, options);
|
|
514
|
+
if (!response.ok) {
|
|
515
|
+
throw new GemmeinError({ status: response.status, code: "provider_error", message: await providerErrorMessage(response) });
|
|
516
|
+
}
|
|
517
|
+
const data = (await response.json());
|
|
518
|
+
const text = extractAiText(data);
|
|
519
|
+
if (text === null) {
|
|
520
|
+
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" });
|
|
521
|
+
}
|
|
522
|
+
return text;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* W9.6 §16: the signed-in person's OWN AI calls, newest first — what they
|
|
526
|
+
* ran, when, what it cost, how it ended; the prompt and answer only where
|
|
527
|
+
* the tool keeps them. Session required.
|
|
528
|
+
*
|
|
529
|
+
* const { calls, nextCursor } = await g.ai.calls();
|
|
530
|
+
*/
|
|
531
|
+
async calls(options = {}) {
|
|
532
|
+
const params = new URLSearchParams();
|
|
533
|
+
if (options.limit)
|
|
534
|
+
params.set("limit", String(options.limit));
|
|
535
|
+
if (options.before)
|
|
536
|
+
params.set("before", options.before);
|
|
537
|
+
const query = params.toString();
|
|
538
|
+
return runtimeRequest(this.config, `/auth/ai-calls${query ? `?${query}` : ""}`);
|
|
539
|
+
}
|
|
434
540
|
/**
|
|
435
541
|
* The non-streaming convenience: one call, one string. Pass a body that
|
|
436
542
|
* does NOT stream (`stream` unset or false); the provider's JSON answer is
|