@frockbot/kernel-contracts 0.1.4 → 0.2.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/package.json +1 -1
- package/src/authoring.ts +64 -0
- package/src/catalog-change.test.ts +56 -0
- package/src/iframe-ui.test.ts +99 -0
- package/src/iframe-ui.ts +400 -0
- package/src/index.ts +3 -0
- package/src/isolate-context-catalog.generated.ts +25 -0
- package/src/isolate.test.ts +156 -40
- package/src/isolate.ts +685 -94
- package/src/loop-events.test.ts +80 -0
- package/src/loop-events.ts +711 -0
- package/src/send-to-user.ts +7 -56
- package/src/session.test.ts +16 -0
- package/src/tool-execution.ts +16 -0
- package/src/types.ts +229 -0
package/src/isolate.ts
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
// Everything decoded here is untrusted: Bot-authored code produces the results
|
|
20
20
|
// and the capability requests, and the isolate produces the health report.
|
|
21
21
|
import type { TurnAdmissionV1 } from "./tool-execution.js";
|
|
22
|
+
import {
|
|
23
|
+
BOT_ISOLATE_HOOK_EVENTS_V1,
|
|
24
|
+
isBotIsolateHookEventNameV1,
|
|
25
|
+
type BotIsolateHookEventNameV1,
|
|
26
|
+
type LoopEventPayloadMapV1,
|
|
27
|
+
} from "./loop-events.js";
|
|
22
28
|
import {
|
|
23
29
|
decodeTurnTypeV1,
|
|
24
30
|
type LlmStreamEvent,
|
|
@@ -28,21 +34,20 @@ import {
|
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* The wire contract version the kernel wrapper emits. Version 2 added
|
|
31
|
-
* per-tool turn admission
|
|
32
|
-
* tools are therefore offered on every turn type.
|
|
37
|
+
* per-tool turn admission. Version 3 added declared loop hooks and hook RPC.
|
|
33
38
|
*/
|
|
34
|
-
export const ISOLATE_CONTRACT_VERSION =
|
|
39
|
+
export const ISOLATE_CONTRACT_VERSION = 3;
|
|
35
40
|
|
|
36
41
|
/** Every contract version the kernel still decodes. */
|
|
37
|
-
export type IsolateContractVersion = 1 | 2;
|
|
42
|
+
export type IsolateContractVersion = 1 | 2 | 3;
|
|
38
43
|
|
|
39
|
-
const ISOLATE_CONTRACT_VERSIONS: readonly IsolateContractVersion[] = [1, 2];
|
|
44
|
+
const ISOLATE_CONTRACT_VERSIONS: readonly IsolateContractVersion[] = [1, 2, 3];
|
|
40
45
|
|
|
41
46
|
/** The upper bound on a single isolate invocation, enforced on both sides. */
|
|
42
47
|
export const ISOLATE_MAX_DEADLINE_MS = 60_000;
|
|
43
48
|
|
|
44
49
|
const MAX_ISOLATE_TOOLS = 64;
|
|
45
|
-
const
|
|
50
|
+
const MAX_ISOLATE_CONNECTIONS = 100;
|
|
46
51
|
const MAX_ISOLATE_CONTENT = 1_000_000;
|
|
47
52
|
const TOOL_NAME = /^[a-z][a-z0-9_]{0,63}$/;
|
|
48
53
|
|
|
@@ -73,12 +78,32 @@ export interface IsolateToolResultV1 {
|
|
|
73
78
|
isError: boolean;
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
export interface IsolateHookInvocationV1<
|
|
82
|
+
Event extends BotIsolateHookEventNameV1 = BotIsolateHookEventNameV1,
|
|
83
|
+
> {
|
|
84
|
+
schemaVersion: 1;
|
|
85
|
+
event: Event;
|
|
86
|
+
payload: LoopEventPayloadMapV1[Event];
|
|
87
|
+
botId: string;
|
|
88
|
+
sessionId: string;
|
|
89
|
+
runId: string;
|
|
90
|
+
turnId: string;
|
|
91
|
+
generationId: string;
|
|
92
|
+
deadlineMs: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type IsolateHookResultV1 =
|
|
96
|
+
| { schemaVersion: 1; status: "unchanged" }
|
|
97
|
+
| { schemaVersion: 1; status: "replaced"; replacement: unknown };
|
|
98
|
+
|
|
76
99
|
export interface IsolateHealthV1 {
|
|
77
100
|
schemaVersion: 1;
|
|
78
101
|
ok: boolean;
|
|
79
102
|
packageId: string;
|
|
80
103
|
contractVersion: IsolateContractVersion;
|
|
81
104
|
tools: IsolateToolDescriptorV1[];
|
|
105
|
+
/** Contract version 3 and later. */
|
|
106
|
+
hooks?: BotIsolateHookEventNameV1[];
|
|
82
107
|
}
|
|
83
108
|
|
|
84
109
|
/** What `IDENTITY` carries into the isolate. Structured-clonable, never a stub. */
|
|
@@ -88,25 +113,6 @@ export interface IsolateIdentityV1 {
|
|
|
88
113
|
packageId: string;
|
|
89
114
|
}
|
|
90
115
|
|
|
91
|
-
export type IsolateCapabilityKindV1 =
|
|
92
|
-
"tool" | "model" | "memory" | "notification" | "computer";
|
|
93
|
-
|
|
94
|
-
export interface IsolateCapabilityDescriptorV1 {
|
|
95
|
-
capabilityId: string;
|
|
96
|
-
kind: IsolateCapabilityKindV1;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface IsolateAuthorityRequestV1 {
|
|
100
|
-
capabilityId: string;
|
|
101
|
-
reason: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Self-modification never widens authority: the answer is always pending. */
|
|
105
|
-
export interface IsolatePendingDecisionV1 {
|
|
106
|
-
status: "pending-user-decision";
|
|
107
|
-
decisionId: string;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
116
|
/**
|
|
111
117
|
* A capability call the authority could not serve. It is a declared variant,
|
|
112
118
|
* not an exception: an error thrown across the loopback binding would surface
|
|
@@ -118,14 +124,121 @@ export interface IsolateCapabilityFailureV1 {
|
|
|
118
124
|
reason: string;
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
/** One safe Connection projection. Credentials and credential references never cross. */
|
|
128
|
+
export interface IsolateConnectionV1 {
|
|
129
|
+
connectionId: string;
|
|
130
|
+
packageId: string;
|
|
131
|
+
connectionTypeId: string;
|
|
132
|
+
displayName: string;
|
|
133
|
+
generation: string;
|
|
134
|
+
safeMetadata: Record<string, unknown>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The Bot's configured model, resolved through one of its ready Connections. */
|
|
138
|
+
export interface IsolateModelBindingV1 {
|
|
139
|
+
connectionId: string;
|
|
140
|
+
packageId: string;
|
|
141
|
+
provider: string;
|
|
142
|
+
providerModelId: string;
|
|
143
|
+
connectionGeneration: string;
|
|
144
|
+
catalogGeneration?: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The per-Bot authority every Package in one Composition sees identically. */
|
|
148
|
+
export interface IsolateCapabilityListV1 {
|
|
149
|
+
status: "available";
|
|
150
|
+
connections: IsolateConnectionV1[];
|
|
151
|
+
model?: IsolateModelBindingV1;
|
|
152
|
+
tools: true;
|
|
153
|
+
memory: boolean;
|
|
154
|
+
workspace: boolean;
|
|
155
|
+
notify: true;
|
|
156
|
+
schedule: true;
|
|
157
|
+
}
|
|
123
158
|
|
|
124
159
|
export type IsolateCapabilityListOutcomeV1 =
|
|
125
|
-
|
|
160
|
+
IsolateCapabilityListV1 | IsolateCapabilityFailureV1;
|
|
161
|
+
|
|
162
|
+
/** An opaque, short-lived reference to a Connection — never its credential. */
|
|
163
|
+
export interface IsolateConnectionLeaseV1 {
|
|
164
|
+
status: "available";
|
|
165
|
+
leaseId: string;
|
|
166
|
+
connectionId: string;
|
|
167
|
+
generation: string;
|
|
168
|
+
expiresAt: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type IsolateConnectionOutcomeV1 =
|
|
172
|
+
IsolateConnectionLeaseV1 | IsolateCapabilityFailureV1;
|
|
173
|
+
|
|
174
|
+
export interface IsolateToolRequestV1 {
|
|
175
|
+
callId: string;
|
|
176
|
+
name: string;
|
|
177
|
+
input: unknown;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export type IsolateToolOutcomeV1 =
|
|
181
|
+
| { status: "completed"; content: string; isError: boolean }
|
|
182
|
+
| IsolateCapabilityFailureV1;
|
|
183
|
+
|
|
184
|
+
export type IsolateMemoryScopeV1 = "bot" | "user" | "project";
|
|
185
|
+
export type IsolateMemoryTierV1 = "profile" | "log" | "note";
|
|
186
|
+
export interface IsolateMemoryReadRequestV1 {
|
|
187
|
+
scope: IsolateMemoryScopeV1;
|
|
188
|
+
projectId?: string;
|
|
189
|
+
}
|
|
190
|
+
export interface IsolateMemoryWriteRequestV1 extends IsolateMemoryReadRequestV1 {
|
|
191
|
+
tier?: IsolateMemoryTierV1;
|
|
192
|
+
fact: string;
|
|
193
|
+
}
|
|
194
|
+
export type IsolateMemoryOutcomeV1 =
|
|
195
|
+
{ status: "available"; value: unknown } | IsolateCapabilityFailureV1;
|
|
196
|
+
|
|
197
|
+
/** A root selector without a User id; the authority supplies its own User. */
|
|
198
|
+
export type IsolateWorkspaceRootV1 =
|
|
199
|
+
| { kind: "bot-instructions"; botId: string }
|
|
200
|
+
| { kind: "user-instructions" }
|
|
201
|
+
| { kind: "package-declared"; packageId: string; rootId: string };
|
|
202
|
+
export interface IsolateWorkspacePathV1 {
|
|
203
|
+
root: IsolateWorkspaceRootV1;
|
|
204
|
+
path: string;
|
|
205
|
+
}
|
|
206
|
+
export interface IsolateWorkspaceListRequestV1 {
|
|
207
|
+
root: IsolateWorkspaceRootV1;
|
|
208
|
+
prefix?: string;
|
|
209
|
+
cursor?: string;
|
|
210
|
+
limit?: number;
|
|
211
|
+
}
|
|
212
|
+
export interface IsolateWorkspaceWriteRequestV1 {
|
|
213
|
+
path: IsolateWorkspacePathV1;
|
|
214
|
+
bytes: Uint8Array;
|
|
215
|
+
expectedGenerationId: string | null;
|
|
216
|
+
mediaType?: string;
|
|
217
|
+
}
|
|
218
|
+
export interface IsolateWorkspaceDeleteRequestV1 {
|
|
219
|
+
path: IsolateWorkspacePathV1;
|
|
220
|
+
expectedGenerationId: string;
|
|
221
|
+
}
|
|
222
|
+
export type IsolateWorkspaceOutcomeV1 =
|
|
223
|
+
{ status: "available"; value: unknown } | IsolateCapabilityFailureV1;
|
|
224
|
+
|
|
225
|
+
export interface IsolateNotificationRequestV1 {
|
|
226
|
+
notificationId: string;
|
|
227
|
+
title: string;
|
|
228
|
+
body: string;
|
|
229
|
+
}
|
|
230
|
+
export type IsolateNotificationOutcomeV1 =
|
|
231
|
+
{ status: "recorded" } | IsolateCapabilityFailureV1;
|
|
232
|
+
|
|
233
|
+
/** A durable Routine operation attributed to one Package call. */
|
|
234
|
+
export interface IsolateScheduleRequestV1 {
|
|
235
|
+
callId: string;
|
|
236
|
+
input: unknown;
|
|
237
|
+
}
|
|
238
|
+
export type IsolateScheduleOutcomeV1 = IsolateToolOutcomeV1;
|
|
126
239
|
|
|
127
240
|
/**
|
|
128
|
-
*
|
|
241
|
+
* Model invocation through the Bot's configured model binding. Events cross the RPC
|
|
129
242
|
* boundary as an NDJSON byte stream — see `decodeIsolateModelEventV1`. A
|
|
130
243
|
* `ReadableStream` of JavaScript objects is not transferable over workerd RPC;
|
|
131
244
|
* a byte stream is, so the kernel encodes and the isolate decodes.
|
|
@@ -136,10 +249,9 @@ export type IsolateModelInvocationV1 =
|
|
|
136
249
|
requestId: string;
|
|
137
250
|
events: ReadableStream<Uint8Array>;
|
|
138
251
|
}
|
|
139
|
-
|
|
|
252
|
+
| IsolateCapabilityFailureV1;
|
|
140
253
|
|
|
141
|
-
export type IsolateModelOutcomeV1 =
|
|
142
|
-
IsolateModelInvocationV1 | IsolateCapabilityFailureV1;
|
|
254
|
+
export type IsolateModelOutcomeV1 = IsolateModelInvocationV1;
|
|
143
255
|
|
|
144
256
|
/**
|
|
145
257
|
* The wrapper `WorkerEntrypoint` the kernel generates. Bot code never
|
|
@@ -148,25 +260,126 @@ export type IsolateModelOutcomeV1 =
|
|
|
148
260
|
export interface BotIsolateEntrypoint {
|
|
149
261
|
health(): Promise<IsolateHealthV1>;
|
|
150
262
|
execute(invocation: IsolateToolInvocationV1): Promise<IsolateToolResultV1>;
|
|
263
|
+
hook(invocation: IsolateHookInvocationV1): Promise<IsolateHookResultV1>;
|
|
151
264
|
}
|
|
152
265
|
|
|
153
266
|
/**
|
|
154
267
|
* The loopback service binding the Bot's Durable Object mints for one isolate.
|
|
155
|
-
* Every method
|
|
156
|
-
* Bot does not already hold.
|
|
268
|
+
* Every method is Bot-authority-derived: nothing here can hand out authority
|
|
269
|
+
* the Bot does not already hold.
|
|
157
270
|
*/
|
|
158
271
|
export interface BotCapabilitiesStub {
|
|
159
272
|
list(): Promise<IsolateCapabilityListOutcomeV1>;
|
|
160
|
-
/**
|
|
161
|
-
* D6 addendum. The kernel records the normalized request and acquires the
|
|
162
|
-
* credential lease through the existing provider path *before* forwarding.
|
|
163
|
-
* Without a matching enabled model capability the answer is a pending
|
|
164
|
-
* decision, never a grant.
|
|
165
|
-
*/
|
|
166
273
|
invokeModel(request: NormalizedModelRequest): Promise<IsolateModelOutcomeV1>;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
274
|
+
invokeTool(request: IsolateToolRequestV1): Promise<IsolateToolOutcomeV1>;
|
|
275
|
+
memoryRead(
|
|
276
|
+
request: IsolateMemoryReadRequestV1,
|
|
277
|
+
): Promise<IsolateMemoryOutcomeV1>;
|
|
278
|
+
memoryWrite(
|
|
279
|
+
request: IsolateMemoryWriteRequestV1,
|
|
280
|
+
): Promise<IsolateMemoryOutcomeV1>;
|
|
281
|
+
memoryForget(
|
|
282
|
+
request: IsolateMemoryWriteRequestV1,
|
|
283
|
+
): Promise<IsolateMemoryOutcomeV1>;
|
|
284
|
+
workspaceRead(
|
|
285
|
+
path: IsolateWorkspacePathV1,
|
|
286
|
+
): Promise<IsolateWorkspaceOutcomeV1>;
|
|
287
|
+
workspaceList(
|
|
288
|
+
request: IsolateWorkspaceListRequestV1,
|
|
289
|
+
): Promise<IsolateWorkspaceOutcomeV1>;
|
|
290
|
+
workspaceStat(
|
|
291
|
+
path: IsolateWorkspacePathV1,
|
|
292
|
+
): Promise<IsolateWorkspaceOutcomeV1>;
|
|
293
|
+
workspaceWrite(
|
|
294
|
+
request: IsolateWorkspaceWriteRequestV1,
|
|
295
|
+
): Promise<IsolateWorkspaceOutcomeV1>;
|
|
296
|
+
workspaceDelete(
|
|
297
|
+
request: IsolateWorkspaceDeleteRequestV1,
|
|
298
|
+
): Promise<IsolateWorkspaceOutcomeV1>;
|
|
299
|
+
connection(connectionId: string): Promise<IsolateConnectionOutcomeV1>;
|
|
300
|
+
notify(
|
|
301
|
+
request: IsolateNotificationRequestV1,
|
|
302
|
+
): Promise<IsolateNotificationOutcomeV1>;
|
|
303
|
+
schedule(
|
|
304
|
+
request: IsolateScheduleRequestV1,
|
|
305
|
+
): Promise<IsolateScheduleOutcomeV1>;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** The model outcome Bot-authored `package.js` receives after wrapper narrowing. */
|
|
309
|
+
export type BotPackageModelOutcomeV1 =
|
|
310
|
+
| {
|
|
311
|
+
status: "streaming";
|
|
312
|
+
requestId: string;
|
|
313
|
+
events: AsyncIterable<LlmStreamEvent>;
|
|
314
|
+
}
|
|
315
|
+
| IsolateCapabilityFailureV1;
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* The common exact `ctx` passed to a Bot-authored Package's tool and hook.
|
|
319
|
+
* The model-facing declarations are generated from these interfaces, and the
|
|
320
|
+
* wrapper's implementation is compile- and test-checked against the same
|
|
321
|
+
* keys.
|
|
322
|
+
*/
|
|
323
|
+
export interface BotPackageContextV1 {
|
|
324
|
+
readonly tool?: string;
|
|
325
|
+
readonly event?: BotIsolateHookEventNameV1;
|
|
326
|
+
readonly botId: string;
|
|
327
|
+
readonly sessionId: string;
|
|
328
|
+
readonly runId: string;
|
|
329
|
+
readonly turnId: string;
|
|
330
|
+
readonly generationId: string;
|
|
331
|
+
readonly packageId: string;
|
|
332
|
+
readonly deadlineMs: number;
|
|
333
|
+
readonly bindings: string[];
|
|
334
|
+
readonly capabilities: {
|
|
335
|
+
list(): Promise<IsolateCapabilityListOutcomeV1>;
|
|
336
|
+
};
|
|
337
|
+
readonly model: {
|
|
338
|
+
invoke(request: NormalizedModelRequest): Promise<BotPackageModelOutcomeV1>;
|
|
339
|
+
};
|
|
340
|
+
readonly tools: {
|
|
341
|
+
/** Runs through the trusted registry's active-Composition and deny guards. */
|
|
342
|
+
invoke(request: IsolateToolRequestV1): Promise<IsolateToolOutcomeV1>;
|
|
343
|
+
};
|
|
344
|
+
readonly memory: {
|
|
345
|
+
read(request: IsolateMemoryReadRequestV1): Promise<IsolateMemoryOutcomeV1>;
|
|
346
|
+
write(
|
|
347
|
+
request: IsolateMemoryWriteRequestV1,
|
|
348
|
+
): Promise<IsolateMemoryOutcomeV1>;
|
|
349
|
+
forget(
|
|
350
|
+
request: IsolateMemoryWriteRequestV1,
|
|
351
|
+
): Promise<IsolateMemoryOutcomeV1>;
|
|
352
|
+
};
|
|
353
|
+
readonly workspace: {
|
|
354
|
+
read(path: IsolateWorkspacePathV1): Promise<IsolateWorkspaceOutcomeV1>;
|
|
355
|
+
list(
|
|
356
|
+
request: IsolateWorkspaceListRequestV1,
|
|
357
|
+
): Promise<IsolateWorkspaceOutcomeV1>;
|
|
358
|
+
stat(path: IsolateWorkspacePathV1): Promise<IsolateWorkspaceOutcomeV1>;
|
|
359
|
+
write(
|
|
360
|
+
request: IsolateWorkspaceWriteRequestV1,
|
|
361
|
+
): Promise<IsolateWorkspaceOutcomeV1>;
|
|
362
|
+
delete(
|
|
363
|
+
request: IsolateWorkspaceDeleteRequestV1,
|
|
364
|
+
): Promise<IsolateWorkspaceOutcomeV1>;
|
|
365
|
+
};
|
|
366
|
+
connection(connectionId: string): Promise<IsolateConnectionOutcomeV1>;
|
|
367
|
+
notify(
|
|
368
|
+
request: IsolateNotificationRequestV1,
|
|
369
|
+
): Promise<IsolateNotificationOutcomeV1>;
|
|
370
|
+
schedule(
|
|
371
|
+
request: IsolateScheduleRequestV1,
|
|
372
|
+
): Promise<IsolateScheduleOutcomeV1>;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export interface BotPackageExecutionContextV1 extends BotPackageContextV1 {
|
|
376
|
+
readonly tool: string;
|
|
377
|
+
readonly event?: never;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export interface BotPackageHookContextV1 extends BotPackageContextV1 {
|
|
381
|
+
readonly tool?: never;
|
|
382
|
+
readonly event: BotIsolateHookEventNameV1;
|
|
170
383
|
}
|
|
171
384
|
|
|
172
385
|
/**
|
|
@@ -200,10 +413,8 @@ export interface IsolateHost {
|
|
|
200
413
|
* D2. The loader identity, and nothing else — a reused id silently serves the
|
|
201
414
|
* first code and `env`, so every component here is content- or owner-derived.
|
|
202
415
|
* The hash covers the mounted wrapper and Package artifact plus the digest of
|
|
203
|
-
* every baked-in binding: User, Bot, Composition generation, and
|
|
204
|
-
*
|
|
205
|
-
* User prefix independently prevents cross-User reuse (AGENTS.md Package
|
|
206
|
-
* composition; ADR 0019).
|
|
416
|
+
* every baked-in binding: User, Bot, Composition generation, Connections, and
|
|
417
|
+
* resolved model. The User prefix independently prevents cross-User reuse.
|
|
207
418
|
*/
|
|
208
419
|
export function isolateLoaderIdV1(input: {
|
|
209
420
|
userId: string;
|
|
@@ -457,25 +668,114 @@ export function decodeIsolateToolResultV1(
|
|
|
457
668
|
};
|
|
458
669
|
}
|
|
459
670
|
|
|
460
|
-
export function
|
|
671
|
+
export function decodeIsolateHookInvocationV1(
|
|
461
672
|
input: unknown,
|
|
462
|
-
label = "isolate
|
|
463
|
-
):
|
|
673
|
+
label = "isolate hook invocation",
|
|
674
|
+
): IsolateHookInvocationV1 {
|
|
464
675
|
const value = record(input, label);
|
|
465
676
|
exactKeys(
|
|
466
677
|
value,
|
|
467
|
-
[
|
|
678
|
+
[
|
|
679
|
+
"schemaVersion",
|
|
680
|
+
"event",
|
|
681
|
+
"payload",
|
|
682
|
+
"botId",
|
|
683
|
+
"sessionId",
|
|
684
|
+
"runId",
|
|
685
|
+
"turnId",
|
|
686
|
+
"generationId",
|
|
687
|
+
"deadlineMs",
|
|
688
|
+
],
|
|
468
689
|
label,
|
|
469
690
|
);
|
|
470
691
|
if (value.schemaVersion !== 1) {
|
|
471
692
|
throw new Error(`${label}.schemaVersion is unsupported`);
|
|
472
693
|
}
|
|
694
|
+
if (!isBotIsolateHookEventNameV1(value.event)) {
|
|
695
|
+
throw new Error(`${label}.event is invalid`);
|
|
696
|
+
}
|
|
697
|
+
// The host authored the event snapshot. The event-specific replacement is
|
|
698
|
+
// decoded on the return seam; here JSON validation prevents a live object
|
|
699
|
+
// from being smuggled into Bot code by a future host caller.
|
|
700
|
+
jsonValue(value.payload, `${label}.payload`);
|
|
701
|
+
const deadlineMs = value.deadlineMs;
|
|
702
|
+
if (
|
|
703
|
+
!Number.isSafeInteger(deadlineMs) ||
|
|
704
|
+
(deadlineMs as number) <= 0 ||
|
|
705
|
+
(deadlineMs as number) > ISOLATE_MAX_DEADLINE_MS
|
|
706
|
+
) {
|
|
707
|
+
throw new Error(`${label}.deadlineMs is out of range`);
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
schemaVersion: 1,
|
|
711
|
+
event: value.event,
|
|
712
|
+
payload: value.payload as LoopEventPayloadMapV1[BotIsolateHookEventNameV1],
|
|
713
|
+
botId: boundedString(value.botId, `${label}.botId`, 256),
|
|
714
|
+
sessionId: boundedString(value.sessionId, `${label}.sessionId`, 257),
|
|
715
|
+
runId: boundedString(value.runId, `${label}.runId`, 128),
|
|
716
|
+
turnId: boundedString(value.turnId, `${label}.turnId`, 128),
|
|
717
|
+
generationId: boundedString(
|
|
718
|
+
value.generationId,
|
|
719
|
+
`${label}.generationId`,
|
|
720
|
+
256,
|
|
721
|
+
),
|
|
722
|
+
deadlineMs: deadlineMs as number,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export function decodeIsolateHookResultV1(
|
|
727
|
+
input: unknown,
|
|
728
|
+
label = "isolate hook result",
|
|
729
|
+
): IsolateHookResultV1 {
|
|
730
|
+
const value = record(input, label);
|
|
731
|
+
if (value.status === "unchanged") {
|
|
732
|
+
exactKeys(value, ["schemaVersion", "status"], label);
|
|
733
|
+
if (value.schemaVersion !== 1) {
|
|
734
|
+
throw new Error(`${label}.schemaVersion is unsupported`);
|
|
735
|
+
}
|
|
736
|
+
return { schemaVersion: 1, status: "unchanged" };
|
|
737
|
+
}
|
|
738
|
+
exactKeys(value, ["schemaVersion", "status", "replacement"], label);
|
|
739
|
+
if (value.schemaVersion !== 1) {
|
|
740
|
+
throw new Error(`${label}.schemaVersion is unsupported`);
|
|
741
|
+
}
|
|
742
|
+
if (value.status !== "replaced") {
|
|
743
|
+
throw new Error(`${label}.status is invalid`);
|
|
744
|
+
}
|
|
745
|
+
jsonValue(value.replacement, `${label}.replacement`);
|
|
746
|
+
return {
|
|
747
|
+
schemaVersion: 1,
|
|
748
|
+
status: "replaced",
|
|
749
|
+
replacement: value.replacement,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export function decodeIsolateHealthV1(
|
|
754
|
+
input: unknown,
|
|
755
|
+
label = "isolate health",
|
|
756
|
+
): IsolateHealthV1 {
|
|
757
|
+
const value = record(input, label);
|
|
758
|
+
if (value.schemaVersion !== 1) {
|
|
759
|
+
throw new Error(`${label}.schemaVersion is unsupported`);
|
|
760
|
+
}
|
|
473
761
|
const contractVersion = ISOLATE_CONTRACT_VERSIONS.find(
|
|
474
762
|
(candidate) => candidate === value.contractVersion,
|
|
475
763
|
);
|
|
476
764
|
if (contractVersion === undefined) {
|
|
477
765
|
throw new Error(`${label}.contractVersion is unsupported`);
|
|
478
766
|
}
|
|
767
|
+
exactKeys(
|
|
768
|
+
value,
|
|
769
|
+
[
|
|
770
|
+
"schemaVersion",
|
|
771
|
+
"ok",
|
|
772
|
+
"packageId",
|
|
773
|
+
"contractVersion",
|
|
774
|
+
"tools",
|
|
775
|
+
...(contractVersion >= 3 ? ["hooks"] : []),
|
|
776
|
+
],
|
|
777
|
+
label,
|
|
778
|
+
);
|
|
479
779
|
if (typeof value.ok !== "boolean") {
|
|
480
780
|
throw new Error(`${label}.ok must be a boolean`);
|
|
481
781
|
}
|
|
@@ -495,12 +795,32 @@ export function decodeIsolateHealthV1(
|
|
|
495
795
|
if (new Set(tools.map((tool) => tool.name)).size !== tools.length) {
|
|
496
796
|
throw new Error(`${label}.tools contains duplicate names`);
|
|
497
797
|
}
|
|
798
|
+
if (
|
|
799
|
+
contractVersion >= 3 &&
|
|
800
|
+
(!Array.isArray(value.hooks) ||
|
|
801
|
+
value.hooks.length > BOT_ISOLATE_HOOK_EVENTS_V1.length)
|
|
802
|
+
) {
|
|
803
|
+
throw new Error(`${label}.hooks must be a bounded array`);
|
|
804
|
+
}
|
|
805
|
+
const hooks =
|
|
806
|
+
contractVersion < 3
|
|
807
|
+
? []
|
|
808
|
+
: (value.hooks as unknown[]).map((hook, index) => {
|
|
809
|
+
if (!isBotIsolateHookEventNameV1(hook)) {
|
|
810
|
+
throw new Error(`${label}.hooks[${index}] is invalid`);
|
|
811
|
+
}
|
|
812
|
+
return hook;
|
|
813
|
+
});
|
|
814
|
+
if (new Set(hooks).size !== hooks.length) {
|
|
815
|
+
throw new Error(`${label}.hooks contains duplicates`);
|
|
816
|
+
}
|
|
498
817
|
return {
|
|
499
818
|
schemaVersion: 1,
|
|
500
819
|
ok: value.ok,
|
|
501
820
|
packageId: boundedString(value.packageId, `${label}.packageId`, 128),
|
|
502
821
|
contractVersion,
|
|
503
822
|
tools,
|
|
823
|
+
hooks,
|
|
504
824
|
};
|
|
505
825
|
}
|
|
506
826
|
|
|
@@ -521,73 +841,344 @@ export function decodeIsolateIdentityV1(
|
|
|
521
841
|
};
|
|
522
842
|
}
|
|
523
843
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
"
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
844
|
+
export function decodeIsolateConnectionV1(
|
|
845
|
+
input: unknown,
|
|
846
|
+
label = "isolate Connection",
|
|
847
|
+
): IsolateConnectionV1 {
|
|
848
|
+
const value = record(input, label);
|
|
849
|
+
exactKeys(
|
|
850
|
+
value,
|
|
851
|
+
[
|
|
852
|
+
"connectionId",
|
|
853
|
+
"packageId",
|
|
854
|
+
"connectionTypeId",
|
|
855
|
+
"displayName",
|
|
856
|
+
"generation",
|
|
857
|
+
"safeMetadata",
|
|
858
|
+
],
|
|
859
|
+
label,
|
|
860
|
+
);
|
|
861
|
+
const safeMetadata = record(value.safeMetadata, `${label}.safeMetadata`);
|
|
862
|
+
jsonValue(safeMetadata, `${label}.safeMetadata`);
|
|
863
|
+
return {
|
|
864
|
+
connectionId: boundedString(
|
|
865
|
+
value.connectionId,
|
|
866
|
+
`${label}.connectionId`,
|
|
867
|
+
256,
|
|
868
|
+
),
|
|
869
|
+
packageId: boundedString(value.packageId, `${label}.packageId`, 128),
|
|
870
|
+
connectionTypeId: boundedString(
|
|
871
|
+
value.connectionTypeId,
|
|
872
|
+
`${label}.connectionTypeId`,
|
|
873
|
+
128,
|
|
874
|
+
),
|
|
875
|
+
displayName: boundedString(value.displayName, `${label}.displayName`, 256),
|
|
876
|
+
generation: boundedString(value.generation, `${label}.generation`, 256),
|
|
877
|
+
safeMetadata,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
531
880
|
|
|
532
|
-
export function
|
|
881
|
+
export function decodeIsolateModelBindingV1(
|
|
533
882
|
input: unknown,
|
|
534
|
-
label = "isolate
|
|
535
|
-
):
|
|
883
|
+
label = "isolate model binding",
|
|
884
|
+
): IsolateModelBindingV1 {
|
|
536
885
|
const value = record(input, label);
|
|
537
|
-
exactKeys(
|
|
538
|
-
|
|
539
|
-
|
|
886
|
+
exactKeys(
|
|
887
|
+
value,
|
|
888
|
+
[
|
|
889
|
+
"connectionId",
|
|
890
|
+
"packageId",
|
|
891
|
+
"provider",
|
|
892
|
+
"providerModelId",
|
|
893
|
+
"connectionGeneration",
|
|
894
|
+
],
|
|
895
|
+
label,
|
|
896
|
+
["catalogGeneration"],
|
|
897
|
+
);
|
|
540
898
|
return {
|
|
541
|
-
|
|
542
|
-
value.
|
|
543
|
-
`${label}.
|
|
899
|
+
connectionId: boundedString(
|
|
900
|
+
value.connectionId,
|
|
901
|
+
`${label}.connectionId`,
|
|
902
|
+
256,
|
|
903
|
+
),
|
|
904
|
+
packageId: boundedString(value.packageId, `${label}.packageId`, 128),
|
|
905
|
+
provider: boundedString(value.provider, `${label}.provider`, 128),
|
|
906
|
+
providerModelId: boundedString(
|
|
907
|
+
value.providerModelId,
|
|
908
|
+
`${label}.providerModelId`,
|
|
544
909
|
256,
|
|
545
910
|
),
|
|
546
|
-
|
|
911
|
+
connectionGeneration: boundedString(
|
|
912
|
+
value.connectionGeneration,
|
|
913
|
+
`${label}.connectionGeneration`,
|
|
914
|
+
256,
|
|
915
|
+
),
|
|
916
|
+
...(value.catalogGeneration === undefined
|
|
917
|
+
? {}
|
|
918
|
+
: {
|
|
919
|
+
catalogGeneration: boundedString(
|
|
920
|
+
value.catalogGeneration,
|
|
921
|
+
`${label}.catalogGeneration`,
|
|
922
|
+
256,
|
|
923
|
+
),
|
|
924
|
+
}),
|
|
547
925
|
};
|
|
548
926
|
}
|
|
549
927
|
|
|
550
928
|
export function decodeIsolateCapabilityListV1(
|
|
551
929
|
input: unknown,
|
|
552
930
|
label = "isolate capability list",
|
|
553
|
-
):
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
931
|
+
): IsolateCapabilityListV1 {
|
|
932
|
+
const value = record(input, label);
|
|
933
|
+
exactKeys(
|
|
934
|
+
value,
|
|
935
|
+
[
|
|
936
|
+
"status",
|
|
937
|
+
"connections",
|
|
938
|
+
"tools",
|
|
939
|
+
"memory",
|
|
940
|
+
"workspace",
|
|
941
|
+
"notify",
|
|
942
|
+
"schedule",
|
|
943
|
+
],
|
|
944
|
+
label,
|
|
945
|
+
["model"],
|
|
560
946
|
);
|
|
947
|
+
if (
|
|
948
|
+
value.status !== "available" ||
|
|
949
|
+
value.tools !== true ||
|
|
950
|
+
value.notify !== true ||
|
|
951
|
+
value.schedule !== true ||
|
|
952
|
+
typeof value.memory !== "boolean" ||
|
|
953
|
+
typeof value.workspace !== "boolean" ||
|
|
954
|
+
!Array.isArray(value.connections) ||
|
|
955
|
+
value.connections.length > MAX_ISOLATE_CONNECTIONS
|
|
956
|
+
) {
|
|
957
|
+
throw new Error(`${label} is invalid`);
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
status: "available",
|
|
961
|
+
connections: value.connections.map((connection, index) =>
|
|
962
|
+
decodeIsolateConnectionV1(connection, `${label}.connections[${index}]`),
|
|
963
|
+
),
|
|
964
|
+
...(value.model === undefined
|
|
965
|
+
? {}
|
|
966
|
+
: { model: decodeIsolateModelBindingV1(value.model, `${label}.model`) }),
|
|
967
|
+
tools: true,
|
|
968
|
+
memory: value.memory,
|
|
969
|
+
workspace: value.workspace,
|
|
970
|
+
notify: true,
|
|
971
|
+
schedule: true,
|
|
972
|
+
};
|
|
561
973
|
}
|
|
562
974
|
|
|
563
|
-
export function
|
|
975
|
+
export function decodeIsolateToolRequestV1(
|
|
564
976
|
input: unknown,
|
|
565
|
-
label = "isolate
|
|
566
|
-
):
|
|
977
|
+
label = "isolate tool request",
|
|
978
|
+
): IsolateToolRequestV1 {
|
|
567
979
|
const value = record(input, label);
|
|
568
|
-
exactKeys(value, ["
|
|
980
|
+
exactKeys(value, ["callId", "name", "input"], label);
|
|
981
|
+
jsonValue(value.input, `${label}.input`);
|
|
569
982
|
return {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
983
|
+
callId: boundedString(value.callId, `${label}.callId`, 256),
|
|
984
|
+
name: boundedString(value.name, `${label}.name`, 128),
|
|
985
|
+
input: value.input,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export function decodeIsolateScheduleRequestV1(
|
|
990
|
+
input: unknown,
|
|
991
|
+
label = "isolate schedule request",
|
|
992
|
+
): IsolateScheduleRequestV1 {
|
|
993
|
+
const value = record(input, label);
|
|
994
|
+
exactKeys(value, ["callId", "input"], label);
|
|
995
|
+
jsonValue(value.input, `${label}.input`);
|
|
996
|
+
return {
|
|
997
|
+
callId: boundedString(value.callId, `${label}.callId`, 256),
|
|
998
|
+
input: value.input,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const MEMORY_SCOPES = ["bot", "user", "project"] as const;
|
|
1003
|
+
const MEMORY_TIERS = ["profile", "log", "note"] as const;
|
|
1004
|
+
|
|
1005
|
+
export function decodeIsolateMemoryReadRequestV1(
|
|
1006
|
+
input: unknown,
|
|
1007
|
+
label = "isolate Memory read request",
|
|
1008
|
+
): IsolateMemoryReadRequestV1 {
|
|
1009
|
+
const value = record(input, label);
|
|
1010
|
+
exactKeys(value, ["scope"], label, ["projectId"]);
|
|
1011
|
+
const scope = MEMORY_SCOPES.find((candidate) => candidate === value.scope);
|
|
1012
|
+
if (!scope) throw new Error(`${label}.scope is invalid`);
|
|
1013
|
+
if (scope === "project" && value.projectId === undefined) {
|
|
1014
|
+
throw new Error(`${label}.projectId is required`);
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
scope,
|
|
1018
|
+
...(value.projectId === undefined
|
|
1019
|
+
? {}
|
|
1020
|
+
: {
|
|
1021
|
+
projectId: boundedString(value.projectId, `${label}.projectId`, 128),
|
|
1022
|
+
}),
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
export function decodeIsolateMemoryWriteRequestV1(
|
|
1027
|
+
input: unknown,
|
|
1028
|
+
label = "isolate Memory write request",
|
|
1029
|
+
): IsolateMemoryWriteRequestV1 {
|
|
1030
|
+
const value = record(input, label);
|
|
1031
|
+
exactKeys(value, ["scope", "fact"], label, ["projectId", "tier"]);
|
|
1032
|
+
const scope = MEMORY_SCOPES.find((candidate) => candidate === value.scope);
|
|
1033
|
+
const tier =
|
|
1034
|
+
value.tier === undefined
|
|
1035
|
+
? undefined
|
|
1036
|
+
: MEMORY_TIERS.find((candidate) => candidate === value.tier);
|
|
1037
|
+
if (!scope || (value.tier !== undefined && !tier)) {
|
|
1038
|
+
throw new Error(`${label} is invalid`);
|
|
1039
|
+
}
|
|
1040
|
+
if (scope === "project" && value.projectId === undefined) {
|
|
1041
|
+
throw new Error(`${label}.projectId is required`);
|
|
1042
|
+
}
|
|
1043
|
+
return {
|
|
1044
|
+
scope,
|
|
1045
|
+
...(value.projectId === undefined
|
|
1046
|
+
? {}
|
|
1047
|
+
: {
|
|
1048
|
+
projectId: boundedString(value.projectId, `${label}.projectId`, 128),
|
|
1049
|
+
}),
|
|
1050
|
+
...(tier ? { tier } : {}),
|
|
1051
|
+
fact: boundedString(value.fact, `${label}.fact`, 2_000),
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function decodeIsolateWorkspaceRootV1(
|
|
1056
|
+
input: unknown,
|
|
1057
|
+
label: string,
|
|
1058
|
+
): IsolateWorkspaceRootV1 {
|
|
1059
|
+
const value = record(input, label);
|
|
1060
|
+
if (value.kind === "user-instructions") {
|
|
1061
|
+
exactKeys(value, ["kind"], label);
|
|
1062
|
+
return { kind: "user-instructions" };
|
|
1063
|
+
}
|
|
1064
|
+
if (value.kind === "bot-instructions") {
|
|
1065
|
+
exactKeys(value, ["kind", "botId"], label);
|
|
1066
|
+
return {
|
|
1067
|
+
kind: "bot-instructions",
|
|
1068
|
+
botId: boundedString(value.botId, `${label}.botId`, 256),
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
if (value.kind === "package-declared") {
|
|
1072
|
+
exactKeys(value, ["kind", "packageId", "rootId"], label);
|
|
1073
|
+
return {
|
|
1074
|
+
kind: "package-declared",
|
|
1075
|
+
packageId: boundedString(value.packageId, `${label}.packageId`, 128),
|
|
1076
|
+
rootId: boundedString(value.rootId, `${label}.rootId`, 128),
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
throw new Error(`${label}.kind is invalid`);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
export function decodeIsolateWorkspacePathV1(
|
|
1083
|
+
input: unknown,
|
|
1084
|
+
label = "isolate Workspace path",
|
|
1085
|
+
): IsolateWorkspacePathV1 {
|
|
1086
|
+
const value = record(input, label);
|
|
1087
|
+
exactKeys(value, ["root", "path"], label);
|
|
1088
|
+
return {
|
|
1089
|
+
root: decodeIsolateWorkspaceRootV1(value.root, `${label}.root`),
|
|
1090
|
+
path: boundedString(value.path, `${label}.path`, 1_024, true),
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
export function decodeIsolateWorkspaceListRequestV1(
|
|
1095
|
+
input: unknown,
|
|
1096
|
+
label = "isolate Workspace list request",
|
|
1097
|
+
): IsolateWorkspaceListRequestV1 {
|
|
1098
|
+
const value = record(input, label);
|
|
1099
|
+
exactKeys(value, ["root"], label, ["prefix", "cursor", "limit"]);
|
|
1100
|
+
const limit = value.limit;
|
|
1101
|
+
if (
|
|
1102
|
+
limit !== undefined &&
|
|
1103
|
+
(!Number.isSafeInteger(limit) ||
|
|
1104
|
+
(limit as number) <= 0 ||
|
|
1105
|
+
(limit as number) > 1_000)
|
|
1106
|
+
) {
|
|
1107
|
+
throw new Error(`${label}.limit is invalid`);
|
|
1108
|
+
}
|
|
1109
|
+
return {
|
|
1110
|
+
root: decodeIsolateWorkspaceRootV1(value.root, `${label}.root`),
|
|
1111
|
+
...(value.prefix === undefined
|
|
1112
|
+
? {}
|
|
1113
|
+
: {
|
|
1114
|
+
prefix: boundedString(value.prefix, `${label}.prefix`, 1_024, true),
|
|
1115
|
+
}),
|
|
1116
|
+
...(value.cursor === undefined
|
|
1117
|
+
? {}
|
|
1118
|
+
: { cursor: boundedString(value.cursor, `${label}.cursor`, 1_024) }),
|
|
1119
|
+
...(limit === undefined ? {} : { limit: limit as number }),
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
export function decodeIsolateWorkspaceWriteRequestV1(
|
|
1124
|
+
input: unknown,
|
|
1125
|
+
label = "isolate Workspace write request",
|
|
1126
|
+
): IsolateWorkspaceWriteRequestV1 {
|
|
1127
|
+
const value = record(input, label);
|
|
1128
|
+
exactKeys(value, ["path", "bytes", "expectedGenerationId"], label, [
|
|
1129
|
+
"mediaType",
|
|
1130
|
+
]);
|
|
1131
|
+
if (!(value.bytes instanceof Uint8Array)) {
|
|
1132
|
+
throw new Error(`${label}.bytes must be bytes`);
|
|
1133
|
+
}
|
|
1134
|
+
if (
|
|
1135
|
+
value.expectedGenerationId !== null &&
|
|
1136
|
+
typeof value.expectedGenerationId !== "string"
|
|
1137
|
+
) {
|
|
1138
|
+
throw new Error(`${label}.expectedGenerationId is invalid`);
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
path: decodeIsolateWorkspacePathV1(value.path, `${label}.path`),
|
|
1142
|
+
bytes: value.bytes,
|
|
1143
|
+
expectedGenerationId: value.expectedGenerationId as string | null,
|
|
1144
|
+
...(value.mediaType === undefined
|
|
1145
|
+
? {}
|
|
1146
|
+
: {
|
|
1147
|
+
mediaType: boundedString(value.mediaType, `${label}.mediaType`, 256),
|
|
1148
|
+
}),
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
export function decodeIsolateWorkspaceDeleteRequestV1(
|
|
1153
|
+
input: unknown,
|
|
1154
|
+
label = "isolate Workspace delete request",
|
|
1155
|
+
): IsolateWorkspaceDeleteRequestV1 {
|
|
1156
|
+
const value = record(input, label);
|
|
1157
|
+
exactKeys(value, ["path", "expectedGenerationId"], label);
|
|
1158
|
+
return {
|
|
1159
|
+
path: decodeIsolateWorkspacePathV1(value.path, `${label}.path`),
|
|
1160
|
+
expectedGenerationId: boundedString(
|
|
1161
|
+
value.expectedGenerationId,
|
|
1162
|
+
`${label}.expectedGenerationId`,
|
|
573
1163
|
256,
|
|
574
1164
|
),
|
|
575
|
-
reason: boundedString(value.reason, `${label}.reason`, 2048, true),
|
|
576
1165
|
};
|
|
577
1166
|
}
|
|
578
1167
|
|
|
579
|
-
export function
|
|
1168
|
+
export function decodeIsolateNotificationRequestV1(
|
|
580
1169
|
input: unknown,
|
|
581
|
-
label = "isolate
|
|
582
|
-
):
|
|
1170
|
+
label = "isolate notification request",
|
|
1171
|
+
): IsolateNotificationRequestV1 {
|
|
583
1172
|
const value = record(input, label);
|
|
584
|
-
exactKeys(value, ["
|
|
585
|
-
if (value.status !== "pending-user-decision") {
|
|
586
|
-
throw new Error(`${label}.status must be pending-user-decision`);
|
|
587
|
-
}
|
|
1173
|
+
exactKeys(value, ["notificationId", "title", "body"], label);
|
|
588
1174
|
return {
|
|
589
|
-
|
|
590
|
-
|
|
1175
|
+
notificationId: boundedString(
|
|
1176
|
+
value.notificationId,
|
|
1177
|
+
`${label}.notificationId`,
|
|
1178
|
+
256,
|
|
1179
|
+
),
|
|
1180
|
+
title: boundedString(value.title, `${label}.title`, 160),
|
|
1181
|
+
body: boundedString(value.body, `${label}.body`, 500),
|
|
591
1182
|
};
|
|
592
1183
|
}
|
|
593
1184
|
|
|
@@ -662,8 +1253,8 @@ export function decodeIsolateModelInvocationV1(
|
|
|
662
1253
|
label = "isolate model invocation",
|
|
663
1254
|
): IsolateModelInvocationV1 {
|
|
664
1255
|
const value = record(input, label);
|
|
665
|
-
if (value.status === "
|
|
666
|
-
return
|
|
1256
|
+
if (value.status === "unavailable") {
|
|
1257
|
+
return decodeIsolateCapabilityFailureV1(value, label);
|
|
667
1258
|
}
|
|
668
1259
|
exactKeys(value, ["status", "requestId", "events"], label);
|
|
669
1260
|
if (value.status !== "streaming") {
|