@frockbot/plugin-shell 0.1.4 → 0.2.1
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/frockbot.json +4 -0
- package/package.json +30 -28
- package/src/agent.ts +10 -13
- package/src/backend-authoring.test.ts +356 -2
- package/src/backend-authoring.ts +585 -20
- package/src/backend-composition-input.test.ts +65 -0
- package/src/backend-composition-input.ts +58 -0
- package/src/backend-composition.ts +19 -1
- package/src/backend-configuration.test.ts +166 -0
- package/src/backend-contracts.test.ts +28 -0
- package/src/backend-contracts.ts +3 -1
- package/src/backend-iframe-ui.test.ts +131 -0
- package/src/backend-isolate.test.ts +198 -203
- package/src/backend-isolate.ts +96 -231
- package/src/backend-package-catalog.test.ts +451 -0
- package/src/backend-package-catalog.ts +923 -0
- package/src/backend-runner-iframe.test.ts +74 -0
- package/src/backend-runner.ts +192 -0
- package/src/backend.ts +947 -206
- package/src/client/FrockBotApp.vue +38 -0
- package/src/client/PackageIframeHost.vue +218 -0
- package/src/client/PackageIframeSettings.vue +52 -0
- package/src/client/SendPayloadView.vue +0 -60
- package/src/client/index.test.ts +119 -0
- package/src/client/index.ts +78 -1
- package/src/client/package-iframe-host-message.test.ts +41 -0
- package/src/client/package-iframe-host-message.ts +27 -0
- package/src/composition-views.ts +54 -0
- package/src/shared.ts +10 -0
package/src/backend-isolate.ts
CHANGED
|
@@ -1,119 +1,61 @@
|
|
|
1
1
|
// The Bot Durable Object half of the isolate capability boundary.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// rather than a grant, and a model call records its normalized request and
|
|
8
|
-
// acquires its credential lease through the existing provider path before a
|
|
9
|
-
// single byte leaves the account.
|
|
3
|
+
// Every Package mounted for one Bot receives the same authority projection:
|
|
4
|
+
// the User's ready Connections, the Bot's configured model, and the shared
|
|
5
|
+
// tool, Memory, Workspace, and notification surfaces. Package identity remains
|
|
6
|
+
// in props only for attribution; it never narrows the authority list.
|
|
10
7
|
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
IsolateCapabilityListV1,
|
|
9
|
+
IsolateConnectionLeaseV1,
|
|
10
|
+
IsolateConnectionOutcomeV1,
|
|
11
|
+
IsolateConnectionV1,
|
|
12
|
+
IsolateModelBindingV1,
|
|
13
|
+
IsolateModelInvocationV1,
|
|
16
14
|
LlmStreamEvent,
|
|
17
15
|
NormalizedModelRequest,
|
|
18
16
|
} from "@frockbot/kernel-contracts";
|
|
19
|
-
import {
|
|
20
|
-
decodeIsolateAuthorityRequestV1,
|
|
21
|
-
encodeIsolateModelEventLineV1,
|
|
22
|
-
} from "@frockbot/kernel-contracts";
|
|
17
|
+
import { encodeIsolateModelEventLineV1 } from "@frockbot/kernel-contracts";
|
|
23
18
|
import type { BotIsolateArtifactStore } from "@frockbot/kernel-composition/isolate";
|
|
24
|
-
import type { EnabledCapabilityV1 } from "@frockbot/configuration-core";
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* gateway Worker's own so Bot-authored code cannot outrun the kernel wrapper.
|
|
29
|
-
*/
|
|
20
|
+
export type { IsolateModelBindingV1 } from "@frockbot/kernel-contracts";
|
|
21
|
+
|
|
30
22
|
export const BOT_ISOLATE_COMPATIBILITY_DATE = "2026-08-27";
|
|
31
23
|
|
|
32
|
-
/**
|
|
33
|
-
* What travels in `ctx.exports.BotCapabilities({ props })`. Structured-clonable
|
|
34
|
-
* by necessity: props cross into a loopback service binding.
|
|
35
|
-
*/
|
|
36
24
|
export interface BotCapabilitiesPropsV1 {
|
|
37
25
|
userId: string;
|
|
38
26
|
botId: string;
|
|
27
|
+
runId: string;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
turnId: string;
|
|
39
30
|
generationId: string;
|
|
40
31
|
packageId: string;
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
connections: IsolateConnectionV1[];
|
|
33
|
+
model?: IsolateModelBindingV1;
|
|
34
|
+
memory: boolean;
|
|
35
|
+
workspace: boolean;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
export const ISOLATE_DECISION_PREFIX = "isolate:decision:";
|
|
46
38
|
export const ISOLATE_MODEL_REQUEST_PREFIX = "isolate:model-request:";
|
|
47
39
|
|
|
48
|
-
/** A durable record that the Bot asked for authority it does not hold. */
|
|
49
|
-
export interface IsolatePendingAuthorityDecisionV1 {
|
|
50
|
-
schemaVersion: 1;
|
|
51
|
-
decisionId: string;
|
|
52
|
-
botId: string;
|
|
53
|
-
packageId: string;
|
|
54
|
-
generationId: string;
|
|
55
|
-
capabilityId: string;
|
|
56
|
-
reason: string;
|
|
57
|
-
requestedAt: string;
|
|
58
|
-
status: "pending";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** The intent recorded before a Bot-authored adapter's model call is forwarded. */
|
|
62
40
|
export interface IsolateModelRequestRecordV1 {
|
|
63
41
|
schemaVersion: 1;
|
|
64
|
-
/** Minted by this Durable Object; the record is keyed by it. */
|
|
65
42
|
recordId: string;
|
|
66
|
-
/** The Bot's own correlation id. Bounded, and never a storage key. */
|
|
67
43
|
requestId: string;
|
|
68
44
|
botId: string;
|
|
69
45
|
packageId: string;
|
|
70
46
|
generationId: string;
|
|
71
|
-
capabilityId: string;
|
|
72
47
|
request: NormalizedModelRequest;
|
|
73
48
|
recordedAt: string;
|
|
74
49
|
}
|
|
75
50
|
|
|
76
|
-
/** The narrow storage surface this module needs from the Durable Object. */
|
|
77
51
|
export interface IsolateCapabilityStore {
|
|
78
52
|
put(key: string, value: unknown): Promise<void>;
|
|
79
|
-
get<T>(key: string): Promise<T | undefined>;
|
|
80
53
|
list<T>(options: { prefix: string }): Promise<Map<string, T>>;
|
|
81
54
|
}
|
|
82
55
|
|
|
83
|
-
/** One account-wide Capability, already resolved from User enablement. */
|
|
84
|
-
export type IsolateCapabilityV1 = EnabledCapabilityV1;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* The Bot's effective model binding, resolved by the authority from generic
|
|
88
|
-
* Package settings and the User's Connection — never from anything the Bot
|
|
89
|
-
* supplied. An `invokeModel` request is authorized only when it names exactly
|
|
90
|
-
* this provider and this model, and it is forwarded carrying exactly this
|
|
91
|
-
* binding.
|
|
92
|
-
*/
|
|
93
|
-
export interface IsolateModelBindingV1 {
|
|
94
|
-
packageId: string;
|
|
95
|
-
capabilityId: string;
|
|
96
|
-
connectionId: string;
|
|
97
|
-
provider: string;
|
|
98
|
-
providerModelId: string;
|
|
99
|
-
connectionGeneration?: string;
|
|
100
|
-
catalogGeneration?: string;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** A configured model binding whose Connection cannot currently be used. */
|
|
104
|
-
export interface IsolateUnavailableModelBindingV1 {
|
|
105
|
-
provider?: string;
|
|
106
|
-
providerModelId: string;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export const ISOLATE_MODEL_UNAVAILABLE_MESSAGE =
|
|
110
|
-
"the enabled model binding is unavailable";
|
|
111
|
-
|
|
112
|
-
/** The bound Bot-supplied correlation id: a field, never a key, and never unbounded. */
|
|
113
56
|
export const MAX_ISOLATE_REQUEST_ID = 256;
|
|
114
57
|
|
|
115
58
|
export interface IsolateModelPath {
|
|
116
|
-
/** Streams through the mounted provider Plugin; the lease is taken inside it. */
|
|
117
59
|
stream(
|
|
118
60
|
request: NormalizedModelRequest,
|
|
119
61
|
signal: AbortSignal,
|
|
@@ -125,56 +67,56 @@ export interface IsolateCapabilityHostOptions {
|
|
|
125
67
|
botId: string;
|
|
126
68
|
packageId: string;
|
|
127
69
|
generationId: string;
|
|
128
|
-
|
|
129
|
-
capabilities: readonly IsolateCapabilityV1[];
|
|
130
|
-
/**
|
|
131
|
-
* The one ready model binding resolved for this Bot. Absent without an
|
|
132
|
-
* unavailable binding means every model request is an authority-widening
|
|
133
|
-
* pending decision.
|
|
134
|
-
*/
|
|
70
|
+
connections: readonly IsolateConnectionV1[];
|
|
135
71
|
modelBinding?: IsolateModelBindingV1;
|
|
136
|
-
/** Present when the configured binding is held but its Connection is unavailable. */
|
|
137
|
-
unavailableModelBinding?: IsolateUnavailableModelBindingV1;
|
|
138
|
-
/** Present only while the ready binding can reach its provider path. */
|
|
139
72
|
modelPath?: IsolateModelPath;
|
|
73
|
+
memory: boolean;
|
|
74
|
+
workspace: boolean;
|
|
140
75
|
now?(): Date;
|
|
141
76
|
newId?(): string;
|
|
142
77
|
}
|
|
143
78
|
|
|
144
79
|
export interface IsolateCapabilityHost {
|
|
145
|
-
list(): Promise<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
80
|
+
list(): Promise<IsolateCapabilityListV1>;
|
|
81
|
+
invokeModel(
|
|
82
|
+
request: NormalizedModelRequest,
|
|
83
|
+
): Promise<IsolateModelInvocationV1>;
|
|
149
84
|
recordedModelRequests(): Promise<IsolateModelRequestRecordV1[]>;
|
|
150
85
|
}
|
|
151
86
|
|
|
152
|
-
|
|
153
|
-
* The enabled model Capability that can serve this request, if any.
|
|
154
|
-
*
|
|
155
|
-
* The authority resolves the effective model against one User-enabled
|
|
156
|
-
* Package, Connection, and Capability. A request naming any other provider or
|
|
157
|
-
* model resolves to nothing, whatever the Bot claims about it — the
|
|
158
|
-
* Bot-supplied `modelBinding` is never read here or anywhere downstream.
|
|
159
|
-
*/
|
|
160
|
-
export function matchingModelCapabilityV1(
|
|
161
|
-
capabilities: readonly IsolateCapabilityV1[],
|
|
87
|
+
export function matchingModelBindingV1(
|
|
162
88
|
binding: IsolateModelBindingV1 | undefined,
|
|
163
89
|
request: NormalizedModelRequest,
|
|
164
|
-
):
|
|
165
|
-
|
|
90
|
+
): IsolateModelBindingV1 | undefined {
|
|
91
|
+
const admitted = request.modelBinding;
|
|
166
92
|
if (
|
|
93
|
+
!binding ||
|
|
94
|
+
!admitted ||
|
|
167
95
|
request.provider !== binding.provider ||
|
|
168
|
-
request.model !== binding.providerModelId
|
|
96
|
+
request.model !== binding.providerModelId ||
|
|
97
|
+
admitted.connectionId !== binding.connectionId ||
|
|
98
|
+
admitted.connectionGeneration !== binding.connectionGeneration ||
|
|
99
|
+
admitted.catalogGeneration !== binding.catalogGeneration
|
|
169
100
|
) {
|
|
170
101
|
return undefined;
|
|
171
102
|
}
|
|
172
|
-
return
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
103
|
+
return binding;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A loaded isolate may receive only the Connection generation baked into its
|
|
108
|
+
* admitted authority snapshot. A later User Connection change gets a new
|
|
109
|
+
* binding digest and isolate identity; it must not leak through this old stub.
|
|
110
|
+
*/
|
|
111
|
+
export function matchesAdmittedConnectionV1(
|
|
112
|
+
admitted: IsolateConnectionV1 | undefined,
|
|
113
|
+
outcome: IsolateConnectionOutcomeV1,
|
|
114
|
+
): outcome is IsolateConnectionLeaseV1 {
|
|
115
|
+
return (
|
|
116
|
+
admitted !== undefined &&
|
|
117
|
+
outcome.status === "available" &&
|
|
118
|
+
outcome.connectionId === admitted.connectionId &&
|
|
119
|
+
outcome.generation === admitted.generation
|
|
178
120
|
);
|
|
179
121
|
}
|
|
180
122
|
|
|
@@ -184,103 +126,47 @@ export function createIsolateCapabilityHost(
|
|
|
184
126
|
const now = options.now ?? (() => new Date());
|
|
185
127
|
const newId = options.newId ?? (() => crypto.randomUUID());
|
|
186
128
|
|
|
187
|
-
async function recordDecision(
|
|
188
|
-
capabilityId: string,
|
|
189
|
-
reason: string,
|
|
190
|
-
): Promise<IsolatePendingDecisionV1> {
|
|
191
|
-
const decisionId = `decision-${newId()}`;
|
|
192
|
-
const record: IsolatePendingAuthorityDecisionV1 = {
|
|
193
|
-
schemaVersion: 1,
|
|
194
|
-
decisionId,
|
|
195
|
-
botId: options.botId,
|
|
196
|
-
packageId: options.packageId,
|
|
197
|
-
generationId: options.generationId,
|
|
198
|
-
capabilityId,
|
|
199
|
-
reason,
|
|
200
|
-
requestedAt: now().toISOString(),
|
|
201
|
-
status: "pending",
|
|
202
|
-
};
|
|
203
|
-
await options.storage.put(
|
|
204
|
-
`${ISOLATE_DECISION_PREFIX}${decisionId}`,
|
|
205
|
-
record,
|
|
206
|
-
);
|
|
207
|
-
return { status: "pending-user-decision", decisionId };
|
|
208
|
-
}
|
|
209
|
-
|
|
210
129
|
return {
|
|
211
|
-
list(): Promise<
|
|
212
|
-
return Promise.resolve(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
decodeIsolateAuthorityRequestV1(request);
|
|
225
|
-
// Self-modification never widens authority, even when the capability is
|
|
226
|
-
// already enabled: the answer is a decision the User makes.
|
|
227
|
-
return await recordDecision(decoded.capabilityId, decoded.reason);
|
|
130
|
+
list(): Promise<IsolateCapabilityListV1> {
|
|
131
|
+
return Promise.resolve({
|
|
132
|
+
status: "available",
|
|
133
|
+
connections: structuredClone([...options.connections]),
|
|
134
|
+
...(options.modelBinding
|
|
135
|
+
? { model: structuredClone(options.modelBinding) }
|
|
136
|
+
: {}),
|
|
137
|
+
tools: true,
|
|
138
|
+
memory: options.memory,
|
|
139
|
+
workspace: options.workspace,
|
|
140
|
+
notify: true,
|
|
141
|
+
schedule: true,
|
|
142
|
+
});
|
|
228
143
|
},
|
|
229
144
|
|
|
230
145
|
async invokeModel(
|
|
231
146
|
request: NormalizedModelRequest,
|
|
232
|
-
): Promise<
|
|
147
|
+
): Promise<IsolateModelInvocationV1> {
|
|
233
148
|
if (request.requestId.length > MAX_ISOLATE_REQUEST_ID) {
|
|
234
149
|
throw new Error("isolate model request requestId is not bounded");
|
|
235
150
|
}
|
|
236
|
-
const binding = options.modelBinding;
|
|
237
|
-
|
|
238
|
-
options.capabilities,
|
|
239
|
-
binding,
|
|
240
|
-
request,
|
|
241
|
-
);
|
|
242
|
-
const unavailableBinding = options.unavailableModelBinding;
|
|
243
|
-
if (
|
|
244
|
-
unavailableBinding &&
|
|
245
|
-
request.model === unavailableBinding.providerModelId &&
|
|
246
|
-
(unavailableBinding.provider === undefined ||
|
|
247
|
-
request.provider === unavailableBinding.provider)
|
|
248
|
-
) {
|
|
249
|
-
return {
|
|
250
|
-
status: "unavailable",
|
|
251
|
-
reason: ISOLATE_MODEL_UNAVAILABLE_MESSAGE,
|
|
252
|
-
} satisfies IsolateCapabilityFailureV1;
|
|
253
|
-
}
|
|
254
|
-
if (capability && binding && !options.modelPath) {
|
|
151
|
+
const binding = matchingModelBindingV1(options.modelBinding, request);
|
|
152
|
+
if (!binding || !options.modelPath) {
|
|
255
153
|
return {
|
|
256
154
|
status: "unavailable",
|
|
257
|
-
reason:
|
|
258
|
-
|
|
155
|
+
reason: options.modelBinding
|
|
156
|
+
? "the request does not match this Bot's configured model"
|
|
157
|
+
: "this Bot has no configured model",
|
|
158
|
+
};
|
|
259
159
|
}
|
|
260
|
-
if (!capability || !binding || !options.modelPath) {
|
|
261
|
-
return await recordDecision(
|
|
262
|
-
`models:${request.provider}:${request.model}`,
|
|
263
|
-
`Bot Package "${options.packageId}" asked to invoke a model with no matching enabled Capability`,
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
// The binding the provider path receives is the authority's, never the
|
|
267
|
-
// Bot's: a Bot-composed request carries no Connection authority.
|
|
268
160
|
const forwarded: NormalizedModelRequest = {
|
|
269
161
|
...structuredClone(request),
|
|
270
162
|
modelBinding: {
|
|
271
163
|
connectionId: binding.connectionId,
|
|
272
|
-
|
|
273
|
-
? { connectionGeneration: binding.connectionGeneration }
|
|
274
|
-
: {}),
|
|
164
|
+
connectionGeneration: binding.connectionGeneration,
|
|
275
165
|
...(binding.catalogGeneration
|
|
276
166
|
? { catalogGeneration: binding.catalogGeneration }
|
|
277
167
|
: {}),
|
|
278
168
|
},
|
|
279
169
|
};
|
|
280
|
-
// Record the exact normalized request before forwarding; the provider
|
|
281
|
-
// path takes the credential lease on the way through. The record is
|
|
282
|
-
// keyed by an id this authority mints, so a Bot cannot overwrite one of
|
|
283
|
-
// its own earlier records by reusing a `requestId`.
|
|
284
170
|
const recordId = `model-request-${newId()}`;
|
|
285
171
|
const record: IsolateModelRequestRecordV1 = {
|
|
286
172
|
schemaVersion: 1,
|
|
@@ -289,7 +175,6 @@ export function createIsolateCapabilityHost(
|
|
|
289
175
|
botId: options.botId,
|
|
290
176
|
packageId: options.packageId,
|
|
291
177
|
generationId: options.generationId,
|
|
292
|
-
capabilityId: capability.capabilityId,
|
|
293
178
|
request: forwarded,
|
|
294
179
|
recordedAt: now().toISOString(),
|
|
295
180
|
};
|
|
@@ -298,22 +183,16 @@ export function createIsolateCapabilityHost(
|
|
|
298
183
|
record,
|
|
299
184
|
);
|
|
300
185
|
const controller = new AbortController();
|
|
301
|
-
const events = options.modelPath.stream(forwarded, controller.signal);
|
|
302
186
|
return {
|
|
303
187
|
status: "streaming",
|
|
304
188
|
requestId: request.requestId,
|
|
305
|
-
events: isolateModelEventStreamV1(
|
|
189
|
+
events: isolateModelEventStreamV1(
|
|
190
|
+
options.modelPath.stream(forwarded, controller.signal),
|
|
191
|
+
controller,
|
|
192
|
+
),
|
|
306
193
|
};
|
|
307
194
|
},
|
|
308
195
|
|
|
309
|
-
async pendingDecisions(): Promise<IsolatePendingAuthorityDecisionV1[]> {
|
|
310
|
-
const stored =
|
|
311
|
-
await options.storage.list<IsolatePendingAuthorityDecisionV1>({
|
|
312
|
-
prefix: ISOLATE_DECISION_PREFIX,
|
|
313
|
-
});
|
|
314
|
-
return [...stored.values()];
|
|
315
|
-
},
|
|
316
|
-
|
|
317
196
|
async recordedModelRequests(): Promise<IsolateModelRequestRecordV1[]> {
|
|
318
197
|
const stored = await options.storage.list<IsolateModelRequestRecordV1>({
|
|
319
198
|
prefix: ISOLATE_MODEL_REQUEST_PREFIX,
|
|
@@ -366,51 +245,37 @@ export function isolateModelEventStreamV1(
|
|
|
366
245
|
}
|
|
367
246
|
|
|
368
247
|
/**
|
|
369
|
-
* The content address of
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* composition; ADR 0019).
|
|
248
|
+
* The content address of the bindings baked into a loaded isolate's env.
|
|
249
|
+
*
|
|
250
|
+
* Connection order is irrelevant; ids and generations are the authority
|
|
251
|
+
* identity. User, Bot, the resolved model binding, and the pinned Composition
|
|
252
|
+
* generation complete the digest. Package id is deliberately absent: two
|
|
253
|
+
* Packages of one Bot receive the same authority projection.
|
|
376
254
|
*/
|
|
377
255
|
export async function isolateBindingDigestV1(input: {
|
|
378
256
|
userId: string;
|
|
379
257
|
botId: string;
|
|
380
|
-
|
|
381
|
-
|
|
258
|
+
connections: readonly Pick<
|
|
259
|
+
IsolateConnectionV1,
|
|
260
|
+
"connectionId" | "generation"
|
|
261
|
+
>[];
|
|
262
|
+
model?: IsolateModelBindingV1;
|
|
263
|
+
compositionGenerationId: string;
|
|
382
264
|
}): Promise<string> {
|
|
383
|
-
const
|
|
384
|
-
.map((
|
|
385
|
-
|
|
386
|
-
capabilityId: capability.capabilityId,
|
|
387
|
-
kind: capability.kind,
|
|
388
|
-
connectionId: capability.connectionId ?? null,
|
|
389
|
-
}))
|
|
390
|
-
.sort(
|
|
391
|
-
(left, right) =>
|
|
392
|
-
compareIsolateIdentifierV1(left.packageId, right.packageId) ||
|
|
393
|
-
compareIsolateIdentifierV1(left.capabilityId, right.capabilityId) ||
|
|
394
|
-
compareIsolateIdentifierV1(left.kind, right.kind) ||
|
|
395
|
-
compareIsolateIdentifierV1(
|
|
396
|
-
left.connectionId ?? "",
|
|
397
|
-
right.connectionId ?? "",
|
|
398
|
-
),
|
|
399
|
-
);
|
|
265
|
+
const connections = [...input.connections]
|
|
266
|
+
.map(({ connectionId, generation }) => ({ connectionId, generation }))
|
|
267
|
+
.sort((left, right) => left.connectionId.localeCompare(right.connectionId));
|
|
400
268
|
return await sha256Hex(
|
|
401
269
|
JSON.stringify({
|
|
402
270
|
userId: input.userId,
|
|
403
271
|
botId: input.botId,
|
|
404
|
-
|
|
405
|
-
|
|
272
|
+
compositionGenerationId: input.compositionGenerationId,
|
|
273
|
+
connections,
|
|
274
|
+
model: input.model ?? null,
|
|
406
275
|
}),
|
|
407
276
|
);
|
|
408
277
|
}
|
|
409
278
|
|
|
410
|
-
function compareIsolateIdentifierV1(left: string, right: string): number {
|
|
411
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
279
|
async function sha256Hex(value: string): Promise<string> {
|
|
415
280
|
const digest = await crypto.subtle.digest(
|
|
416
281
|
"SHA-256",
|