@workbench-ai/workbench 0.0.49 → 0.0.51

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.
@@ -1,4 +1,4 @@
1
- import { type SurfaceSnapshotFile } from "@workbench-ai/workbench-core";
1
+ import { type RunSummary, type SurfaceSnapshotFile } from "@workbench-ai/workbench-core";
2
2
  import { type LocalProjectSource } from "./project-source.js";
3
3
  export interface LocalWorkbenchDevServer {
4
4
  url: string;
@@ -20,30 +20,17 @@ export declare function localBenchmarkSnapshot(context: LocalWorkbenchRequestCon
20
20
  workspaceRoot: string;
21
21
  activeId: string | null;
22
22
  currentBenchmarkFingerprint: string | null;
23
- summaries: {
24
- id: string;
25
- name?: string;
26
- ordinal: number;
27
- benchmarkFingerprint: string;
28
- subjectFingerprint: string;
29
- ownerUserId?: string;
30
- ownerUsername?: string;
31
- visibility?: "private" | "public";
32
- createdAt: string;
33
- baseId?: string;
34
- referenceIds: string[];
35
- status: import("@workbench-ai/workbench-contract").SubjectStatus;
36
- fileChanges: string[];
37
- metrics?: Record<string, number>;
38
- usage?: import("@workbench-ai/workbench-contract").UsageSummary;
39
- }[];
23
+ summaries: import("@workbench-ai/workbench-contract").CandidateSummary[];
40
24
  evaluations: {
41
25
  id: string;
42
26
  runId: string;
43
27
  benchmarkFingerprint: string;
44
- subjectFingerprint: string;
45
- subjectId: string;
46
- subjectName?: string;
28
+ candidateFingerprint: string;
29
+ candidateId: string;
30
+ candidateName?: string;
31
+ candidateVersion: number;
32
+ candidateRunId?: string;
33
+ candidateRunName?: string;
47
34
  createdAt: string;
48
35
  updatedAt: string;
49
36
  status: import("@workbench-ai/workbench-contract").EvaluationStatus;
@@ -51,11 +38,14 @@ export declare function localBenchmarkSnapshot(context: LocalWorkbenchRequestCon
51
38
  completedSampleCount: number;
52
39
  errorSampleCount: number;
53
40
  metrics?: Record<string, import("@workbench-ai/workbench-contract").MetricStats>;
41
+ selectionMetric?: string;
42
+ selectionLabel?: string;
43
+ selectionScore?: import("@workbench-ai/workbench-contract").MetricStats;
54
44
  durationMs?: import("@workbench-ai/workbench-contract").MetricStats;
55
45
  usage?: import("@workbench-ai/workbench-contract").EvaluationUsageStats;
56
46
  error?: string;
57
47
  }[];
58
- runs: import("@workbench-ai/workbench-contract").RunSummary[];
48
+ runs: RunSummary[];
59
49
  }>;
60
50
  export declare function localSpecDocument(context: LocalWorkbenchRequestContext, benchmarkFingerprint?: string | null): Promise<import("@workbench-ai/workbench-contract").AuthoredWorkbenchSourceDocument>;
61
51
  export declare function localSourceFiles(workspace: string): Promise<SurfaceSnapshotFile[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"dev-open-server.d.ts","sourceRoot":"","sources":["../src/dev-open-server.ts"],"names":[],"mappings":"AAKA,OAAO,EAUL,KAAK,mBAAmB,EAIzB,MAAM,8BAA8B,CAAC;AAatC,OAAO,EAGL,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAkBD,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACtD;AAKD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,uBAAuB,CAAC,CAwClC;AAoOD,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBjF;AAUD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,4BAA4B,EACrC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,uFAiCrC;AAwBD,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAExF;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,4BAA4B,EACrC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,GACnC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAchC"}
1
+ {"version":3,"file":"dev-open-server.d.ts","sourceRoot":"","sources":["../src/dev-open-server.ts"],"names":[],"mappings":"AAKA,OAAO,EAYL,KAAK,UAAU,EACf,KAAK,mBAAmB,EAIzB,MAAM,8BAA8B,CAAC;AAatC,OAAO,EAGL,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAkBD,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACtD;AAKD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,uBAAuB,CAAC,CAwClC;AAoQD,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBjF;AAeD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,4BAA4B,EACrC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,uFAiCrC;AAwBD,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAExF;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,4BAA4B,EACrC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,GACnC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAchC"}
@@ -2,8 +2,8 @@ import { promises as fs } from "node:fs";
2
2
  import http from "node:http";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { buildSubjectCaseExecutionRefs, buildWorkbenchExecutionEvidence, createSubjectFilePreview, createCaseReview, loadAuthoredWorkbenchSourceDocument, summarizeSubjectFiles, traceSessionLabel, } from "@workbench-ai/workbench-core";
6
- import { readLocalExecutionFiles, loadLocalArchiveIndex, readLocalEvaluationRecord, readLocalJobInRun, readLocalRunJobs, readLocalSubjectFilesForId, readLocalSubjectRecord, } from "./local-archive.js";
5
+ import { buildCandidateCaseExecutionRefs, buildWorkbenchExecutionEvidence, candidateRecordWithoutDerivedFields, candidateSummaryFromRecord, createCandidateFilePreview, createCaseReview, loadAuthoredWorkbenchSourceDocument, summarizeCandidateFiles, traceSessionLabel, } from "@workbench-ai/workbench-core";
6
+ import { readLocalExecutionFiles, loadLocalArchiveIndex, readLocalEvaluationRecord, readLocalJobInRun, readLocalRunJobs, readLocalCandidateFilesForId, readLocalCandidateRecord, } from "./local-archive.js";
7
7
  import { readLocalAuthoredProjectSource, readLocalProjectSource, WORKBENCH_BENCHMARK_FILE, } from "./project-source.js";
8
8
  import { localBenchmarkFingerprint } from "./benchmark-fingerprint.js";
9
9
  class LocalApiError extends Error {
@@ -108,7 +108,34 @@ async function handleLocalWorkbenchRequest(args) {
108
108
  await sendFontFile(args.response, args.context.assetsRoot, url, args.request.method);
109
109
  return;
110
110
  }
111
- await sendHtml(args.response, args.request.method);
111
+ if (url.pathname.startsWith("/assets/")) {
112
+ throw new LocalApiError("Workbench local asset not found.", 404);
113
+ }
114
+ await sendHtml(args.response, args.request.method, isKnownWorkbenchDocumentPath(url.pathname) ? 200 : 404);
115
+ }
116
+ function isKnownWorkbenchDocumentPath(pathname) {
117
+ const segments = pathname.split("/").filter(Boolean).map(decodeDocumentPathSegment);
118
+ if (segments.length === 0) {
119
+ return true;
120
+ }
121
+ if (segments[0] === "evaluations") {
122
+ return segments.length === 1;
123
+ }
124
+ if (segments[0] !== "candidates") {
125
+ return false;
126
+ }
127
+ if (segments.length === 1 || segments.length === 2) {
128
+ return true;
129
+ }
130
+ return segments.length === 3 && (segments[2] === "files" || segments[2] === "manifest");
131
+ }
132
+ function decodeDocumentPathSegment(segment) {
133
+ try {
134
+ return decodeURIComponent(segment);
135
+ }
136
+ catch {
137
+ return segment;
138
+ }
112
139
  }
113
140
  async function handleApiRequest(request, response, context, url) {
114
141
  const { workspace } = context;
@@ -120,45 +147,45 @@ async function handleApiRequest(request, response, context, url) {
120
147
  sendJson(response, await localSpecDocument(context, readOptionalSearchString(url.searchParams, "fingerprint")), 200, request.method);
121
148
  return;
122
149
  case "/api/source/files":
123
- sendJson(response, summarizeSubjectFiles(await localBenchmarkMountedFiles(context, readOptionalSearchString(url.searchParams, "fingerprint")), []), 200, request.method);
150
+ sendJson(response, summarizeCandidateFiles(await localBenchmarkMountedFiles(context, readOptionalSearchString(url.searchParams, "fingerprint")), []), 200, request.method);
124
151
  return;
125
152
  case "/api/source/preview":
126
- sendJson(response, createSubjectFilePreview({
153
+ sendJson(response, createCandidateFilePreview({
127
154
  files: await localBenchmarkMountedFiles(context, readOptionalSearchString(url.searchParams, "fingerprint")),
128
155
  path: readSearchString(url.searchParams, "path"),
129
156
  view: readPreviewMode(url.searchParams),
130
157
  }), 200, request.method);
131
158
  return;
132
159
  case "/api/record":
133
- sendJson(response, await readSubjectForApi(workspace, readSearchString(url.searchParams, "id")), 200, request.method);
160
+ sendJson(response, await readCandidateForApi(workspace, readSearchString(url.searchParams, "id")), 200, request.method);
134
161
  return;
135
162
  case "/api/evaluation":
136
163
  sendJson(response, await readEvaluationForApi(workspace, readSearchString(url.searchParams, "id")), 200, request.method);
137
164
  return;
138
- case "/api/subject/files": {
139
- const subjectId = readSearchString(url.searchParams, "id");
140
- const subject = await readSubjectForApi(workspace, subjectId);
141
- sendJson(response, summarizeSubjectFiles(await readSubjectFilesForApi(workspace, subjectId), subject.fileChanges), 200, request.method);
165
+ case "/api/candidate/files": {
166
+ const candidateId = readSearchString(url.searchParams, "id");
167
+ const candidate = await readCandidateForApi(workspace, candidateId);
168
+ sendJson(response, summarizeCandidateFiles(await readCandidateFilesForApi(workspace, candidateId), candidate.fileChanges), 200, request.method);
142
169
  return;
143
170
  }
144
- case "/api/subject/preview": {
145
- const subjectId = readSearchString(url.searchParams, "id");
146
- sendJson(response, createSubjectFilePreview({
147
- files: await readSubjectFilesForApi(workspace, subjectId),
171
+ case "/api/candidate/preview": {
172
+ const candidateId = readSearchString(url.searchParams, "id");
173
+ sendJson(response, createCandidateFilePreview({
174
+ files: await readCandidateFilesForApi(workspace, candidateId),
148
175
  path: readSearchString(url.searchParams, "path"),
149
176
  view: readPreviewMode(url.searchParams),
150
177
  }), 200, request.method);
151
178
  return;
152
179
  }
153
180
  case "/api/case-review": {
154
- const subjectId = readSearchString(url.searchParams, "id");
181
+ const candidateId = readSearchString(url.searchParams, "id");
155
182
  const caseId = readSearchString(url.searchParams, "case");
156
183
  const runId = readSearchString(url.searchParams, "run");
157
184
  const jobs = await readLocalRunJobs(workspace, runId);
158
185
  sendJson(response, createCaseReview({
159
- subject: await readSubjectForApi(workspace, subjectId),
186
+ candidate: await readCandidateForApi(workspace, candidateId),
160
187
  caseId,
161
- executions: buildSubjectCaseExecutionRefs({ jobs, subjectId, caseId }),
188
+ executions: buildCandidateCaseExecutionRefs({ jobs, candidateId, caseId }),
162
189
  }), 200, request.method);
163
190
  return;
164
191
  }
@@ -190,7 +217,7 @@ async function handleApiRequest(request, response, context, url) {
190
217
  const previewJobId = readSearchString(url.searchParams, "id");
191
218
  const previewFilePath = readSearchString(url.searchParams, "path");
192
219
  const previewFiles = await readExecutionFilesForRun(workspace, previewRunId, previewJobId);
193
- sendJson(response, createSubjectFilePreview({
220
+ sendJson(response, createCandidateFilePreview({
194
221
  files: previewFiles,
195
222
  path: previewFilePath,
196
223
  view: readPreviewMode(url.searchParams),
@@ -204,11 +231,11 @@ async function handleApiRequest(request, response, context, url) {
204
231
  export async function localBenchmarkSnapshot(context) {
205
232
  const { workspace } = context;
206
233
  const snapshot = await loadLocalArchiveIndex(workspace);
207
- const subjects = snapshot.subjects.filter(isInspectableSubjectRecord);
208
- const summaries = subjects.map(subjectSummary);
209
- const activeId = snapshot.activeId && subjects.some((subject) => subject.id === snapshot.activeId)
234
+ const candidates = snapshot.candidates.filter(isInspectableCandidateRecord);
235
+ const summaries = candidates.map(candidateSummary);
236
+ const activeId = snapshot.activeId && candidates.some((candidate) => candidate.id === snapshot.activeId)
210
237
  ? snapshot.activeId
211
- : subjects.at(-1)?.id ?? null;
238
+ : candidates.at(-1)?.id ?? null;
212
239
  const currentBenchmarkFingerprint = await readCurrentBenchmarkFingerprint(context);
213
240
  return {
214
241
  workspaceRoot: path.resolve(workspace),
@@ -216,9 +243,13 @@ export async function localBenchmarkSnapshot(context) {
216
243
  currentBenchmarkFingerprint,
217
244
  summaries,
218
245
  evaluations: snapshot.evaluations.map(evaluationSummary),
219
- runs: snapshot.runs,
246
+ runs: snapshot.runs.map(publicLocalRunSummary),
220
247
  };
221
248
  }
249
+ function publicLocalRunSummary(run) {
250
+ const { executionFingerprint: _executionFingerprint, ...summary } = run;
251
+ return summary;
252
+ }
222
253
  async function readCurrentBenchmarkFingerprint(context) {
223
254
  return await context.readProjectSource()
224
255
  .then(localBenchmarkFingerprint)
@@ -290,8 +321,8 @@ export async function localBenchmarkMountedFiles(context, benchmarkFingerprint)
290
321
  return inspectableEngineCaseFiles(projectSource.engineCases);
291
322
  }
292
323
  function localHistoricalBenchmarkDocument(snapshot, benchmarkFingerprint) {
293
- const subject = snapshot.subjects.find((entry) => entry.benchmarkFingerprint === benchmarkFingerprint && readBenchmarkSourceMetadata(entry));
294
- const source = subject ? readBenchmarkSourceMetadata(subject) : null;
324
+ const candidate = snapshot.candidates.find((entry) => entry.benchmarkFingerprint === benchmarkFingerprint && readBenchmarkSourceMetadata(entry));
325
+ const source = candidate ? readBenchmarkSourceMetadata(candidate) : null;
295
326
  if (!source?.sourceYaml) {
296
327
  return null;
297
328
  }
@@ -317,28 +348,28 @@ function engineCaseFiles(bundle) {
317
348
  ? buckets.source
318
349
  : [...(buckets.public ?? []), ...(buckets.private ?? [])];
319
350
  }
320
- function subjectSummary(subject) {
321
- const { eval: _eval, prompt: _prompt, meta: _meta, ...summary } = subject;
322
- return summary;
351
+ function candidateSummary(candidate) {
352
+ return candidateSummaryFromRecord(candidate);
323
353
  }
324
- function isInspectableSubjectRecord(subject) {
325
- return Boolean(subject.eval || asRecord(asRecord(subject.meta)?.source));
354
+ function isInspectableCandidateRecord(candidate) {
355
+ return Boolean(candidate.eval || asRecord(asRecord(candidate.meta)?.source));
326
356
  }
327
357
  function evaluationSummary(evaluation) {
328
358
  const { evaluation: _evaluation, ...summary } = evaluation;
329
359
  return summary;
330
360
  }
331
- async function readSubjectForApi(workspace, subjectId) {
332
- return await readArchiveRecord("Subject", subjectId, () => readLocalSubjectRecord(workspace, subjectId));
361
+ async function readCandidateForApi(workspace, candidateId) {
362
+ const candidate = await readArchiveRecord("Candidate", candidateId, () => readLocalCandidateRecord(workspace, candidateId));
363
+ return candidateRecordWithoutDerivedFields(candidate);
333
364
  }
334
365
  async function readEvaluationForApi(workspace, evaluationId) {
335
366
  return await readArchiveRecord("Evaluation", evaluationId, () => readLocalEvaluationRecord(workspace, evaluationId));
336
367
  }
337
- async function readSubjectFilesForApi(workspace, subjectId) {
338
- return await readArchiveRecord("Subject", subjectId, () => readLocalSubjectFilesForId(workspace, subjectId));
368
+ async function readCandidateFilesForApi(workspace, candidateId) {
369
+ return await readArchiveRecord("Candidate", candidateId, () => readLocalCandidateFilesForId(workspace, candidateId));
339
370
  }
340
- function readBenchmarkSourceMetadata(subject) {
341
- const benchmark = asRecord(asRecord(subject.meta)?.benchmark);
371
+ function readBenchmarkSourceMetadata(candidate) {
372
+ const benchmark = asRecord(asRecord(candidate.meta)?.benchmark);
342
373
  const files = Array.isArray(benchmark?.files)
343
374
  ? benchmark.files
344
375
  .map(readSurfaceSnapshotFile)
@@ -390,7 +421,7 @@ async function readArchiveRecord(kind, id, read) {
390
421
  }
391
422
  async function loadExecutionFiles(workspace, runId, jobId) {
392
423
  const files = await readExecutionFilesForRun(workspace, runId, jobId);
393
- return summarizeSubjectFiles(files);
424
+ return summarizeCandidateFiles(files);
394
425
  }
395
426
  async function readExecutionFilesForRun(workspace, runId, jobId) {
396
427
  await assertExecutionJobInRun(workspace, runId, jobId);
@@ -430,7 +461,7 @@ function readLocalTraceSessions(job, role) {
430
461
  jobId: typeof session.jobId === "string" && session.jobId.length > 0
431
462
  ? session.jobId
432
463
  : job.id,
433
- role: session.role === "optimizer" || session.role === "runner" || session.role === "engine"
464
+ role: session.role === "improver" || session.role === "runner" || session.role === "engine"
434
465
  ? session.role
435
466
  : role,
436
467
  kind: typeof session.kind === "string" && session.kind.length > 0 ? session.kind : "trace",
@@ -443,7 +474,7 @@ function readLocalTraceSessions(job, role) {
443
474
  }));
444
475
  }
445
476
  function traceSessionDisplayLabel(session, fallbackRole) {
446
- const role = session.role === "optimizer" || session.role === "runner" || session.role === "engine"
477
+ const role = session.role === "improver" || session.role === "runner" || session.role === "engine"
447
478
  ? session.role
448
479
  : fallbackRole;
449
480
  return typeof session.label === "string" && session.label.length > 0
@@ -483,7 +514,16 @@ function readPreviewMode(params) {
483
514
  throw new LocalApiError("view must be diff, raw, or rendered.");
484
515
  }
485
516
  async function sendFile(response, filePath, contentType, method = "GET") {
486
- const body = await fs.readFile(filePath);
517
+ let body;
518
+ try {
519
+ body = await fs.readFile(filePath);
520
+ }
521
+ catch (error) {
522
+ if (error.code === "ENOENT") {
523
+ throw new LocalApiError("Workbench local asset not found.", 404);
524
+ }
525
+ throw error;
526
+ }
487
527
  response.writeHead(200, {
488
528
  "content-type": contentType,
489
529
  "content-length": body.byteLength,
@@ -504,7 +544,7 @@ async function sendFontFile(response, assetsRoot, url, method = "GET") {
504
544
  }
505
545
  await sendFile(response, path.join(assetsRoot, "fonts", fileName), "font/woff2", method);
506
546
  }
507
- async function sendHtml(response, method = "GET") {
547
+ async function sendHtml(response, method = "GET", status = 200) {
508
548
  const body = `<!doctype html>
509
549
  <html lang="en">
510
550
  <head>
@@ -518,7 +558,7 @@ async function sendHtml(response, method = "GET") {
518
558
  <script type="module" src="/assets/client.js"></script>
519
559
  </body>
520
560
  </html>`;
521
- response.writeHead(200, {
561
+ response.writeHead(status, {
522
562
  "content-type": "text/html; charset=utf-8",
523
563
  "content-length": Buffer.byteLength(body),
524
564
  "cache-control": "no-store",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsHA,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AA6BD,UAAU,iBAAiB;CAAG;AAqK9B,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,GAAE,KAIH,EACD,cAAc,GAAE,iBAAsB,GACrC,OAAO,CAAC,MAAM,CAAC,CA8GjB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2IA,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AA4BD,UAAU,iBAAiB;CAAG;AAuK9B,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,GAAE,KAIH,EACD,cAAc,GAAE,iBAAsB,GACrC,OAAO,CAAC,MAAM,CAAC,CAmHjB"}