@bolt-foundry/gambit 0.8.1 → 0.8.3

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +78 -2
  2. package/README.md +31 -9
  3. package/esm/gambit/simulator-ui/dist/bundle.js +4744 -4360
  4. package/esm/gambit/simulator-ui/dist/bundle.js.map +4 -4
  5. package/esm/gambit/simulator-ui/dist/favicon.ico +0 -0
  6. package/esm/mod.d.ts +8 -4
  7. package/esm/mod.d.ts.map +1 -1
  8. package/esm/mod.js +6 -2
  9. package/esm/src/cli_utils.d.ts.map +1 -1
  10. package/esm/src/cli_utils.js +39 -3
  11. package/esm/src/openai_compat.d.ts +63 -0
  12. package/esm/src/openai_compat.d.ts.map +1 -0
  13. package/esm/src/openai_compat.js +277 -0
  14. package/esm/src/providers/google.d.ts +16 -0
  15. package/esm/src/providers/google.d.ts.map +1 -0
  16. package/esm/src/providers/google.js +352 -0
  17. package/esm/src/providers/ollama.d.ts +17 -0
  18. package/esm/src/providers/ollama.d.ts.map +1 -0
  19. package/esm/src/providers/ollama.js +509 -0
  20. package/esm/src/providers/openrouter.d.ts +22 -0
  21. package/esm/src/providers/openrouter.d.ts.map +1 -0
  22. package/esm/src/providers/openrouter.js +592 -0
  23. package/esm/src/server.d.ts +2 -0
  24. package/esm/src/server.d.ts.map +1 -1
  25. package/esm/src/server.js +612 -29
  26. package/esm/src/trace.d.ts.map +1 -1
  27. package/esm/src/trace.js +2 -2
  28. package/package.json +3 -2
  29. package/script/gambit/simulator-ui/dist/bundle.js +4744 -4360
  30. package/script/gambit/simulator-ui/dist/bundle.js.map +4 -4
  31. package/script/gambit/simulator-ui/dist/favicon.ico +0 -0
  32. package/script/mod.d.ts +8 -4
  33. package/script/mod.d.ts.map +1 -1
  34. package/script/mod.js +13 -7
  35. package/script/src/cli_utils.d.ts.map +1 -1
  36. package/script/src/cli_utils.js +38 -2
  37. package/script/src/openai_compat.d.ts +63 -0
  38. package/script/src/openai_compat.d.ts.map +1 -0
  39. package/script/src/openai_compat.js +281 -0
  40. package/script/src/providers/google.d.ts +16 -0
  41. package/script/src/providers/google.d.ts.map +1 -0
  42. package/script/src/providers/google.js +359 -0
  43. package/script/src/providers/ollama.d.ts +17 -0
  44. package/script/src/providers/ollama.d.ts.map +1 -0
  45. package/script/src/providers/ollama.js +551 -0
  46. package/script/src/providers/openrouter.d.ts +22 -0
  47. package/script/src/providers/openrouter.d.ts.map +1 -0
  48. package/script/src/providers/openrouter.js +632 -0
  49. package/script/src/server.d.ts +2 -0
  50. package/script/src/server.d.ts.map +1 -1
  51. package/script/src/server.js +612 -29
  52. package/script/src/trace.d.ts.map +1 -1
  53. package/script/src/trace.js +2 -2
