@ziprack/sdk 0.3.0 → 0.4.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/dist/ai/index.d.ts +36 -4
- package/dist/core/mock/MockRack.d.ts +7 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/docs/ai.md +84 -0
- package/dist/docs/installation.md +5 -2
- package/dist/docs/manifest.md +2 -1
- package/dist/docs/mcp.md +3 -0
- package/dist/docs/security.md +8 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +30 -19
- package/dist/index.js.map +1 -1
- package/dist/{store-DQbG_t2D.js → store-BHnO2KB3.js} +15 -12
- package/dist/store-BHnO2KB3.js.map +1 -0
- package/dist/testing.js +1 -1
- package/dist/ziprack-sdk.iife.js +1 -1
- package/dist/ziprack-sdk.iife.js.map +1 -1
- package/package.json +1 -1
- package/dist/store-DQbG_t2D.js.map +0 -1
package/dist/ai/index.d.ts
CHANGED
|
@@ -4,15 +4,34 @@ export interface Usage {
|
|
|
4
4
|
input_tokens: number;
|
|
5
5
|
output_tokens: number;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Where a grounded answer's evidence came from: a passage in one of the
|
|
9
|
+
* manifest's knowledge slots. Present only on prompts bound to knowledge, and
|
|
10
|
+
* empty when nothing relevant was found or retrieval was unavailable.
|
|
11
|
+
*/
|
|
12
|
+
export interface Citation {
|
|
13
|
+
slot: string;
|
|
14
|
+
title: string | null;
|
|
15
|
+
ref: string | null;
|
|
16
|
+
sources: Array<{
|
|
17
|
+
title: string;
|
|
18
|
+
ref?: string;
|
|
19
|
+
}>;
|
|
20
|
+
url: string | null;
|
|
21
|
+
t_start: number | null;
|
|
22
|
+
t_end: number | null;
|
|
23
|
+
}
|
|
7
24
|
export interface ChatReply {
|
|
8
25
|
message_id: string;
|
|
9
26
|
text: string;
|
|
10
27
|
usage: Usage;
|
|
28
|
+
citations?: Citation[];
|
|
11
29
|
}
|
|
12
30
|
export declare class Chat {
|
|
13
31
|
private readonly link;
|
|
14
32
|
readonly promptId: string;
|
|
15
|
-
|
|
33
|
+
private readonly vars;
|
|
34
|
+
constructor(link: Link, promptId: string, vars?: JsonObject);
|
|
16
35
|
/**
|
|
17
36
|
* Send a participant message. onDelta receives text as it streams; the
|
|
18
37
|
* resolved result carries the full text.
|
|
@@ -30,12 +49,25 @@ export declare class AiApi {
|
|
|
30
49
|
prompts(): Array<{
|
|
31
50
|
id: string;
|
|
32
51
|
mode: 'analysis' | 'chat';
|
|
52
|
+
knowledge?: string[];
|
|
53
|
+
}>;
|
|
54
|
+
/** The knowledge slots the manifest declares. Their contents stay on the Rack. */
|
|
55
|
+
knowledge(): Array<{
|
|
56
|
+
id: string;
|
|
57
|
+
label: string | null;
|
|
33
58
|
}>;
|
|
34
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Analysis prompt: structured JSON conforming to the declared output_schema.
|
|
61
|
+
* A prompt bound to knowledge also returns the citations it was grounded in.
|
|
62
|
+
*/
|
|
35
63
|
ask<T extends object = JsonObject>(promptId: string, vars?: JsonObject): Promise<AckResult<{
|
|
36
64
|
output: T;
|
|
37
65
|
usage: Usage;
|
|
66
|
+
citations?: Citation[];
|
|
38
67
|
}>>;
|
|
39
|
-
/**
|
|
40
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Chat prompt: conversation history is held by the Rack. `vars` fill the
|
|
70
|
+
* template's placeholders on every turn, e.g. which persona to speak as.
|
|
71
|
+
*/
|
|
72
|
+
chat(promptId: string, vars?: JsonObject): Chat;
|
|
41
73
|
}
|
|
@@ -39,6 +39,13 @@ export interface MockManifest {
|
|
|
39
39
|
id: string;
|
|
40
40
|
mode: 'analysis' | 'chat';
|
|
41
41
|
output_schema?: JsonObject;
|
|
42
|
+
knowledge?: Array<{
|
|
43
|
+
slot: string;
|
|
44
|
+
}>;
|
|
45
|
+
}>;
|
|
46
|
+
knowledge?: Array<{
|
|
47
|
+
id: string;
|
|
48
|
+
label?: string;
|
|
42
49
|
}>;
|
|
43
50
|
broadcast?: {
|
|
44
51
|
facilitator_events?: string[];
|
package/dist/core/types.d.ts
CHANGED
|
@@ -114,10 +114,16 @@ export interface LaunchData {
|
|
|
114
114
|
max_bytes: number;
|
|
115
115
|
}>;
|
|
116
116
|
};
|
|
117
|
+
/** `knowledge` lists the slots a prompt is grounded in (Racks with knowledge support). */
|
|
117
118
|
ai?: {
|
|
118
119
|
prompts: Array<{
|
|
119
120
|
id: string;
|
|
120
121
|
mode: 'analysis' | 'chat';
|
|
122
|
+
knowledge?: string[];
|
|
123
|
+
}>;
|
|
124
|
+
knowledge?: Array<{
|
|
125
|
+
id: string;
|
|
126
|
+
label: string | null;
|
|
121
127
|
}>;
|
|
122
128
|
};
|
|
123
129
|
broadcast?: LaunchBroadcast;
|
package/dist/docs/ai.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
- [Chat](#chat)
|
|
7
7
|
- [Budgets](#budgets)
|
|
8
8
|
- [Untrusted Variables](#untrusted-variables)
|
|
9
|
+
- [Knowledge](#knowledge)
|
|
9
10
|
- [Not This Capability](#not-this-capability)
|
|
10
11
|
|
|
11
12
|
<a name="introduction"></a>
|
|
@@ -150,6 +151,89 @@ pass through, and design the template so that it would not matter:
|
|
|
150
151
|
schema-constrained answer is a much smaller blast radius.
|
|
151
152
|
- Show model output as model output. Do not render it as if it were Rack-authored guidance.
|
|
152
153
|
|
|
154
|
+
<a name="knowledge"></a>
|
|
155
|
+
## Knowledge
|
|
156
|
+
|
|
157
|
+
A prompt can be **grounded** in documents the Rack holds: before the model is called, the Rack
|
|
158
|
+
retrieves the most relevant passages and hands them over as quoted reference material. The applet
|
|
159
|
+
still sends only variables — it never sees the documents, the search or the filter.
|
|
160
|
+
|
|
161
|
+
Declare **slots** (named collections the Rack fills) and bind prompts to them:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
"knowledge": [
|
|
165
|
+
{
|
|
166
|
+
"id": "factbook",
|
|
167
|
+
"label": "Fact Book",
|
|
168
|
+
"fields": {
|
|
169
|
+
"stage": { "enum": ["s3_misdiagnosis", "s5_initial_treatment"] },
|
|
170
|
+
"stakeholders": { "type": "array", "items": { "enum": ["patient", "caregiver", "hepatologist"] } },
|
|
171
|
+
"persona_eligible": { "type": "boolean" }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
"prompts": [
|
|
176
|
+
{
|
|
177
|
+
"id": "persona_chat",
|
|
178
|
+
"mode": "chat",
|
|
179
|
+
"template": "You are a composite {{persona}} from the fact book...",
|
|
180
|
+
"vars": { "type": "object", "properties": { "persona": { "enum": ["patient_neuro", "caregiver_parent"] } } },
|
|
181
|
+
"knowledge": [{
|
|
182
|
+
"slot": "factbook",
|
|
183
|
+
"top_k": 6,
|
|
184
|
+
"filter": { "persona_eligible": true },
|
|
185
|
+
"filter_by": { "persona": {
|
|
186
|
+
"patient_neuro": { "stakeholders": { "contains": "patient" } },
|
|
187
|
+
"caregiver_parent": { "stakeholders": { "contains": "caregiver" } }
|
|
188
|
+
} }
|
|
189
|
+
}]
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
| Field | Rules |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `knowledge[].id` | Required. `^[a-z][a-z0-9_]*$`, unique. |
|
|
197
|
+
| `knowledge[].source` | `rack` (the default): documents are loaded after install, from the Rack or over MCP. |
|
|
198
|
+
| `knowledge[].fields` | The metadata a document may tag and a filter may use: `type` (`string`, `number`, `boolean`, `array`) and an optional `enum` (`items.enum` for arrays). `sensitivity`, `content`, `embedding`, `text` and `id` are reserved. |
|
|
199
|
+
| `prompts[].knowledge[].slot` | A declared slot. Up to three per prompt. |
|
|
200
|
+
| `top_k` | Passages to retrieve, up to the Rack's ceiling. |
|
|
201
|
+
| `query` | Analysis prompts: what to search for, with `{{var}}` placeholders. Default: the string variables. Chat prompts search with the participant's message. |
|
|
202
|
+
| `filter` | Fixed conditions: a value (equals), `{ "in": [...] }`, or `{ "contains": value }` for an array field. |
|
|
203
|
+
| `filter_by` | Maps each value of an **enum** variable to more conditions. Every enum value must be mapped. This is how one prompt serves several personas without the applet writing filters. |
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const coach = Rack.ai.chat('persona_chat', { persona: 'caregiver_parent' });
|
|
207
|
+
const reply = await coach.send('What was the hardest moment?');
|
|
208
|
+
|
|
209
|
+
reply.data?.citations; // [{ slot, title, ref: 'slide 105', sources: [...], url, t_start, t_end }]
|
|
210
|
+
Rack.ai.knowledge(); // [{ id: 'factbook', label: 'Fact Book' }]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Citations** come back on every grounded call — empty when nothing relevant was found or the
|
|
214
|
+
Rack's index was unavailable, in which case the model answered ungrounded. Show them; they are how a
|
|
215
|
+
participant can check an answer.
|
|
216
|
+
|
|
217
|
+
**Loading documents** happens on the Rack, not in the package: an author uploads to a slot from the
|
|
218
|
+
applet's AI tab or with the `create-knowledge-upload` MCP tool. The Rack accepts prepared JSONL (one
|
|
219
|
+
chunk per line: `id`, `text`, and optionally `context`, `title`, `ref`, `sources`, `url`, `t_start`,
|
|
220
|
+
`t_end`, `sensitivity`, `metadata` using the slot's fields), Markdown, plain text, HTML, and WebVTT or
|
|
221
|
+
SRT transcripts. Heavy sources — slide decks, PDFs, video — are prepared locally and arrive as JSONL
|
|
222
|
+
or a transcript; the Rack never parses them. The `get-knowledge-prep-skill` MCP tool installs an
|
|
223
|
+
agent skill that does that preparation, validation and loading. Chunks above the Rack's sensitivity ceiling are refused
|
|
224
|
+
and never leave the Rack, and each applet can be held to a lower ceiling for retrieval.
|
|
225
|
+
|
|
226
|
+
A Rack without a knowledge-capable ai package refuses a manifest with a `knowledge` section at
|
|
227
|
+
install, naming the unowned section, rather than answering ungrounded.
|
|
228
|
+
|
|
229
|
+
### Untrusted Passages
|
|
230
|
+
|
|
231
|
+
Retrieved text is document content, not Rack instruction. It travels in a quoted `<reference>` block
|
|
232
|
+
of the user message — never the system prompt — and the model is told to use it as evidence and never
|
|
233
|
+
to follow instructions inside it. Delimiting reduces indirect prompt injection; it does not eliminate
|
|
234
|
+
it. Load only documents you would be comfortable having quoted back to a participant, and use
|
|
235
|
+
`sensitivity` to keep anything that must not be quoted out of the index altogether.
|
|
236
|
+
|
|
153
237
|
<a name="not-this-capability"></a>
|
|
154
238
|
## Not This Capability
|
|
155
239
|
|
|
@@ -84,6 +84,7 @@ ones you will actually touch.
|
|
|
84
84
|
| `allowed_extensions` | static list | Anything else rejects the whole upload. |
|
|
85
85
|
| `asset_token_ttl_hours` | `6` | Lifetime of the signed package-asset token. |
|
|
86
86
|
| `session_ttl_hours` | `6` | Lifetime of a launch session. |
|
|
87
|
+
| `session_cookie` | `ziprack_launch` | HttpOnly cookie that binds launch sessions to the browser that launched them. |
|
|
87
88
|
| `csp.img_hosts`, `csp.media_hosts` | `[]` | Per-deployment allowlist. A bare scheme is rejected. |
|
|
88
89
|
| `player.frame_allow` | `[]` | Features added to the applet iframe's `allow` list, e.g. `microphone`. Bare names from a fixed set only. |
|
|
89
90
|
| `media.providers` | `['vimeo','youtube']` | Drives `frame-src` and manifest validation. |
|
|
@@ -126,8 +127,10 @@ validation. Defaults apply when a manifest declares nothing.
|
|
|
126
127
|
| `GET /r/{code}` | Join a room by code (`broadcast`). |
|
|
127
128
|
| `*` `/ziprack/admin/*` | The headless admin API (`ziprack/admin`). |
|
|
128
129
|
|
|
129
|
-
Session routes
|
|
130
|
-
|
|
130
|
+
Session routes run without the `web` group: no Laravel session, no CSRF, rate limited by
|
|
131
|
+
`ziprack.routes.session_rate_limit`. Each request needs the token in the path **and** the
|
|
132
|
+
`ziprack_launch` cookie the player set when it issued that token, so a token copied out of the page
|
|
133
|
+
or an access log is refused from any other browser.
|
|
131
134
|
|
|
132
135
|
<a name="participants-and-guests"></a>
|
|
133
136
|
## Participants and Guests
|
package/dist/docs/manifest.md
CHANGED
|
@@ -98,7 +98,8 @@ At most 20 roles.
|
|
|
98
98
|
| `reporting.metrics[]` | `activity@1` (+ `attempt@1`) | `{key, label, pre, post}` — pairs two declared activities |
|
|
99
99
|
| `collections[]` | `db@1` | `{name, scope, schema, indexed?, quota?, permissions?, seed?}` |
|
|
100
100
|
| `uploads[]` | `storage@1` | `{name, scope, mime[], max_bytes?, max_files?}` |
|
|
101
|
-
| `prompts[]` | `ai@1` | `{id, mode, template, vars?, output_schema?, budget?, model?}` |
|
|
101
|
+
| `prompts[]` | `ai@1` | `{id, mode, template, vars?, output_schema?, budget?, model?, knowledge?}` |
|
|
102
|
+
| `knowledge[]` | `ai@1` | `{id, source?, label?, fields?}` |
|
|
102
103
|
| `broadcast` | `broadcast@1` | `{channels?, facilitator_events?}` |
|
|
103
104
|
|
|
104
105
|
Each is specified in full on its capability's page. Two cross-cutting rules are worth stating here:
|
package/dist/docs/mcp.md
CHANGED
|
@@ -107,6 +107,9 @@ user is no longer an admin.
|
|
|
107
107
|
| `list-collections`, `list-records` | db | |
|
|
108
108
|
| `list-files` | storage | |
|
|
109
109
|
| `ai-spend` | ai | |
|
|
110
|
+
| `list-knowledge`, `search-knowledge` | ai | Knowledge slots, documents, label counts; try retrieval as a prompt would |
|
|
111
|
+
| `get-knowledge-prep-skill` | ai | The agent skill that prepares decks, PDFs and transcripts as tagged JSONL locally, validates them and loads them |
|
|
112
|
+
| `create-knowledge-upload`, `delete-knowledge-document` | ai | Load a document into a slot via a signed URL; delete one. Need `ziprack.ai.load-knowledge` |
|
|
110
113
|
| `list-rooms`, `get-room` | broadcast | |
|
|
111
114
|
|
|
112
115
|
Capability tools are only present when their capability package is installed.
|
package/dist/docs/security.md
CHANGED
|
@@ -119,12 +119,16 @@ instead of loosening the policy.
|
|
|
119
119
|
scoped to one version, expiring after `ziprack.asset_token_ttl_hours` (6). The extraction disk is
|
|
120
120
|
never publicly served.
|
|
121
121
|
- **Launch session.** Capability calls go to `/ziprack/s/{session}/messages` with a token in the
|
|
122
|
-
path — no session
|
|
123
|
-
`ziprack.session_ttl_hours` (6). The
|
|
122
|
+
path — no Laravel session, no CSRF, rate limited, expiring after
|
|
123
|
+
`ziprack.session_ttl_hours` (6). The token is bound to the launching browser: the launch sets a
|
|
124
|
+
random host-only, HttpOnly, SameSite=Lax `ziprack_launch` cookie (never shared with sibling subdomains), the token carries its hash, and every
|
|
125
|
+
session request must present the matching cookie. A token seen in page source, a screenshot or
|
|
126
|
+
a proxy log cannot be replayed from anywhere else. Logging in or out starts a new cookie, so the next person on a shared
|
|
127
|
+
browser cannot use the previous user's open sessions. The session is what binds a message to a participant, a version,
|
|
124
128
|
an attempt and a room; nothing in the payload can widen that scope.
|
|
125
129
|
- **Signed file URLs.** `storage@1` hands out short-lived URLs, never object keys.
|
|
126
|
-
- **Channel authentication.** The Rack signs its own WebSocket subscriptions
|
|
127
|
-
frame.
|
|
130
|
+
- **Channel authentication.** The Rack signs its own WebSocket subscriptions, and stops signing
|
|
131
|
+
them for a room once it is closed. No token reaches the frame.
|
|
128
132
|
|
|
129
133
|
<a name="the-migration-runner"></a>
|
|
130
134
|
## The Migration Runner
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { HostBridge } from './core/bridge';
|
|
|
6
6
|
export type { AnswerOptions, StatementInput } from './activity';
|
|
7
7
|
export type { Collection, ListQuery, ListResult, RecordRow, WhereClause, WhereOperator } from './db';
|
|
8
8
|
export type { StoredFile, FileStatus } from './storage';
|
|
9
|
-
export type { Chat, ChatReply, Usage } from './ai';
|
|
9
|
+
export type { Chat, ChatReply, Citation, Usage } from './ai';
|
|
10
10
|
export type { Channel, RoomEventName, RoomEvents } from './broadcast';
|
|
11
11
|
export type { MockManifest, AiMockHandler } from './core/mock/MockRack';
|
|
12
12
|
/** The singleton every applet talks through. */
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as f, c as h, i as g, a as u, M as l, b as w, L as b } from "./store-
|
|
2
|
-
import { F as K, P as V, d as W } from "./store-
|
|
1
|
+
import { n as f, c as h, i as g, a as u, M as l, b as w, L as b } from "./store-BHnO2KB3.js";
|
|
2
|
+
import { F as K, P as V, d as W } from "./store-BHnO2KB3.js";
|
|
3
3
|
class v {
|
|
4
4
|
constructor(t) {
|
|
5
5
|
this.link = t;
|
|
@@ -32,8 +32,8 @@ class v {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
class y {
|
|
35
|
-
constructor(t, e) {
|
|
36
|
-
this.link = t, this.promptId = e;
|
|
35
|
+
constructor(t, e, s = {}) {
|
|
36
|
+
this.link = t, this.promptId = e, this.vars = s;
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Send a participant message. onDelta receives text as it streams; the
|
|
@@ -41,21 +41,22 @@ class y {
|
|
|
41
41
|
*/
|
|
42
42
|
async send(t, e) {
|
|
43
43
|
let s = "";
|
|
44
|
-
const n =
|
|
44
|
+
const n = Object.keys(this.vars).length > 0 ? { prompt: this.promptId, message: t, vars: this.vars } : { prompt: this.promptId, message: t }, i = await this.link.send("ai:chat", n, {
|
|
45
45
|
timeoutMs: 6e4,
|
|
46
|
-
onPartial: (
|
|
47
|
-
const
|
|
48
|
-
s +=
|
|
46
|
+
onPartial: (a) => {
|
|
47
|
+
const o = typeof a.delta == "string" ? a.delta : "";
|
|
48
|
+
s += o, e?.(o, s);
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
|
-
return
|
|
51
|
+
return i.ok ? {
|
|
52
52
|
ok: !0,
|
|
53
53
|
data: {
|
|
54
|
-
message_id: String(
|
|
55
|
-
text:
|
|
56
|
-
usage:
|
|
54
|
+
message_id: String(i.data?.message_id ?? ""),
|
|
55
|
+
text: i.data?.text ?? s,
|
|
56
|
+
usage: i.data?.usage ?? { input_tokens: 0, output_tokens: 0 },
|
|
57
|
+
...i.data?.citations ? { citations: i.data.citations } : {}
|
|
57
58
|
}
|
|
58
|
-
} :
|
|
59
|
+
} : i;
|
|
59
60
|
}
|
|
60
61
|
reset() {
|
|
61
62
|
return this.link.send("ai:chat.reset", { prompt: this.promptId });
|
|
@@ -68,13 +69,23 @@ class M {
|
|
|
68
69
|
prompts() {
|
|
69
70
|
return this.link.launch()?.ai?.prompts ?? [];
|
|
70
71
|
}
|
|
71
|
-
/**
|
|
72
|
+
/** The knowledge slots the manifest declares. Their contents stay on the Rack. */
|
|
73
|
+
knowledge() {
|
|
74
|
+
return this.link.launch()?.ai?.knowledge ?? [];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Analysis prompt: structured JSON conforming to the declared output_schema.
|
|
78
|
+
* A prompt bound to knowledge also returns the citations it was grounded in.
|
|
79
|
+
*/
|
|
72
80
|
ask(t, e = {}) {
|
|
73
81
|
return this.link.send("ai:ask", { prompt: t, vars: e }, { timeoutMs: 6e4 });
|
|
74
82
|
}
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Chat prompt: conversation history is held by the Rack. `vars` fill the
|
|
85
|
+
* template's placeholders on every turn, e.g. which persona to speak as.
|
|
86
|
+
*/
|
|
87
|
+
chat(t, e = {}) {
|
|
88
|
+
return new y(this.link, t, e);
|
|
78
89
|
}
|
|
79
90
|
}
|
|
80
91
|
class L {
|
|
@@ -395,12 +406,12 @@ class P {
|
|
|
395
406
|
return;
|
|
396
407
|
}
|
|
397
408
|
if (t.partial && !t.done) {
|
|
398
|
-
const { requestId:
|
|
409
|
+
const { requestId: z, ok: F, partial: $, ...k } = t;
|
|
399
410
|
e.onPartial?.(k), clearTimeout(e.timer), e.timer = setTimeout(() => this.expire(t.requestId), e.timeoutMs);
|
|
400
411
|
return;
|
|
401
412
|
}
|
|
402
413
|
clearTimeout(e.timer), this.pending.delete(t.requestId);
|
|
403
|
-
const { requestId: s, ok: n, data: i, error: a, message: o, details: p, partial: U, done:
|
|
414
|
+
const { requestId: s, ok: n, data: i, error: a, message: o, details: p, partial: U, done: j, ...c } = t;
|
|
404
415
|
e.resolve({ ok: n, data: i ?? (Object.keys(c).length ? c : void 0), error: a, message: o, details: p });
|
|
405
416
|
}
|
|
406
417
|
}
|