@loomcycle/client 1.106.0 → 1.107.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/README.md +104 -2
- package/dist/cjs/client.js +30 -0
- package/dist/cjs/errors.js +91 -1
- package/dist/cjs/fetch-helpers.js +65 -5
- package/dist/cjs/index.js +7 -2
- package/dist/client.d.ts +25 -1
- package/dist/client.js +30 -0
- package/dist/errors.d.ts +108 -0
- package/dist/errors.js +87 -0
- package/dist/fetch-helpers.d.ts +10 -2
- package/dist/fetch-helpers.js +66 -6
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/types.d.ts +131 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -323,6 +323,77 @@ const { entries } = await client.listVolumes();
|
|
|
323
323
|
|
|
324
324
|
Refusals throw `SubstrateToolRefusedError` (collision with a static volume name, no `dynamic_root` configured, cross-tenant); transport failures throw the usual typed errors.
|
|
325
325
|
|
|
326
|
+
### Decision models
|
|
327
|
+
|
|
328
|
+
Ask a **decision model** typed questions about a piece of text and get each answer with its probabilities. A decision model does not write: it reads the `state` you give it and answers a choice, a yes/no or a score, so there is no reply to parse and a call costs a few output tokens. Use it to route, gate, rank or grade text you already have. No run is involved; the tokens are charged to the caller and count against its token budget.
|
|
329
|
+
|
|
330
|
+
| Method | Returns | Notes |
|
|
331
|
+
|---|---|---|
|
|
332
|
+
| `decide(req: DecideRequest)` | `Promise<DecideResponse>` | `{model?, state, questions}` → `{model, provider, served_model, answers, usage}`. Mirrors `POST /v1/_decide`. |
|
|
333
|
+
| `listDecisionModels()` | `Promise<DecisionModelsResponse>` | The models a call may name, each with its limits, and the default. Mirrors `GET /v1/_decide/models`. |
|
|
334
|
+
|
|
335
|
+
`questions` is keyed by names you choose, and each answer comes back under its question's name. `criteria` is typed per question type:
|
|
336
|
+
|
|
337
|
+
| `type` | `criteria` | Answer |
|
|
338
|
+
|---|---|---|
|
|
339
|
+
| `choice` | required — `Record<string, string \| null>`: each key is an option, its value describes it (`null` when the key explains itself) | `{type, choice, probabilities, confidence}` |
|
|
340
|
+
| `noul` | optional — `{true?: string; false?: string}` | `{type, noul}` — the probability of YES, 0 to 1 |
|
|
341
|
+
| `score` | required — `string[]`, level descriptions **lowest first** | `{type, score, legend, probabilities, confidence}` — `score` is the expected position, counted from 0 |
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
const res = await client.decide({
|
|
345
|
+
state: { ticket: "My invoice for March was charged twice and I want my money back." },
|
|
346
|
+
questions: {
|
|
347
|
+
route: {
|
|
348
|
+
type: "choice",
|
|
349
|
+
instructions: "Which team should handle this ticket?",
|
|
350
|
+
criteria: { billing: "invoices, refunds, charges", support: "bugs, outages", sales: null },
|
|
351
|
+
},
|
|
352
|
+
urgent: { type: "noul", instructions: "Does this ticket need a reply within the hour?" },
|
|
353
|
+
detail: {
|
|
354
|
+
type: "score",
|
|
355
|
+
instructions: "How complete is the problem report?",
|
|
356
|
+
criteria: ["no detail", "some detail", "everything needed"],
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const route = res.answers.route;
|
|
362
|
+
if (route?.type === "choice") console.log(route.choice, route.probabilities);
|
|
363
|
+
const urgent = res.answers.urgent;
|
|
364
|
+
if (urgent?.type === "noul" && urgent.noul > 0.5) escalate();
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
- **`noul: 0` is an answer** (a definite no), not a missing one — never test it for truthiness.
|
|
368
|
+
- **The probabilities are not calibrated.** Compare options within one answer; do not treat a fixed threshold as a guarantee.
|
|
369
|
+
- **An answer is the model's own JSON, passed through unchanged.** The answer types name the documented fields and stay open to others, so a field a later model adds is readable without an adapter release.
|
|
370
|
+
- Omit `model` for the deployment's default; name one only from `listDecisionModels()`.
|
|
371
|
+
|
|
372
|
+
A refusal throws with the decision's own code on `e.code` (see [Errors](#errors)):
|
|
373
|
+
|
|
374
|
+
| Status | `e.code` | What to do |
|
|
375
|
+
|---|---|---|
|
|
376
|
+
| 400 | `invalid_input` / `bad_question` / `bad_options` / `too_many_questions` | Fix the request; the message names the fault. |
|
|
377
|
+
| 400 | `model_not_allowed` | Omit `model`, or use a name from `listDecisionModels()`. |
|
|
378
|
+
| 403 | `operator_key_restricted` | The caller may not use the operator's provider key and has none of its own. Do not retry. |
|
|
379
|
+
| 413 | `prompt_too_large` | The request does not fit the model's context and is never shortened for you: shorten `state` or ask fewer questions. |
|
|
380
|
+
| 429 | `token_limit_exceeded` | The caller is at a hard token budget. Retrying does not help until the budget is raised or the month rolls over. |
|
|
381
|
+
| 502 | `model_not_found` / `call_failed` | The provider does not serve that model (the same call fails again) / the call did not complete (try once more). |
|
|
382
|
+
| 503 | `decision_not_configured` | This deployment declares no decision models. Do not retry. |
|
|
383
|
+
| 504 | `timeout` | Send the same call again; if it repeats, make it smaller. |
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
try {
|
|
387
|
+
await client.decide(req);
|
|
388
|
+
} catch (e) {
|
|
389
|
+
if (e instanceof LoomcycleError && e.code === "prompt_too_large") {
|
|
390
|
+
// shorten state and retry
|
|
391
|
+
} else throw e;
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Two of these have a class of their own: a `prompt_too_large` throws `PromptTooLargeError` and a `token_limit_exceeded` throws `TokenLimitExceededError` (with the budget on `e.scope` / `e.used` / `e.limit`). For the rest, branch on `e.code`, not on the class: the class follows the HTTP status alone.
|
|
396
|
+
|
|
326
397
|
### Channels + run-state stream (v0.9.x n8n Phase 0)
|
|
327
398
|
|
|
328
399
|
Two substrate-side surfaces added in the n8n integration's Phase 0 wire-API work. Useful for any orchestrator (not just n8n) that needs to see channel state or subscribe to run-state transitions.
|
|
@@ -458,6 +529,8 @@ These events are part of the persisted transcript (not the live `runStreaming` e
|
|
|
458
529
|
|
|
459
530
|
Non-2xx responses throw typed subclasses of `LoomcycleError`. The original HTTP status is on `e.status`; the truncated response body is on `e.bodyText` (≤1 KiB).
|
|
460
531
|
|
|
532
|
+
When the error body is JSON with a string `code` (`token_limit_exceeded`, `per_user_quota_exhausted`, `model_not_allowed`, …), that code is on `e.code`, whichever class the status maps to. A status is shared between unrelated conditions — a 429 is backpressure, a per-user quota or a token budget — so branch on `e.code` when you need to tell them apart. It is `undefined` for a plain-text body.
|
|
533
|
+
|
|
461
534
|
| HTTP status / body | Exception class |
|
|
462
535
|
|---|---|
|
|
463
536
|
| 400 | `InvalidArgumentError` |
|
|
@@ -472,13 +545,42 @@ Non-2xx responses throw typed subclasses of `LoomcycleError`. The original HTTP
|
|
|
472
545
|
| 409 + "session" | `SessionBusyError` |
|
|
473
546
|
| 409 + "agent_id" | `AgentIDInUseError` |
|
|
474
547
|
| 409 (other) | `LoomcycleError` (base) |
|
|
475
|
-
| 413 | `SnapshotTooLargeError` |
|
|
548
|
+
| 413 + code `snapshot_too_large` | `SnapshotTooLargeError` ⎫ |
|
|
549
|
+
| 413 + code `prompt_too_large` | `PromptTooLargeError` ⎬ all are `instanceof SnapshotTooLargeError` (back-compat) |
|
|
550
|
+
| 413 (other) | `RequestTooLargeError` ⎭ |
|
|
476
551
|
| 422 | `SnapshotVersionError` |
|
|
477
|
-
| 429 | `
|
|
552
|
+
| 429 + code `per_user_quota_exhausted` | `PerUserQuotaExhaustedError` |
|
|
553
|
+
| 429 + code `token_limit_exceeded` | `TokenLimitExceededError` (subclass of `BackpressureError` — back-compat) |
|
|
554
|
+
| 429 (other) | `BackpressureError` |
|
|
478
555
|
| 503 + "pause manager not configured" | `PauseNotConfiguredError` (subclass of `UnavailableError` — back-compat) |
|
|
479
556
|
| 503 (other) | `UnavailableError` |
|
|
480
557
|
| 500 / other | `LoomcycleError` (base) |
|
|
481
558
|
|
|
559
|
+
**429: a token budget is not backpressure.** A plain `BackpressureError` is transient: the server is busy, back off and retry. `TokenLimitExceededError` is not: the caller is at a hard token budget (run creation and `decide()` both refuse with it), and the same call is refused until the budget is raised or the calendar month rolls over. It extends `BackpressureError` only so that an existing `instanceof BackpressureError` keeps catching it, so test for it first. The client never retries on its own; this matters to your retry loop.
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
import { BackpressureError, TokenLimitExceededError } from "@loomcycle/client";
|
|
563
|
+
|
|
564
|
+
try {
|
|
565
|
+
for await (const ev of client.runStreaming({ /* ... */ })) {}
|
|
566
|
+
} catch (e) {
|
|
567
|
+
if (e instanceof TokenLimitExceededError) {
|
|
568
|
+
// Not retryable now. e.scope is "operator" | "tenant" | "user"; e.scopeId is
|
|
569
|
+
// the tenant or user (null for the operator-wide budget); e.used and e.limit
|
|
570
|
+
// are tokens; e.window is "month". Each is null if the server did not send it.
|
|
571
|
+
console.error(`token budget reached for ${e.scope}: ${e.used}/${e.limit} this ${e.window}`);
|
|
572
|
+
} else if (e instanceof BackpressureError) {
|
|
573
|
+
// Transient: back off and retry.
|
|
574
|
+
} else {
|
|
575
|
+
throw e;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**413: three classes, one legacy base.** Every 413 used to throw `SnapshotTooLargeError`, whatever was too large. A 413 is now named for its cause: `PromptTooLargeError` for a `decide()` request that does not fit the model's context (shorten `state` or ask fewer questions), `RequestTooLargeError` for any other non-snapshot 413 (a request body over the server's limit, a memory value or quota, a channel payload — `e.code` says which), and `SnapshotTooLargeError` itself for a snapshot over its cap. `PromptTooLargeError` extends `RequestTooLargeError`, which extends `SnapshotTooLargeError`, so an existing `instanceof SnapshotTooLargeError` still matches every 413; that relation is for compatibility and says nothing about snapshots. To single out a snapshot that is too large, test `e.code === "snapshot_too_large"`.
|
|
581
|
+
|
|
582
|
+
`e.name` (and the `meta_reason` of a `stream_close` frame) is the most specific class name, so it reads `TokenLimitExceededError`, `PromptTooLargeError` or `RequestTooLargeError` where it used to read `BackpressureError` or `SnapshotTooLargeError`. Match with `instanceof` or `e.code`, not on the name.
|
|
583
|
+
|
|
482
584
|
Priority within `404`: most-specific keyword wins (`snapshot` → `session` → `hook` → `agent` → base). The dispatch is keyword-matched on the response body lowercase; a hook with id `hook_agent_scan` still routes to `HookNotFoundError`, not `AgentNotFoundError`.
|
|
483
585
|
|
|
484
586
|
```ts
|
package/dist/cjs/client.js
CHANGED
|
@@ -772,6 +772,36 @@ class LoomcycleClient {
|
|
|
772
772
|
params.set("tenant", opts.tenant);
|
|
773
773
|
return (0, fetch_helpers_js_1.deleteRequest)(this.ctx, `/v1/_limits?${params.toString()}`, opts);
|
|
774
774
|
}
|
|
775
|
+
/** Ask a decision model typed questions (`choice` / `noul` / `score`) about
|
|
776
|
+
* `state` and get each answer with its probabilities. A decision model does
|
|
777
|
+
* not write: there is no reply to parse and the call costs a few output
|
|
778
|
+
* tokens. No run is involved; the tokens are charged to the caller and count
|
|
779
|
+
* against its token budget. Mirrors `POST /v1/_decide`.
|
|
780
|
+
*
|
|
781
|
+
* A refusal throws with the decision's own code on `e.code` and the HTTP
|
|
782
|
+
* status on `e.status`: 400 `invalid_input` / `bad_question` / `bad_options`
|
|
783
|
+
* / `too_many_questions` / `model_not_allowed`, 403
|
|
784
|
+
* `operator_key_restricted`, 413 `prompt_too_large`, 429
|
|
785
|
+
* `token_limit_exceeded`, 502 `model_not_found` / `call_failed`, 503
|
|
786
|
+
* `decision_not_configured`, 504 `timeout`. Branch on the code: apart from
|
|
787
|
+
* `PromptTooLargeError` (413) and `TokenLimitExceededError` (429), the
|
|
788
|
+
* error class follows the status alone, and a status is shared with
|
|
789
|
+
* unrelated conditions. */
|
|
790
|
+
async decide(req, opts) {
|
|
791
|
+
const body = {
|
|
792
|
+
state: req.state,
|
|
793
|
+
questions: req.questions,
|
|
794
|
+
};
|
|
795
|
+
if (req.model !== undefined)
|
|
796
|
+
body.model = req.model;
|
|
797
|
+
return (0, fetch_helpers_js_1.postJSON)(this.ctx, "/v1/_decide", body, opts);
|
|
798
|
+
}
|
|
799
|
+
/** The decision models a {@link decide} call may name, with each one's
|
|
800
|
+
* limits, and the default. Throws with `code: "decision_not_configured"`
|
|
801
|
+
* (503) on a deployment that declares none. Mirrors `GET /v1/_decide/models`. */
|
|
802
|
+
async listDecisionModels(opts) {
|
|
803
|
+
return (0, fetch_helpers_js_1.jsonFetch)(this.ctx, "/v1/_decide/models", opts);
|
|
804
|
+
}
|
|
775
805
|
/** Read the full event log for a session. Each entry has seq,
|
|
776
806
|
* run_id, ts_ns, type, event (the providers.Event payload). */
|
|
777
807
|
async getTranscript(sessionId, opts) {
|
package/dist/cjs/errors.js
CHANGED
|
@@ -19,15 +19,22 @@
|
|
|
19
19
|
* to raise typed errors via `raiseFromResponse`.
|
|
20
20
|
*/
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.SubstrateToolRefusedError = exports.ChannelCursorRegressionError = exports.SnapshotVersionError = exports.SnapshotTooLargeError = exports.SnapshotNotFoundError = exports.NotPausedError = exports.AlreadyPausingError = exports.PauseNotConfiguredError = exports.InvalidArgumentError = exports.UnavailableError = exports.AuthError = exports.PerUserQuotaExhaustedError = exports.BackpressureError = exports.AgentIDInUseError = exports.SessionBusyError = exports.SessionNotFoundError = exports.AgentNotFoundError = exports.NotFoundError = exports.LoomcycleError = void 0;
|
|
22
|
+
exports.SubstrateToolRefusedError = exports.ChannelCursorRegressionError = exports.SnapshotVersionError = exports.PromptTooLargeError = exports.RequestTooLargeError = exports.SnapshotTooLargeError = exports.SnapshotNotFoundError = exports.NotPausedError = exports.AlreadyPausingError = exports.PauseNotConfiguredError = exports.InvalidArgumentError = exports.UnavailableError = exports.AuthError = exports.PerUserQuotaExhaustedError = exports.TokenLimitExceededError = exports.BackpressureError = exports.AgentIDInUseError = exports.SessionBusyError = exports.SessionNotFoundError = exports.AgentNotFoundError = exports.NotFoundError = exports.LoomcycleError = void 0;
|
|
23
23
|
class LoomcycleError extends Error {
|
|
24
24
|
status;
|
|
25
25
|
bodyText;
|
|
26
|
+
/** The machine-readable `code` of a JSON error body (`model_not_allowed`,
|
|
27
|
+
* `token_limit_exceeded`, `per_user_quota_exhausted`, …), on whichever
|
|
28
|
+
* class the status maps to. Undefined when the body is not JSON or carries
|
|
29
|
+
* no string `code`. Read from the whole body, so it survives where
|
|
30
|
+
* `bodyText` is cut at 1 KiB. Branch on this, not on the message. */
|
|
31
|
+
code;
|
|
26
32
|
constructor(message, opts) {
|
|
27
33
|
super(message);
|
|
28
34
|
this.name = "LoomcycleError";
|
|
29
35
|
this.status = opts?.status;
|
|
30
36
|
this.bodyText = opts?.bodyText;
|
|
37
|
+
this.code = opts?.code;
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
40
|
exports.LoomcycleError = LoomcycleError;
|
|
@@ -71,6 +78,10 @@ class AgentIDInUseError extends LoomcycleError {
|
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
80
|
exports.AgentIDInUseError = AgentIDInUseError;
|
|
81
|
+
/** Every HTTP 429 that is not a per-user quota. A plain BackpressureError is
|
|
82
|
+
* the transient case (the server is busy; back off and retry). It has one
|
|
83
|
+
* subclass that is NOT transient, TokenLimitExceededError — check for that
|
|
84
|
+
* first when deciding whether to retry. */
|
|
74
85
|
class BackpressureError extends LoomcycleError {
|
|
75
86
|
constructor(message, opts) {
|
|
76
87
|
super(message, opts);
|
|
@@ -78,6 +89,51 @@ class BackpressureError extends LoomcycleError {
|
|
|
78
89
|
}
|
|
79
90
|
}
|
|
80
91
|
exports.BackpressureError = BackpressureError;
|
|
92
|
+
/**
|
|
93
|
+
* TokenLimitExceededError signals that the caller is at a hard token budget:
|
|
94
|
+
* HTTP 429 with body code `token_limit_exceeded`, from run creation and from
|
|
95
|
+
* `decide()`.
|
|
96
|
+
*
|
|
97
|
+
* Do not retry it the way you retry backpressure. The budget is a calendar
|
|
98
|
+
* month's tokens for an operator, tenant or user; the same call is refused
|
|
99
|
+
* until an operator raises the budget or the window rolls over.
|
|
100
|
+
*
|
|
101
|
+
* It extends BackpressureError only so that code written before this class
|
|
102
|
+
* existed, which caught this refusal with `instanceof BackpressureError`,
|
|
103
|
+
* keeps catching it. Test for this class BEFORE BackpressureError:
|
|
104
|
+
*
|
|
105
|
+
* if (e instanceof TokenLimitExceededError) {
|
|
106
|
+
* // not retryable now: report e.scope / e.used / e.limit
|
|
107
|
+
* } else if (e instanceof BackpressureError) {
|
|
108
|
+
* // transient: back off and retry
|
|
109
|
+
* }
|
|
110
|
+
*
|
|
111
|
+
* The fields come from the response body and are null when the server did
|
|
112
|
+
* not send them.
|
|
113
|
+
*/
|
|
114
|
+
class TokenLimitExceededError extends BackpressureError {
|
|
115
|
+
/** Whose budget was hit: "operator", "tenant" or "user". */
|
|
116
|
+
scope;
|
|
117
|
+
/** The id of that scope: the tenant id or the user subject. Null for the
|
|
118
|
+
* operator-wide budget, and where the server withholds it. */
|
|
119
|
+
scopeId;
|
|
120
|
+
/** Tokens the scope has used in the current window. */
|
|
121
|
+
used;
|
|
122
|
+
/** The hard ceiling that was reached, in tokens. */
|
|
123
|
+
limit;
|
|
124
|
+
/** The budget window, e.g. "month" (a calendar month, UTC). */
|
|
125
|
+
window;
|
|
126
|
+
constructor(message, opts) {
|
|
127
|
+
super(message, opts);
|
|
128
|
+
this.name = "TokenLimitExceededError";
|
|
129
|
+
this.scope = opts?.scope ?? null;
|
|
130
|
+
this.scopeId = opts?.scopeId ?? null;
|
|
131
|
+
this.used = opts?.used ?? null;
|
|
132
|
+
this.limit = opts?.limit ?? null;
|
|
133
|
+
this.window = opts?.window ?? null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.TokenLimitExceededError = TokenLimitExceededError;
|
|
81
137
|
/**
|
|
82
138
|
* PerUserQuotaExhaustedError signals that the caller has hit their
|
|
83
139
|
* per-user cap on in-flight (active+queued) runs. Distinct from
|
|
@@ -182,6 +238,14 @@ class SnapshotNotFoundError extends NotFoundError {
|
|
|
182
238
|
}
|
|
183
239
|
}
|
|
184
240
|
exports.SnapshotNotFoundError = SnapshotNotFoundError;
|
|
241
|
+
/** A snapshot envelope over the size cap (HTTP 413, body code
|
|
242
|
+
* `snapshot_too_large`).
|
|
243
|
+
*
|
|
244
|
+
* For compatibility it is ALSO the base class of every other 413: until
|
|
245
|
+
* RequestTooLargeError and PromptTooLargeError existed, the client raised
|
|
246
|
+
* this class for any 413, so `instanceof SnapshotTooLargeError` is true for
|
|
247
|
+
* all of them and stays true. To single out a snapshot that is too large,
|
|
248
|
+
* test `e.code === "snapshot_too_large"`. */
|
|
185
249
|
class SnapshotTooLargeError extends LoomcycleError {
|
|
186
250
|
constructor(message, opts) {
|
|
187
251
|
super(message, opts);
|
|
@@ -189,6 +253,32 @@ class SnapshotTooLargeError extends LoomcycleError {
|
|
|
189
253
|
}
|
|
190
254
|
}
|
|
191
255
|
exports.SnapshotTooLargeError = SnapshotTooLargeError;
|
|
256
|
+
/** An HTTP 413 that is not about a snapshot: a request body over the server's
|
|
257
|
+
* size limit (`request_too_large`, or a plain-text 413 with no code), a
|
|
258
|
+
* memory value or quota (`memory_value_too_large`, `memory_quota_exceeded`),
|
|
259
|
+
* a channel payload (`payload_too_large`). `e.code` says which.
|
|
260
|
+
*
|
|
261
|
+
* It extends SnapshotTooLargeError only because that is the class these
|
|
262
|
+
* responses were raised as before this one existed; the relation keeps an
|
|
263
|
+
* existing `instanceof SnapshotTooLargeError` matching and says nothing about
|
|
264
|
+
* snapshots. */
|
|
265
|
+
class RequestTooLargeError extends SnapshotTooLargeError {
|
|
266
|
+
constructor(message, opts) {
|
|
267
|
+
super(message, opts);
|
|
268
|
+
this.name = "RequestTooLargeError";
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.RequestTooLargeError = RequestTooLargeError;
|
|
272
|
+
/** A `decide()` request that does not fit the decision model's context (HTTP
|
|
273
|
+
* 413, body code `prompt_too_large`). The server never shortens it for you:
|
|
274
|
+
* shorten `state` or ask fewer questions, then send it again. */
|
|
275
|
+
class PromptTooLargeError extends RequestTooLargeError {
|
|
276
|
+
constructor(message, opts) {
|
|
277
|
+
super(message, opts);
|
|
278
|
+
this.name = "PromptTooLargeError";
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
exports.PromptTooLargeError = PromptTooLargeError;
|
|
192
282
|
class SnapshotVersionError extends LoomcycleError {
|
|
193
283
|
constructor(message, opts) {
|
|
194
284
|
super(message, opts);
|
|
@@ -178,15 +178,23 @@ async function deleteJSON(ctx, path, opts) {
|
|
|
178
178
|
* 409 + "session" → SessionBusyError
|
|
179
179
|
* 409 + "agent_id" → AgentIDInUseError
|
|
180
180
|
* 409 + (other) → LoomcycleError (base)
|
|
181
|
-
* 413
|
|
181
|
+
* 413 + code "snapshot_too_large" → SnapshotTooLargeError ──┐ all are
|
|
182
|
+
* 413 + code "prompt_too_large" → PromptTooLargeError │ instanceof
|
|
183
|
+
* 413 (other) → RequestTooLargeError ───┘ SnapshotTooLargeError
|
|
182
184
|
* 422 → SnapshotVersionError (snapshot-version-too-new/unknown)
|
|
183
|
-
* 429
|
|
185
|
+
* 429 + code "per_user_quota_exhausted" → PerUserQuotaExhaustedError
|
|
186
|
+
* 429 + code "token_limit_exceeded" → TokenLimitExceededError
|
|
187
|
+
* (subclass of BackpressureError)
|
|
188
|
+
* 429 (other) → BackpressureError
|
|
184
189
|
* 503 + "pause manager not configured" → PauseNotConfiguredError
|
|
185
190
|
* (subclass of UnavailableError)
|
|
186
191
|
* 503 + (other) → UnavailableError
|
|
187
192
|
* 500-599 (other) → LoomcycleError (base)
|
|
188
193
|
* default → LoomcycleError (base)
|
|
189
194
|
*
|
|
195
|
+
* Whatever class the status maps to, a JSON body's string `code` is on the
|
|
196
|
+
* error as `code` (see bodyCode).
|
|
197
|
+
*
|
|
190
198
|
* Priority within a status group is most-specific-first; an unknown
|
|
191
199
|
* 409 falls through to base LoomcycleError so callers see a
|
|
192
200
|
* meaningful message + status. For 404, the catch-all is NotFoundError
|
|
@@ -212,7 +220,7 @@ async function raiseFromResponse(resp) {
|
|
|
212
220
|
// ("401 Unauthorized" not "401 " with a trailing space).
|
|
213
221
|
const statusPhrase = resp.statusText || stockStatusPhrase(status);
|
|
214
222
|
const msg = bodyText.trim() ? bodyText.slice(0, 1024) : `${status} ${statusPhrase}`;
|
|
215
|
-
const opts = { status, bodyText: bodyText.slice(0, 1024) };
|
|
223
|
+
const opts = { status, bodyText: bodyText.slice(0, 1024), code: bodyCode(bodyText) };
|
|
216
224
|
switch (status) {
|
|
217
225
|
case 400:
|
|
218
226
|
throw new errors_js_1.InvalidArgumentError(msg, opts);
|
|
@@ -248,7 +256,14 @@ async function raiseFromResponse(resp) {
|
|
|
248
256
|
throw new errors_js_1.AgentIDInUseError(msg, opts);
|
|
249
257
|
throw new errors_js_1.LoomcycleError(msg, opts);
|
|
250
258
|
case 413:
|
|
251
|
-
|
|
259
|
+
// Every 413 used to be a SnapshotTooLargeError, so the two classes
|
|
260
|
+
// below extend it: an existing `instanceof SnapshotTooLargeError`
|
|
261
|
+
// still matches all three.
|
|
262
|
+
if (opts.code === "snapshot_too_large")
|
|
263
|
+
throw new errors_js_1.SnapshotTooLargeError(msg, opts);
|
|
264
|
+
if (opts.code === "prompt_too_large")
|
|
265
|
+
throw new errors_js_1.PromptTooLargeError(msg, opts);
|
|
266
|
+
throw new errors_js_1.RequestTooLargeError(msg, opts);
|
|
252
267
|
case 422: {
|
|
253
268
|
// 422 is shared between snapshot version errors (existing)
|
|
254
269
|
// and v0.8.22 substrate tool refusals. Discriminate by body:
|
|
@@ -257,7 +272,7 @@ async function raiseFromResponse(resp) {
|
|
|
257
272
|
try {
|
|
258
273
|
const parsed = JSON.parse(bodyText);
|
|
259
274
|
if (parsed.code === "tool_refused") {
|
|
260
|
-
throw new errors_js_1.SubstrateToolRefusedError(parsed.error ?? msg, { status, bodyText, tool: parsed.tool });
|
|
275
|
+
throw new errors_js_1.SubstrateToolRefusedError(parsed.error ?? msg, { status, bodyText, code: parsed.code, tool: parsed.tool });
|
|
261
276
|
}
|
|
262
277
|
}
|
|
263
278
|
catch (e) {
|
|
@@ -269,6 +284,20 @@ async function raiseFromResponse(resp) {
|
|
|
269
284
|
throw new errors_js_1.SnapshotVersionError(msg, opts);
|
|
270
285
|
}
|
|
271
286
|
case 429: {
|
|
287
|
+
// A hard token budget is not transient the way backpressure is, so
|
|
288
|
+
// it gets its own class (a BackpressureError subclass, for callers
|
|
289
|
+
// that already catch that).
|
|
290
|
+
if (opts.code === "token_limit_exceeded") {
|
|
291
|
+
const body = bodyObject(bodyText) ?? {};
|
|
292
|
+
throw new errors_js_1.TokenLimitExceededError(msg, {
|
|
293
|
+
...opts,
|
|
294
|
+
scope: stringField(body.scope),
|
|
295
|
+
scopeId: stringField(body.scope_id),
|
|
296
|
+
used: numberField(body.used),
|
|
297
|
+
limit: numberField(body.limit),
|
|
298
|
+
window: stringField(body.window),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
272
301
|
// v0.10.1: distinguish per-user quota exhaustion from
|
|
273
302
|
// operator-wide backpressure. The shapes share the 429 status
|
|
274
303
|
// but the JSON body's `code` field discriminates. Consumers
|
|
@@ -284,6 +313,7 @@ async function raiseFromResponse(resp) {
|
|
|
284
313
|
throw new errors_js_1.PerUserQuotaExhaustedError(parsed.error ?? msg, {
|
|
285
314
|
status,
|
|
286
315
|
bodyText,
|
|
316
|
+
code: parsed.code,
|
|
287
317
|
userId: parsed.user_id,
|
|
288
318
|
cap: parsed.cap,
|
|
289
319
|
retryAfterMs: Number.isFinite(retryAfterMs) ? retryAfterMs : undefined,
|
|
@@ -305,6 +335,36 @@ async function raiseFromResponse(resp) {
|
|
|
305
335
|
throw new errors_js_1.LoomcycleError(msg, opts);
|
|
306
336
|
}
|
|
307
337
|
}
|
|
338
|
+
/** bodyCode returns the string `code` of a JSON error body, or undefined when
|
|
339
|
+
* the body is not a JSON object or has none. Statuses are shared between
|
|
340
|
+
* unrelated conditions (a 400 is any bad request; a 429 is backpressure, a
|
|
341
|
+
* user quota or a token budget), and the code is what tells them apart. */
|
|
342
|
+
function bodyCode(bodyText) {
|
|
343
|
+
return stringField(bodyObject(bodyText)?.code);
|
|
344
|
+
}
|
|
345
|
+
/** bodyObject returns a JSON error body as an object, or undefined when the
|
|
346
|
+
* body is not JSON or is a JSON array or scalar. */
|
|
347
|
+
function bodyObject(bodyText) {
|
|
348
|
+
try {
|
|
349
|
+
const parsed = JSON.parse(bodyText);
|
|
350
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
351
|
+
return parsed;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// not JSON: a plain-text error body has no fields
|
|
356
|
+
}
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
/** stringField / numberField read one body field, dropping a value of the
|
|
360
|
+
* wrong type: an error's typed fields must not carry whatever a proxy or an
|
|
361
|
+
* older server put under that name. An empty string counts as absent. */
|
|
362
|
+
function stringField(v) {
|
|
363
|
+
return typeof v === "string" && v !== "" ? v : undefined;
|
|
364
|
+
}
|
|
365
|
+
function numberField(v) {
|
|
366
|
+
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
367
|
+
}
|
|
308
368
|
/** stockStatusPhrase returns a stock reason phrase for the common
|
|
309
369
|
* HTTP statuses raiseFromResponse handles. Used as a fallback when
|
|
310
370
|
* Response.statusText is empty (HTTP/2 strips reason phrases). */
|
package/dist/cjs/index.js
CHANGED
|
@@ -116,7 +116,9 @@
|
|
|
116
116
|
* AuthError, UnavailableError, InvalidArgumentError,
|
|
117
117
|
* PauseNotConfiguredError (subclass of UnavailableError),
|
|
118
118
|
* AlreadyPausingError, NotPausedError, SnapshotNotFoundError,
|
|
119
|
-
* SnapshotTooLargeError,
|
|
119
|
+
* SnapshotTooLargeError, RequestTooLargeError + PromptTooLargeError
|
|
120
|
+
* (both subclasses of SnapshotTooLargeError), TokenLimitExceededError
|
|
121
|
+
* (subclass of BackpressureError), SnapshotVersionError,
|
|
120
122
|
* SubstrateToolRefusedError (v0.8.22)
|
|
121
123
|
*
|
|
122
124
|
* Transport: HTTP+SSE. Auth: Bearer token via the Authorization
|
|
@@ -127,7 +129,7 @@
|
|
|
127
129
|
* See `adapters/ts/README.md` for usage examples.
|
|
128
130
|
*/
|
|
129
131
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
130
|
-
exports.UnavailableError = exports.SubstrateToolRefusedError = exports.SnapshotVersionError = exports.SnapshotTooLargeError = exports.SnapshotNotFoundError = exports.SessionNotFoundError = exports.SessionBusyError = exports.PerUserQuotaExhaustedError = exports.PauseNotConfiguredError = exports.NotPausedError = exports.LoomcycleError = exports.ChannelCursorRegressionError = exports.InvalidArgumentError = exports.NotFoundError = exports.BackpressureError = exports.AuthError = exports.AlreadyPausingError = exports.AgentNotFoundError = exports.AgentIDInUseError = exports.CLIENT_TOOL_SUBPROTOCOL = exports.clientToolsURL = exports.ClientToolHost = exports.InteractiveSession = exports.LoomcycleClient = void 0;
|
|
132
|
+
exports.UnavailableError = exports.TokenLimitExceededError = exports.SubstrateToolRefusedError = exports.SnapshotVersionError = exports.SnapshotTooLargeError = exports.SnapshotNotFoundError = exports.SessionNotFoundError = exports.SessionBusyError = exports.RequestTooLargeError = exports.PromptTooLargeError = exports.PerUserQuotaExhaustedError = exports.PauseNotConfiguredError = exports.NotPausedError = exports.LoomcycleError = exports.ChannelCursorRegressionError = exports.InvalidArgumentError = exports.NotFoundError = exports.BackpressureError = exports.AuthError = exports.AlreadyPausingError = exports.AgentNotFoundError = exports.AgentIDInUseError = exports.CLIENT_TOOL_SUBPROTOCOL = exports.clientToolsURL = exports.ClientToolHost = exports.InteractiveSession = exports.LoomcycleClient = void 0;
|
|
131
133
|
var client_js_1 = require("./client.js");
|
|
132
134
|
Object.defineProperty(exports, "LoomcycleClient", { enumerable: true, get: function () { return client_js_1.LoomcycleClient; } });
|
|
133
135
|
var interactive_js_1 = require("./interactive.js");
|
|
@@ -149,10 +151,13 @@ Object.defineProperty(exports, "LoomcycleError", { enumerable: true, get: functi
|
|
|
149
151
|
Object.defineProperty(exports, "NotPausedError", { enumerable: true, get: function () { return errors_js_1.NotPausedError; } });
|
|
150
152
|
Object.defineProperty(exports, "PauseNotConfiguredError", { enumerable: true, get: function () { return errors_js_1.PauseNotConfiguredError; } });
|
|
151
153
|
Object.defineProperty(exports, "PerUserQuotaExhaustedError", { enumerable: true, get: function () { return errors_js_1.PerUserQuotaExhaustedError; } });
|
|
154
|
+
Object.defineProperty(exports, "PromptTooLargeError", { enumerable: true, get: function () { return errors_js_1.PromptTooLargeError; } });
|
|
155
|
+
Object.defineProperty(exports, "RequestTooLargeError", { enumerable: true, get: function () { return errors_js_1.RequestTooLargeError; } });
|
|
152
156
|
Object.defineProperty(exports, "SessionBusyError", { enumerable: true, get: function () { return errors_js_1.SessionBusyError; } });
|
|
153
157
|
Object.defineProperty(exports, "SessionNotFoundError", { enumerable: true, get: function () { return errors_js_1.SessionNotFoundError; } });
|
|
154
158
|
Object.defineProperty(exports, "SnapshotNotFoundError", { enumerable: true, get: function () { return errors_js_1.SnapshotNotFoundError; } });
|
|
155
159
|
Object.defineProperty(exports, "SnapshotTooLargeError", { enumerable: true, get: function () { return errors_js_1.SnapshotTooLargeError; } });
|
|
156
160
|
Object.defineProperty(exports, "SnapshotVersionError", { enumerable: true, get: function () { return errors_js_1.SnapshotVersionError; } });
|
|
157
161
|
Object.defineProperty(exports, "SubstrateToolRefusedError", { enumerable: true, get: function () { return errors_js_1.SubstrateToolRefusedError; } });
|
|
162
|
+
Object.defineProperty(exports, "TokenLimitExceededError", { enumerable: true, get: function () { return errors_js_1.TokenLimitExceededError; } });
|
|
158
163
|
Object.defineProperty(exports, "UnavailableError", { enumerable: true, get: function () { return errors_js_1.UnavailableError; } });
|
package/dist/client.d.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { InteractiveSession } from "./interactive.js";
|
|
27
27
|
import { ClientToolHost, type ConnectClientToolsOptions } from "./client-tools.js";
|
|
28
|
-
import type { CredentialListResponse, CredentialMeta, CredentialScope, DirectoryInspection, DirectoryTenant, DirectoryUser, ErasureReport, ErasureResult, Agent, AgentEvent, AgentStatus, CancelAgentResult, ClientOptions, ContinueOptions, RunOverrideOptions, EffectiveConfigResponse, RetuneRunResponse, RunConfigResponse, RunPromptResponse, CreateSnapshotOptions, EnsureCodeAgentOptions, EnsureCodeAgentResult, EnsureMcpServerOptions, EnsureMcpServerResult, HealthResponse, InterruptListResponse, InterruptStatus, WalkRunsPage, AckChannelOptions, AwaitChannelsOptions, BroadcastChannelsOptions, ChannelAckResult, ChannelAwaitResult, ChannelBroadcastResult, ChannelDescriptor, ChannelPeekResult, ChannelPublishResult, ChannelPurgeResult, ChannelReleaseResult, ChannelSubscribeResult, CreateChannelOptions, ListChannelsResponse, PeekChannelOptions, PublishChannelOptions, ReleaseChannelOptions, SetMemoryEntryOptions, SetMemoryEntryResponse, SubscribeChannelOptions, UpdateChannelOptions, LibraryAgentDefinition, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, ListUsersResponse, UserRecord, CreateUserBody, UpdateUserBody, MintedUserToken, ListUserTokensResponse, RunnableAgentsResponse, LLMChatOptions, LLMChatResponse, LLMChatStreamItem, LLMEmbeddingsOptions, LLMEmbeddingsResponse, MCPServerDefVerifyResult, MemoryEmbedStatsResponse, MemoryEntriesResponse, MemoryEntryResponse, MemoryBackfillResponse, MemoryPurgeResponse, MemoryReembedResponse, MemoryScopeIDsResponse, MemoryScopesResponse, MemorySearchInput, MemorySearchResponse, PauseResult, PersistentVolumesResponse, EphemeralVolumesResponse, ResolveInterruptOptions, ResumeResult, ResolverMatrix, CompactRunResult, ReplaySessionResult, CancelTurnResult, ReviewRunResult, RunBatchOptions, RunBatchResult, RunOptions, ConfiguredRun, ConfiguredRunPatch, RunStateStreamItem, RuntimeStateResponse, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotRestoreResponse, StreamUserRunStatesOptions, SubstrateToolInput, SubstrateToolResponse, CreatedTeam, ListTeamsResponse, PromotedTeam, RetiredTeam, TeamBreakpoints, TeamDefDetail, TeamRunDetached, TeamRunTarget, TeamVerification, TeamDraft, TeamDraftVerification, TeamVersionList, TeamDiagram, TeamRunResult, PathToolInput, PathToolResponse, DocumentToolInput, DocumentToolResponse, HistoryToolInput, HistoryToolResponse, TranscriptResponse, WhoamiResponse, UsageDimension, UsageReportResponse, TokenLimit, TokenLimitsResponse, ConfigResponse, SetTokenLimitRequest } from "./types.js";
|
|
28
|
+
import type { CredentialListResponse, CredentialMeta, DecideRequest, DecideResponse, DecisionModelsResponse, CredentialScope, DirectoryInspection, DirectoryTenant, DirectoryUser, ErasureReport, ErasureResult, Agent, AgentEvent, AgentStatus, CancelAgentResult, ClientOptions, ContinueOptions, RunOverrideOptions, EffectiveConfigResponse, RetuneRunResponse, RunConfigResponse, RunPromptResponse, CreateSnapshotOptions, EnsureCodeAgentOptions, EnsureCodeAgentResult, EnsureMcpServerOptions, EnsureMcpServerResult, HealthResponse, InterruptListResponse, InterruptStatus, WalkRunsPage, AckChannelOptions, AwaitChannelsOptions, BroadcastChannelsOptions, ChannelAckResult, ChannelAwaitResult, ChannelBroadcastResult, ChannelDescriptor, ChannelPeekResult, ChannelPublishResult, ChannelPurgeResult, ChannelReleaseResult, ChannelSubscribeResult, CreateChannelOptions, ListChannelsResponse, PeekChannelOptions, PublishChannelOptions, ReleaseChannelOptions, SetMemoryEntryOptions, SetMemoryEntryResponse, SubscribeChannelOptions, UpdateChannelOptions, LibraryAgentDefinition, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, ListUsersResponse, UserRecord, CreateUserBody, UpdateUserBody, MintedUserToken, ListUserTokensResponse, RunnableAgentsResponse, LLMChatOptions, LLMChatResponse, LLMChatStreamItem, LLMEmbeddingsOptions, LLMEmbeddingsResponse, MCPServerDefVerifyResult, MemoryEmbedStatsResponse, MemoryEntriesResponse, MemoryEntryResponse, MemoryBackfillResponse, MemoryPurgeResponse, MemoryReembedResponse, MemoryScopeIDsResponse, MemoryScopesResponse, MemorySearchInput, MemorySearchResponse, PauseResult, PersistentVolumesResponse, EphemeralVolumesResponse, ResolveInterruptOptions, ResumeResult, ResolverMatrix, CompactRunResult, ReplaySessionResult, CancelTurnResult, ReviewRunResult, RunBatchOptions, RunBatchResult, RunOptions, ConfiguredRun, ConfiguredRunPatch, RunStateStreamItem, RuntimeStateResponse, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotRestoreResponse, StreamUserRunStatesOptions, SubstrateToolInput, SubstrateToolResponse, CreatedTeam, ListTeamsResponse, PromotedTeam, RetiredTeam, TeamBreakpoints, TeamDefDetail, TeamRunDetached, TeamRunTarget, TeamVerification, TeamDraft, TeamDraftVerification, TeamVersionList, TeamDiagram, TeamRunResult, PathToolInput, PathToolResponse, DocumentToolInput, DocumentToolResponse, HistoryToolInput, HistoryToolResponse, TranscriptResponse, WhoamiResponse, UsageDimension, UsageReportResponse, TokenLimit, TokenLimitsResponse, ConfigResponse, SetTokenLimitRequest } from "./types.js";
|
|
29
29
|
export declare class LoomcycleClient {
|
|
30
30
|
private ctx;
|
|
31
31
|
constructor(opts?: ClientOptions);
|
|
@@ -464,6 +464,30 @@ export declare class LoomcycleClient {
|
|
|
464
464
|
tenant?: string;
|
|
465
465
|
signal?: AbortSignal;
|
|
466
466
|
}): Promise<void>;
|
|
467
|
+
/** Ask a decision model typed questions (`choice` / `noul` / `score`) about
|
|
468
|
+
* `state` and get each answer with its probabilities. A decision model does
|
|
469
|
+
* not write: there is no reply to parse and the call costs a few output
|
|
470
|
+
* tokens. No run is involved; the tokens are charged to the caller and count
|
|
471
|
+
* against its token budget. Mirrors `POST /v1/_decide`.
|
|
472
|
+
*
|
|
473
|
+
* A refusal throws with the decision's own code on `e.code` and the HTTP
|
|
474
|
+
* status on `e.status`: 400 `invalid_input` / `bad_question` / `bad_options`
|
|
475
|
+
* / `too_many_questions` / `model_not_allowed`, 403
|
|
476
|
+
* `operator_key_restricted`, 413 `prompt_too_large`, 429
|
|
477
|
+
* `token_limit_exceeded`, 502 `model_not_found` / `call_failed`, 503
|
|
478
|
+
* `decision_not_configured`, 504 `timeout`. Branch on the code: apart from
|
|
479
|
+
* `PromptTooLargeError` (413) and `TokenLimitExceededError` (429), the
|
|
480
|
+
* error class follows the status alone, and a status is shared with
|
|
481
|
+
* unrelated conditions. */
|
|
482
|
+
decide(req: DecideRequest, opts?: {
|
|
483
|
+
signal?: AbortSignal;
|
|
484
|
+
}): Promise<DecideResponse>;
|
|
485
|
+
/** The decision models a {@link decide} call may name, with each one's
|
|
486
|
+
* limits, and the default. Throws with `code: "decision_not_configured"`
|
|
487
|
+
* (503) on a deployment that declares none. Mirrors `GET /v1/_decide/models`. */
|
|
488
|
+
listDecisionModels(opts?: {
|
|
489
|
+
signal?: AbortSignal;
|
|
490
|
+
}): Promise<DecisionModelsResponse>;
|
|
467
491
|
/** Read the full event log for a session. Each entry has seq,
|
|
468
492
|
* run_id, ts_ns, type, event (the providers.Event payload). */
|
|
469
493
|
getTranscript(sessionId: string, opts?: {
|
package/dist/client.js
CHANGED
|
@@ -769,6 +769,36 @@ export class LoomcycleClient {
|
|
|
769
769
|
params.set("tenant", opts.tenant);
|
|
770
770
|
return deleteRequest(this.ctx, `/v1/_limits?${params.toString()}`, opts);
|
|
771
771
|
}
|
|
772
|
+
/** Ask a decision model typed questions (`choice` / `noul` / `score`) about
|
|
773
|
+
* `state` and get each answer with its probabilities. A decision model does
|
|
774
|
+
* not write: there is no reply to parse and the call costs a few output
|
|
775
|
+
* tokens. No run is involved; the tokens are charged to the caller and count
|
|
776
|
+
* against its token budget. Mirrors `POST /v1/_decide`.
|
|
777
|
+
*
|
|
778
|
+
* A refusal throws with the decision's own code on `e.code` and the HTTP
|
|
779
|
+
* status on `e.status`: 400 `invalid_input` / `bad_question` / `bad_options`
|
|
780
|
+
* / `too_many_questions` / `model_not_allowed`, 403
|
|
781
|
+
* `operator_key_restricted`, 413 `prompt_too_large`, 429
|
|
782
|
+
* `token_limit_exceeded`, 502 `model_not_found` / `call_failed`, 503
|
|
783
|
+
* `decision_not_configured`, 504 `timeout`. Branch on the code: apart from
|
|
784
|
+
* `PromptTooLargeError` (413) and `TokenLimitExceededError` (429), the
|
|
785
|
+
* error class follows the status alone, and a status is shared with
|
|
786
|
+
* unrelated conditions. */
|
|
787
|
+
async decide(req, opts) {
|
|
788
|
+
const body = {
|
|
789
|
+
state: req.state,
|
|
790
|
+
questions: req.questions,
|
|
791
|
+
};
|
|
792
|
+
if (req.model !== undefined)
|
|
793
|
+
body.model = req.model;
|
|
794
|
+
return postJSON(this.ctx, "/v1/_decide", body, opts);
|
|
795
|
+
}
|
|
796
|
+
/** The decision models a {@link decide} call may name, with each one's
|
|
797
|
+
* limits, and the default. Throws with `code: "decision_not_configured"`
|
|
798
|
+
* (503) on a deployment that declares none. Mirrors `GET /v1/_decide/models`. */
|
|
799
|
+
async listDecisionModels(opts) {
|
|
800
|
+
return jsonFetch(this.ctx, "/v1/_decide/models", opts);
|
|
801
|
+
}
|
|
772
802
|
/** Read the full event log for a session. Each entry has seq,
|
|
773
803
|
* run_id, ts_ns, type, event (the providers.Event payload). */
|
|
774
804
|
async getTranscript(sessionId, opts) {
|