@@ -0,0 +1,551 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.DEFAULT_OLLAMA_BASE_URL = exports.OLLAMA_PREFIX = void 0;
40
+ exports.fetchOllamaTags = fetchOllamaTags;
41
+ exports.ensureOllamaModel = ensureOllamaModel;
42
+ exports.createOllamaProvider = createOllamaProvider;
43
+ const dntShim = __importStar(require("../../_dnt.shims.js"));
44
+ const openai_1 = __importDefault(require("openai"));
45
+ const gambit_core_1 = require("@bolt-foundry/gambit-core");
46
+ const logger = console;
47
+ exports.OLLAMA_PREFIX = "ollama/";
48
+ exports.DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434/v1";
49
+ function buildOllamaApiBase(baseURL) {
50
+ const url = new URL(baseURL ?? exports.DEFAULT_OLLAMA_BASE_URL);
51
+ url.pathname = url.pathname.replace(/\/v1\/?$/, "/");
52
+ if (!url.pathname.endsWith("/")) {
53
+ url.pathname += "/";
54
+ }
55
+ return url;
56
+ }
57
+ async function fetchOllamaTags(baseURL) {
58
+ const apiBase = buildOllamaApiBase(baseURL);
59
+ const tagsUrl = new URL("api/tags", apiBase);
60
+ const tagsResponse = await fetch(tagsUrl);
61
+ if (!tagsResponse.ok) {
62
+ throw new Error(`Failed to list Ollama models (${tagsResponse.status} ${tagsResponse.statusText}).`);
63
+ }
64
+ const tags = (await tagsResponse.json());
65
+ const models = tags.models ?? [];
66
+ return new Set(models
67
+ .map((entry) => entry.name?.trim())
68
+ .filter((name) => Boolean(name)));
69
+ }
70
+ async function ensureOllamaModel(model, baseURL) {
71
+ const tags = await fetchOllamaTags(baseURL);
72
+ if (tags.has(model)) {
73
+ return;
74
+ }
75
+ logger.log(`Ollama model "${model}" not found; pulling from Ollama...`);
76
+ const apiBase = buildOllamaApiBase(baseURL);
77
+ const pullUrl = new URL("api/pull", apiBase);
78
+ const pullResponse = await fetch(pullUrl, {
79
+ method: "POST",
80
+ headers: { "Content-Type": "application/json" },
81
+ body: JSON.stringify({ name: model }),
82
+ });
83
+ if (!pullResponse.ok || !pullResponse.body) {
84
+ throw new Error(`Failed to pull Ollama model "${model}" (${pullResponse.status} ${pullResponse.statusText}).`);
85
+ }
86
+ const decoder = new TextDecoder();
87
+ const reader = pullResponse.body.getReader();
88
+ let buffer = "";
89
+ while (true) {
90
+ const { value, done } = await reader.read();
91
+ if (done)
92
+ break;
93
+ buffer += decoder.decode(value, { stream: true });
94
+ const lines = buffer.split("\n");
95
+ buffer = lines.pop() ?? "";
96
+ for (const line of lines) {
97
+ if (!line.trim())
98
+ continue;
99
+ try {
100
+ const event = JSON.parse(line);
101
+ if (event.error) {
102
+ throw new Error(event.error);
103
+ }
104
+ if (event.status) {
105
+ logger.log(`[ollama] ${event.status}`);
106
+ }
107
+ }
108
+ catch (err) {
109
+ throw new Error(`Failed to parse Ollama pull response: ${err.message}`);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ function safeJson(input) {
115
+ try {
116
+ const parsed = JSON.parse(input);
117
+ if (parsed && typeof parsed === "object") {
118
+ return parsed;
119
+ }
120
+ }
121
+ catch {
122
+ // fall through
123
+ }
124
+ return {};
125
+ }
126
+ function isAsyncIterable(value) {
127
+ return Boolean(value &&
128
+ typeof value === "object" &&
129
+ Symbol.asyncIterator in value);
130
+ }
131
+ function mapUsage(usage) {
132
+ if (!usage)
133
+ return undefined;
134
+ return {
135
+ promptTokens: usage.input_tokens ?? 0,
136
+ completionTokens: usage.output_tokens ?? 0,
137
+ totalTokens: usage.total_tokens ?? 0,
138
+ };
139
+ }
140
+ function mapStatus(status) {
141
+ if (!status)
142
+ return undefined;
143
+ if (status === "completed")
144
+ return "completed";
145
+ if (status === "in_progress" || status === "queued")
146
+ return "in_progress";
147
+ return "failed";
148
+ }
149
+ function mapError(error) {
150
+ if (!error)
151
+ return undefined;
152
+ return { code: error.code, message: error.message };
153
+ }
154
+ function mapTools(tools) {
155
+ if (!tools || tools.length === 0)
156
+ return undefined;
157
+ return tools.map((tool) => ({
158
+ type: "function",
159
+ name: tool.function.name,
160
+ description: tool.function.description ?? null,
161
+ parameters: normalizeToolParameters(tool.function.parameters),
162
+ strict: false,
163
+ }));
164
+ }
165
+ function normalizeToolParameters(parameters) {
166
+ const normalized = structuredClone(parameters ?? {});
167
+ if (normalized.type !== "object") {
168
+ return normalized;
169
+ }
170
+ if (normalized.properties === undefined) {
171
+ normalized.properties = {};
172
+ }
173
+ const props = normalized.properties;
174
+ if (props && typeof props === "object" && !Array.isArray(props)) {
175
+ const requiredKeys = Array.isArray(normalized.required)
176
+ ? normalized.required.filter((key) => typeof key === "string" && key in props)
177
+ : [];
178
+ for (const [key, value] of Object.entries(props)) {
179
+ if (!value || typeof value !== "object" || Array.isArray(value))
180
+ continue;
181
+ if (!("type" in value))
182
+ continue;
183
+ if (value.type === "object" && value.additionalProperties !== false) {
184
+ props[key] = {
185
+ ...value,
186
+ additionalProperties: false,
187
+ };
188
+ }
189
+ }
190
+ if (requiredKeys.length > 0) {
191
+ normalized.required = requiredKeys;
192
+ }
193
+ }
194
+ const additional = normalized.additionalProperties;
195
+ if (additional !== false) {
196
+ normalized.additionalProperties = false;
197
+ }
198
+ return normalized;
199
+ }
200
+ function appendSyntheticTools(tools, input) {
201
+ const needed = new Set();
202
+ for (const item of input) {
203
+ if (item.type !== "function_call")
204
+ continue;
205
+ if (item.name === gambit_core_1.GAMBIT_TOOL_CONTEXT || item.name === gambit_core_1.GAMBIT_TOOL_INIT) {
206
+ needed.add(item.name);
207
+ }
208
+ }
209
+ for (const name of needed) {
210
+ if (tools.some((tool) => tool.name === name))
211
+ continue;
212
+ tools.push({
213
+ type: "function",
214
+ name,
215
+ description: "Synthetic Gambit context payload.",
216
+ parameters: {
217
+ type: "object",
218
+ properties: {},
219
+ additionalProperties: false,
220
+ required: [],
221
+ },
222
+ strict: false,
223
+ });
224
+ }
225
+ }
226
+ function mapToolChoice(toolChoice) {
227
+ if (!toolChoice)
228
+ return undefined;
229
+ if (toolChoice === "auto" || toolChoice === "required")
230
+ return toolChoice;
231
+ return { type: "function", name: toolChoice.function.name };
232
+ }
233
+ function mapOpenAIOutputItem(item) {
234
+ const itemType = item.type;
235
+ if (itemType === "message") {
236
+ const message = item;
237
+ const content = [];
238
+ for (const part of message.content ?? []) {
239
+ if (part.type === "output_text") {
240
+ content.push({ type: "output_text", text: part.text });
241
+ }
242
+ }
243
+ if (content.length === 0)
244
+ return null;
245
+ return {
246
+ type: "message",
247
+ role: "assistant",
248
+ content,
249
+ id: message.id,
250
+ };
251
+ }
252
+ if (itemType === "function_call") {
253
+ const call = item;
254
+ return {
255
+ type: "function_call",
256
+ call_id: call.call_id,
257
+ name: call.name,
258
+ arguments: call.arguments,
259
+ id: call.id,
260
+ };
261
+ }
262
+ return null;
263
+ }
264
+ function normalizeOpenAIResponse(response) {
265
+ const outputItems = (response.output ?? [])
266
+ .map(mapOpenAIOutputItem)
267
+ .filter((item) => Boolean(item));
268
+ return {
269
+ id: response.id,
270
+ object: "response",
271
+ model: response.model,
272
+ created: response.created_at,
273
+ status: mapStatus(response.status ?? undefined),
274
+ output: outputItems,
275
+ usage: mapUsage(response.usage),
276
+ error: mapError(response.error),
277
+ };
278
+ }
279
+ function toOpenAIInputItems(items) {
280
+ const mapped = [];
281
+ for (const item of items) {
282
+ if (item.type === "message") {
283
+ const isAssistant = item.role === "assistant";
284
+ const content = item.content
285
+ .map((part) => {
286
+ if (part.type === "output_text") {
287
+ return {
288
+ type: "output_text",
289
+ text: part.text,
290
+ };
291
+ }
292
+ if (part.type === "input_text") {
293
+ return {
294
+ type: isAssistant ? "output_text" : "input_text",
295
+ text: part.text,
296
+ };
297
+ }
298
+ return null;
299
+ })
300
+ .filter((part) => Boolean(part));
301
+ if (content.length === 0)
302
+ continue;
303
+ mapped.push({
304
+ type: "message",
305
+ role: item.role,
306
+ content,
307
+ id: item.id,
308
+ });
309
+ continue;
310
+ }
311
+ if (item.type === "function_call") {
312
+ mapped.push({
313
+ type: "function_call",
314
+ call_id: item.call_id,
315
+ name: item.name,
316
+ arguments: item.arguments,
317
+ id: item.id,
318
+ });
319
+ continue;
320
+ }
321
+ if (item.type === "function_call_output") {
322
+ mapped.push({
323
+ type: "function_call_output",
324
+ call_id: item.call_id,
325
+ output: item.output,
326
+ id: item.id,
327
+ });
328
+ }
329
+ }
330
+ return mapped;
331
+ }
332
+ function chatMessagesToResponseItems(messages) {
333
+ const items = [];
334
+ for (const message of messages) {
335
+ if (message.role === "tool") {
336
+ if (message.tool_call_id &&
337
+ typeof message.content === "string") {
338
+ items.push({
339
+ type: "function_call_output",
340
+ call_id: message.tool_call_id,
341
+ output: message.content,
342
+ });
343
+ }
344
+ continue;
345
+ }
346
+ if (message.role === "system" || message.role === "user" ||
347
+ message.role === "assistant") {
348
+ const content = [];
349
+ if (typeof message.content === "string" && message.content.length > 0) {
350
+ content.push({
351
+ type: message.role === "assistant" ? "output_text" : "input_text",
352
+ text: message.content,
353
+ });
354
+ }
355
+ if (content.length > 0) {
356
+ items.push({
357
+ type: "message",
358
+ role: message.role,
359
+ content,
360
+ });
361
+ }
362
+ }
363
+ if (message.role === "assistant" && message.tool_calls) {
364
+ for (const call of message.tool_calls) {
365
+ items.push({
366
+ type: "function_call",
367
+ call_id: call.id,
368
+ name: call.function.name,
369
+ arguments: call.function.arguments,
370
+ });
371
+ }
372
+ }
373
+ }
374
+ return items;
375
+ }
376
+ function responseItemsToChat(items) {
377
+ const textParts = [];
378
+ const toolCalls = [];
379
+ const messageToolCalls = [];
380
+ for (const item of items) {
381
+ if (item.type === "message" && item.role === "assistant") {
382
+ for (const part of item.content) {
383
+ if (part.type === "output_text") {
384
+ textParts.push(part.text);
385
+ }
386
+ }
387
+ }
388
+ if (item.type === "function_call") {
389
+ toolCalls.push({
390
+ id: item.call_id,
391
+ name: item.name,
392
+ args: safeJson(item.arguments),
393
+ });
394
+ messageToolCalls.push({
395
+ id: item.call_id,
396
+ type: "function",
397
+ function: { name: item.name, arguments: item.arguments },
398
+ });
399
+ }
400
+ }
401
+ const content = textParts.length > 0 ? textParts.join("") : null;
402
+ const message = {
403
+ role: "assistant",
404
+ content,
405
+ tool_calls: messageToolCalls.length > 0 ? messageToolCalls : undefined,
406
+ };
407
+ return {
408
+ message,
409
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
410
+ };
411
+ }
412
+ async function createResponse(client, request, onStreamEvent) {
413
+ const baseParams = {
414
+ model: request.model,
415
+ input: toOpenAIInputItems(request.input),
416
+ instructions: request.instructions,
417
+ tools: undefined,
418
+ tool_choice: mapToolChoice(request.tool_choice),
419
+ stream: request.stream,
420
+ max_output_tokens: request.max_output_tokens,
421
+ metadata: request.metadata,
422
+ };
423
+ const mappedTools = mapTools(request.tools) ?? [];
424
+ appendSyntheticTools(mappedTools, request.input);
425
+ if (mappedTools.length > 0) {
426
+ baseParams.tools = mappedTools;
427
+ }
428
+ const params = { ...(request.params ?? {}), ...baseParams };
429
+ const debugResponses = dntShim.Deno.env.get("GAMBIT_DEBUG_RESPONSES") === "1";
430
+ let responseOrStream;
431
+ try {
432
+ responseOrStream = await client.responses.create(params);
433
+ }
434
+ catch (err) {
435
+ if (debugResponses) {
436
+ logger.error("[responses-debug] request", params);
437
+ if (err instanceof openai_1.default.APIError) {
438
+ logger.error("[responses-debug] error", err.error);
439
+ }
440
+ else {
441
+ logger.error("[responses-debug] error", err);
442
+ }
443
+ }
444
+ throw err;
445
+ }
446
+ if (request.stream &&
447
+ isAsyncIterable(responseOrStream)) {
448
+ let completed = null;
449
+ for await (const event of responseOrStream) {
450
+ if (!event || typeof event !== "object" || !("type" in event)) {
451
+ continue;
452
+ }
453
+ switch (event.type) {
454
+ case "response.created": {
455
+ const mapped = normalizeOpenAIResponse(event.response);
456
+ onStreamEvent?.({ type: "response.created", response: mapped });
457
+ break;
458
+ }
459
+ case "response.output_text.delta":
460
+ onStreamEvent?.({
461
+ type: "response.output_text.delta",
462
+ output_index: event.output_index,
463
+ delta: event.delta,
464
+ item_id: event.item_id,
465
+ });
466
+ break;
467
+ case "response.output_text.done":
468
+ onStreamEvent?.({
469
+ type: "response.output_text.done",
470
+ output_index: event.output_index,
471
+ text: event.text,
472
+ item_id: event.item_id,
473
+ });
474
+ break;
475
+ case "response.output_item.added": {
476
+ const item = mapOpenAIOutputItem(event.item);
477
+ if (item) {
478
+ onStreamEvent?.({
479
+ type: "response.output_item.added",
480
+ output_index: event.output_index,
481
+ item,
482
+ });
483
+ }
484
+ break;
485
+ }
486
+ case "response.output_item.done": {
487
+ const item = mapOpenAIOutputItem(event.item);
488
+ if (item) {
489
+ onStreamEvent?.({
490
+ type: "response.output_item.done",
491
+ output_index: event.output_index,
492
+ item,
493
+ });
494
+ }
495
+ break;
496
+ }
497
+ case "response.completed": {
498
+ completed = normalizeOpenAIResponse(event.response);
499
+ onStreamEvent?.({ type: "response.completed", response: completed });
500
+ break;
501
+ }
502
+ case "response.failed": {
503
+ const error = mapError(event.response?.error ?? undefined);
504
+ onStreamEvent?.({
505
+ type: "response.failed",
506
+ error: error ?? {},
507
+ });
508
+ break;
509
+ }
510
+ default:
511
+ break;
512
+ }
513
+ }
514
+ if (completed)
515
+ return completed;
516
+ throw new Error("Ollama responses stream ended without completion.");
517
+ }
518
+ return normalizeOpenAIResponse(responseOrStream);
519
+ }
520
+ function createOllamaProvider(opts) {
521
+ const client = (opts.client ??
522
+ new openai_1.default({
523
+ apiKey: opts.apiKey ?? "ollama",
524
+ baseURL: opts.baseURL ?? "http://localhost:11434/v1",
525
+ }));
526
+ return {
527
+ async responses(input) {
528
+ return await createResponse(client, input.request, input.onStreamEvent);
529
+ },
530
+ async chat(input) {
531
+ const response = await createResponse(client, {
532
+ model: input.model,
533
+ input: chatMessagesToResponseItems(input.messages),
534
+ tools: input.tools,
535
+ stream: input.stream,
536
+ params: input.params ?? {},
537
+ }, (event) => {
538
+ if (event.type === "response.output_text.delta") {
539
+ input.onStreamText?.(event.delta);
540
+ }
541
+ });
542
+ const mapped = responseItemsToChat(response.output);
543
+ return {
544
+ message: mapped.message,
545
+ finishReason: mapped.toolCalls ? "tool_calls" : "stop",
546
+ toolCalls: mapped.toolCalls,
547
+ usage: response.usage,
548
+ };
549
+ },
550
+ };
551
+ }
@@ -0,0 +1,22 @@
1
+ import type { ModelProvider } from "@bolt-foundry/gambit-core";
2
+ export declare const OPENROUTER_PREFIX = "openrouter/";
3
+ type OpenAIClient = {
4
+ chat: {
5
+ completions: {
6
+ create: (params: unknown) => Promise<unknown>;
7
+ };
8
+ };
9
+ responses: {
10
+ create: (params: unknown) => Promise<unknown>;
11
+ };
12
+ };
13
+ export declare function createOpenRouterProvider(opts: {
14
+ apiKey: string;
15
+ baseURL?: string;
16
+ referer?: string;
17
+ title?: string;
18
+ enableResponses?: boolean;
19
+ client?: OpenAIClient;
20
+ }): ModelProvider;
21
+ export {};
22
+ //# sourceMappingURL=openrouter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter.d.ts","sourceRoot":"","sources":["../../../src/src/providers/openrouter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAKV,aAAa,EAOd,MAAM,2BAA2B,CAAC;AAOnC,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAE/C,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE;QACJ,WAAW,EAAE;YACX,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;SAC/C,CAAC;KACH,CAAC;IACF,SAAS,EAAE;QACT,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KAC/C,CAAC;CACH,CAAC;AA8eF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,GAAG,aAAa,CA8MhB"}