workers-ai-provider 3.1.14 → 3.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/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # workers-ai-provider
2
2
 
3
- [Workers AI](https://developers.cloudflare.com/workers-ai/) provider for the [AI SDK](https://sdk.vercel.ai/). Run Cloudflare's models for chat, embeddings, image generation, transcription, text-to-speech, reranking, and [AI Search](https://developers.cloudflare.com/ai-search/) — all from a single provider.
3
+ [Workers AI](https://developers.cloudflare.com/workers-ai/) provider for the [AI SDK](https://sdk.vercel.ai/). Run Cloudflare's models for chat, embeddings, image generation, transcription, text-to-speech, reranking, and [AI Search](https://developers.cloudflare.com/ai-search/) — all from a single provider. It can also route **third-party** models (OpenAI, Anthropic, Google, …) through [AI Gateway](https://developers.cloudflare.com/ai-gateway/) — see [Third-party models](#third-party-models-via-ai-gateway).
4
4
 
5
- ## Quick Start
5
+ ## Quick start
6
+
7
+ ```bash
8
+ npm install workers-ai-provider ai
9
+ ```
6
10
 
7
11
  ```jsonc
8
12
  // wrangler.jsonc
@@ -20,7 +24,7 @@ export default {
20
24
  const workersai = createWorkersAI({ binding: env.AI });
21
25
 
22
26
  const result = streamText({
23
- model: workersai("@cf/moonshotai/kimi-k2.5"),
27
+ model: workersai("@cf/moonshotai/kimi-k2.7-code"),
24
28
  messages: [{ role: "user", content: "Write a haiku about Cloudflare" }],
25
29
  });
26
30
 
@@ -29,10 +33,6 @@ export default {
29
33
  };
30
34
  ```
31
35
 
32
- ```bash
33
- npm install workers-ai-provider ai
34
- ```
35
-
36
36
  ## Configuration
37
37
 
38
38
  ### Workers binding (recommended)
@@ -73,10 +73,10 @@ Some good defaults:
73
73
 
74
74
  | Task | Model | Notes |
75
75
  | -------------- | -------------------------------------- | ----------------------------------- |
76
- | Chat | `@cf/moonshotai/kimi-k2.5` | 256k ctx, tools, vision, reasoning |
76
+ | Chat | `@cf/moonshotai/kimi-k2.7-code` | 256k ctx, tools, vision, reasoning |
77
77
  | Chat | `@cf/zai-org/glm-4.7-flash` | Fast, multilingual, 131k ctx |
78
78
  | Chat | `@cf/openai/gpt-oss-120b` | OpenAI open-weights, high reasoning |
79
- | Reasoning | `@cf/moonshotai/kimi-k2.5` | Configurable `reasoning_effort` |
79
+ | Reasoning | `@cf/moonshotai/kimi-k2.7-code` | Configurable `reasoning_effort` |
80
80
  | Reasoning | `@cf/qwen/qwq-32b` | Emits `reasoning_content` |
81
81
  | Embeddings | `@cf/baai/bge-base-en-v1.5` | 768-dim, English |
82
82
  | Embeddings | `@cf/google/embeddinggemma-300m` | 100+ languages, by Google |
@@ -86,13 +86,13 @@ Some good defaults:
86
86
  | Text-to-Speech | `@cf/deepgram/aura-2-en` | Context-aware, natural pacing |
87
87
  | Reranking | `@cf/baai/bge-reranker-base` | Fast document reranking |
88
88
 
89
- ## Text Generation
89
+ ## Text generation
90
90
 
91
91
  ```ts
92
92
  import { generateText } from "ai";
93
93
 
94
94
  const { text } = await generateText({
95
- model: workersai("@cf/moonshotai/kimi-k2.5"),
95
+ model: workersai("@cf/moonshotai/kimi-k2.7-code"),
96
96
  prompt: "Explain Workers AI in one paragraph",
97
97
  });
98
98
  ```
@@ -103,7 +103,7 @@ Streaming:
103
103
  import { streamText } from "ai";
104
104
 
105
105
  const result = streamText({
106
- model: workersai("@cf/moonshotai/kimi-k2.5"),
106
+ model: workersai("@cf/moonshotai/kimi-k2.7-code"),
107
107
  messages: [{ role: "user", content: "Write a short story" }],
108
108
  });
109
109
 
@@ -112,7 +112,7 @@ for await (const chunk of result.textStream) {
112
112
  }
113
113
  ```
114
114
 
115
- ## Reasoning Controls
115
+ ## Reasoning controls
116
116
 
117
117
  Reasoning-capable Workers AI models (GLM-4.7-flash, Kimi K2.5/K2.6, GPT-OSS, QwQ) accept `reasoning_effort` and `chat_template_kwargs` on their inputs. Either set them at model creation time as settings, or per-call via `providerOptions["workers-ai"]` (per-call wins):
118
118
 
@@ -141,7 +141,7 @@ await generateText({
141
141
 
142
142
  `reasoning_effort: null` is meaningful — it's the explicit "disable reasoning" signal for models that support it. Both fields land on the `inputs` object of `binding.run()` (and the JSON body of the REST request), matching the shape expected by Workers AI. See the [model catalog](https://developers.cloudflare.com/workers-ai/models/) for per-model reasoning capabilities.
143
143
 
144
- ## Vision (Image Inputs)
144
+ ## Vision (image inputs)
145
145
 
146
146
  Send images to vision-capable models like Kimi K2.5:
147
147
 
@@ -149,7 +149,7 @@ Send images to vision-capable models like Kimi K2.5:
149
149
  import { generateText } from "ai";
150
150
 
151
151
  const { text } = await generateText({
152
- model: workersai("@cf/moonshotai/kimi-k2.5"),
152
+ model: workersai("@cf/moonshotai/kimi-k2.7-code"),
153
153
  messages: [
154
154
  {
155
155
  role: "user",
@@ -164,14 +164,14 @@ const { text } = await generateText({
164
164
 
165
165
  Images can be provided as `Uint8Array`, base64 strings, or data URLs. Multiple images per message are supported. Works with both the binding and REST API configurations.
166
166
 
167
- ## Tool Calling
167
+ ## Tool calling
168
168
 
169
169
  ```ts
170
170
  import { generateText, stepCountIs } from "ai";
171
171
  import { z } from "zod";
172
172
 
173
173
  const { text } = await generateText({
174
- model: workersai("@cf/moonshotai/kimi-k2.5"),
174
+ model: workersai("@cf/moonshotai/kimi-k2.7-code"),
175
175
  prompt: "What's the weather in London?",
176
176
  tools: {
177
177
  getWeather: {
@@ -184,14 +184,14 @@ const { text } = await generateText({
184
184
  });
185
185
  ```
186
186
 
187
- ## Structured Output
187
+ ## Structured output
188
188
 
189
189
  ```ts
190
190
  import { generateText, Output } from "ai";
191
191
  import { z } from "zod";
192
192
 
193
193
  const { output } = await generateText({
194
- model: workersai("@cf/moonshotai/kimi-k2.5"),
194
+ model: workersai("@cf/moonshotai/kimi-k2.7-code"),
195
195
  prompt: "Recipe for spaghetti bolognese",
196
196
  output: Output.object({
197
197
  schema: z.object({
@@ -214,7 +214,7 @@ const { embeddings } = await embedMany({
214
214
  });
215
215
  ```
216
216
 
217
- ## Image Generation
217
+ ## Image generation
218
218
 
219
219
  ```ts
220
220
  import { generateImage } from "ai";
@@ -228,7 +228,7 @@ const { images } = await generateImage({
228
228
  // images[0].uint8Array contains the PNG bytes
229
229
  ```
230
230
 
231
- ## Transcription (Speech-to-Text)
231
+ ## Transcription (speech-to-text)
232
232
 
233
233
  Transcribe audio using Whisper or Deepgram Nova-3 models.
234
234
 
@@ -265,7 +265,7 @@ const { text } = await transcribe({
265
265
  });
266
266
  ```
267
267
 
268
- ## Text-to-Speech
268
+ ## Text-to-speech
269
269
 
270
270
  Generate spoken audio from text using Deepgram Aura-2.
271
271
 
@@ -329,21 +329,173 @@ Streaming works the same way — use `streamText` instead of `generateText`.
329
329
 
330
330
  > `createAutoRAG` still works but is deprecated. Use `createAISearch` instead.
331
331
 
332
- ## API Reference
332
+ ## Third-party models via AI Gateway
333
+
334
+ > **⚠️ Experimental.** Everything in this section (routing third-party models via `createWorkersAI({ providers })`, the provider plugins, the registry, the resume layer, and `createGatewayFetch`/`createGatewayProvider`) is a new and substantial addition — well beyond the package's original job of wrapping Workers AI. Treat the whole surface as experimental: APIs may change, and several behaviors depend on undocumented AI Gateway internals (the `cf-aig-run-id` resume buffer, per-provider run-path wire formats). It does **not** affect the stable Workers AI / AI Search APIs above. Bug reports and feedback are very welcome.
335
+
336
+ Route **third-party** catalog models — OpenAI, Anthropic, Google, xAI/Grok, Groq, and the OpenAI-compatible long tail — through [AI Gateway](https://developers.cloudflare.com/ai-gateway/) using the same `env.AI` binding, with resumable streaming, BYOK, caching, and fallback.
337
+
338
+ Install only the wire-format plugins you actually use. They're **optional** peer dependencies:
339
+
340
+ ```bash
341
+ npm install @ai-sdk/openai # openai, deepseek, xai/grok, groq, mistral, perplexity, cerebras, openrouter, fireworks, alibaba, minimax
342
+ npm install @ai-sdk/anthropic # anthropic
343
+ npm install @ai-sdk/google # google, google-vertex
344
+ ```
345
+
346
+ ### Setup
347
+
348
+ Pass the plugins to `createWorkersAI` via `providers`. Then it's the same provider you already use: `@cf/...` ids build Workers AI models, and a `"<provider>/<model>"` catalog slug is routed through AI Gateway automatically. `createWorkersAI` is the single public entry point — there's no separate factory to import.
349
+
350
+ ```ts
351
+ import { createWorkersAI } from "workers-ai-provider";
352
+ import { openai } from "workers-ai-provider/openai";
353
+ import { anthropic } from "workers-ai-provider/anthropic";
354
+ import { streamText } from "ai";
355
+
356
+ const workersai = createWorkersAI({
357
+ binding: env.AI,
358
+ providers: [openai, anthropic], // opt-in; enables third-party routing
359
+ // gateway is optional — catalog routing uses your account's "default"
360
+ // gateway unless you set one, e.g. gateway: { id: "my-gateway" }.
361
+ });
362
+
363
+ workersai("@cf/meta/llama-3.1-8b-instruct"); // Workers AI (unchanged)
364
+
365
+ const result = streamText({
366
+ model: workersai("openai/gpt-5", { resume: true }), // routed through AI Gateway
367
+ prompt: "Hello",
368
+ });
369
+ // result.response.headers["cf-aig-run-id"] is set — resume from there.
370
+ ```
371
+
372
+ The settings argument is **typed from the model id**: pass a `"<provider>/<model>"` catalog slug and the second argument autocompletes the per-call gateway options (`resume`, `fallback`, `cacheTtl`, `byok`, `metadata`, …, i.e. `DelegateCallOptions`); pass a `@cf/...` id and it autocompletes the usual `WorkersAIChatSettings`. `providers` is optional and **additive**: leave it unset and `createWorkersAI` behaves exactly as before; passing a catalog slug without it throws a helpful error pointing you here.
373
+
374
+ `gateway` is optional for catalog routing — when unset, requests use your account's `"default"` AI Gateway. Set `gateway: { id: "…" }` (here or per call) to use a specific gateway.
375
+
376
+ The examples below assume a `workersai` configured with `providers` as above.
377
+
378
+ ### Wire-format plugins and provider coverage
379
+
380
+ One plugin per **wire format** serves every provider of that format. The `openai` plugin alone covers `openai/…`, `deepseek/…`, `xai/…` (alias `grok`), `groq/…`, `mistral/…`, `perplexity/…`, `cerebras/…`, `openrouter/…`, `fireworks/…`, plus the unified-catalog chat providers `alibaba/…` (Qwen) and `minimax/…`.
381
+
382
+ The registry covers every provider in the [AI Gateway provider directory](https://developers.cloudflare.com/ai-gateway/usage/providers/) — OpenAI, Anthropic, Google AI Studio, Google Vertex AI, xAI/Grok, Groq, DeepSeek, Mistral, Perplexity, Cerebras, OpenRouter, Cohere, Baseten, Parallel, Azure OpenAI, Amazon Bedrock, HuggingFace, Replicate, Fal, Ideogram, Cartesia, Deepgram, ElevenLabs (plus Fireworks) — so `createGatewayFetch` can auto-detect them from the request URL.
383
+
384
+ **Coverage maturity varies** (the whole feature is experimental). Only the unified-billing run-catalog providers — OpenAI, Anthropic, Google, xAI/Grok, Groq, Alibaba/Qwen, MiniMax — are exercised end-to-end against a live gateway. The remaining registry entries (BYOK gateway-path providers like DeepSeek/Mistral/Perplexity/Cerebras/OpenRouter/Fireworks, and bring-your-own-provider-only ones like Cohere/Baseten/Parallel/Azure OpenAI/Bedrock/HuggingFace/Replicate/Fal/Ideogram/Cartesia/Deepgram/ElevenLabs) are registry-level wiring (gateway id, host pattern, endpoint transform) that is **not yet live-verified** — the routing is in place, but a provider's exact request shape may need adjustment. Please file issues for any that misbehave.
385
+
386
+ > **Run-path wire format is per-provider — not always OpenAI.** On the resumable run path (`env.AI.run`), Cloudflare's unified catalog **normalizes most providers to OpenAI chat-completions** (so `google/…` is parsed with the `openai` plugin on the run path, even though the gateway path uses the native `google` plugin), but **passes Anthropic through natively** — so `anthropic/…` uses the `anthropic` plugin on both paths. In practice: include `openai` for the openai-wire run-path providers (openai, google, xai/grok, groq), and `anthropic` to use `anthropic/…`. The native `google` plugin is only needed if you force google onto the **gateway path**. If a plugin a transport needs is missing, the delegate throws a `GatewayDelegateError` naming it.
387
+
388
+ ### Transports
389
+
390
+ The transport is chosen automatically from the options you pass:
391
+
392
+ | Transport | Backed by | Resume (`cf-aig-run-id`) | Caching | Server fallback | Billing |
393
+ | ----------------- | ---------------------------- | ------------------------ | ------- | --------------- | ----------------- |
394
+ | **run** (default) | `env.AI.run(...)` | ✅ | ❌ | ❌ | Unified billing |
395
+ | **gateway** | `env.AI.gateway(id).run([])` | ❌ | ✅ | ✅ | BYOK / stored key |
396
+
397
+ Run-catalog providers (OpenAI, Anthropic, Google, xAI, Groq, plus the unified-catalog chat providers Alibaba/Qwen and MiniMax) default to the resumable **run path**. BYOK-only providers (deepseek, mistral, perplexity, …) always use the **gateway path**. Asking for an impossible combination (e.g. `resume: true` with `fallback.mode: "server"`) throws a `GatewayDelegateError`.
398
+
399
+ > Alibaba and MiniMax are **run-path only** — they're on the unified catalog but not the native gateway directory, so there's no gateway path. Requesting `transport: "gateway"`, caching, or server-side fallback on them throws a clear `GatewayDelegateError` at build time (rather than failing upstream); use the default run path or `fallback.mode: "client"`.
400
+
401
+ ### BYOK (bring your own key)
402
+
403
+ On the gateway path, set `byok: true` and supply the upstream key via `extraHeaders`:
404
+
405
+ ```ts
406
+ streamText({
407
+ model: workersai("deepseek/deepseek-chat", {
408
+ byok: true,
409
+ extraHeaders: { authorization: `Bearer ${env.DEEPSEEK_API_KEY}` },
410
+ }),
411
+ prompt: "Hello",
412
+ });
413
+ ```
414
+
415
+ Without `byok`, provider auth headers are stripped so unified billing / the gateway's stored key applies.
416
+
417
+ ### Fallback
418
+
419
+ ```ts
420
+ // Client-side: keeps resume per leg. A failed pre-stream dispatch falls through.
421
+ workersai("openai/gpt-5", {
422
+ fallback: { mode: "client", models: ["anthropic/claude-sonnet-4-5"] },
423
+ });
424
+
425
+ // Server-side: same-vendor, on the gateway path.
426
+ workersai("openai/gpt-5", { fallback: { mode: "server", models: ["openai/gpt-5-mini"] } });
427
+ ```
428
+
429
+ If every client-side leg fails, a `WorkersAIFallbackError` carries the per-attempt tree.
430
+
431
+ ### Caching
432
+
433
+ ```ts
434
+ workersai("openai/gpt-5", { cacheTtl: 3600 }); // gateway path; cacheTtl/skipCache force it
435
+ ```
436
+
437
+ ### Metadata & logging
438
+
439
+ Attach custom metadata (for spend attribution, tenant breakdowns, etc.) and toggle gateway log collection per request. Both work on either transport — on the run path they go into the typed gateway options; on the gateway path they become `cf-aig-metadata` / `cf-aig-collect-log` headers. Call-level `metadata` merges over (and wins against) any `metadata` set via `gateway: { metadata }`.
440
+
441
+ ```ts
442
+ workersai("openai/gpt-5", {
443
+ metadata: { teamId: "AI", userId: 12345 }, // breaks down spend in the dashboard
444
+ collectLog: false, // opt this request out of log collection
445
+ });
446
+ ```
447
+
448
+ ### Resume after disconnect
449
+
450
+ The run path wraps the response stream so a transient mid-stream drop reconnects through the gateway resume endpoint transparently. For cross-invocation recovery (e.g. a Durable Object re-attaching after eviction), persist `{ runId, eventOffset }` via `onDispatch` + `onProgress` and re-attach with `createResumableStream`:
451
+
452
+ ```ts
453
+ workersai("openai/gpt-5", {
454
+ onDispatch: (info) => save({ runId: info.runId }),
455
+ onProgress: (eventOffset) => save({ eventOffset }), // throttle your own writes
456
+ onResumeExpired: "accept-partial", // or "error" (default) once the ~5.5 min buffer TTL elapses
457
+ });
458
+ ```
459
+
460
+ ### Bring your own provider
461
+
462
+ For provider-native or non-chat providers the slug delegate can't auto-wire (bedrock, replicate, audio/image), or for full control, route any `@ai-sdk/*` provider through the gateway:
463
+
464
+ ```ts
465
+ import { createOpenAI } from "@ai-sdk/openai";
466
+ import { createGatewayFetch } from "workers-ai-provider/gateway";
467
+
468
+ const openai = createOpenAI({
469
+ apiKey: env.OPENAI_API_KEY, // forwarded when byok: true
470
+ fetch: createGatewayFetch({ binding: env.AI, gateway: "my-gateway", byok: true }),
471
+ });
472
+ const model = openai("gpt-5");
473
+ ```
474
+
475
+ The provider id is detected from the request URL (or pass `provider` explicitly).
476
+
477
+ ### Errors
478
+
479
+ `WorkersAIGatewayError` carries a coarse `code` (`auth`, `rate-limit`, `not-found`, `bad-request`, `provider-error`, `gateway-error`, `resume-expired`), a `recoverable` hint, the HTTP `status`, and the parsed CF/provider envelope. `WorkersAIFallbackError` carries the `attempts` tree.
480
+
481
+ ## API reference
333
482
 
334
483
  ### `createWorkersAI(options)`
335
484
 
336
- | Option | Type | Description |
337
- | ----------- | ---------------- | ---------------------------------------------------------------------------- |
338
- | `binding` | `Ai` | Workers AI binding (`env.AI`). Use this OR credentials. |
339
- | `accountId` | `string` | Cloudflare account ID. Required with `apiKey`. |
340
- | `apiKey` | `string` | Cloudflare API token. Required with `accountId`. |
341
- | `gateway` | `GatewayOptions` | Optional [AI Gateway](https://developers.cloudflare.com/ai-gateway/) config. |
485
+ | Option | Type | Description |
486
+ | ----------------- | ------------------------------- | ------------------------------------------------------------------------------------------------- |
487
+ | `binding` | `Ai` | Workers AI binding (`env.AI`). Use this OR credentials. |
488
+ | `accountId` | `string` | Cloudflare account ID. Required with `apiKey`. |
489
+ | `apiKey` | `string` | Cloudflare API token. Required with `accountId`. |
490
+ | `gateway` | `GatewayOptions` | Optional [AI Gateway](https://developers.cloudflare.com/ai-gateway/) config. |
491
+ | `providers` | `ProviderPlugin[]` | _Experimental._ Wire-format plugins that enable routing `"<provider>/<model>"` slugs via gateway. |
492
+ | `resume` | `boolean` | _Experimental._ Default resume behavior for gateway-routed catalog models. Defaults to `true`. |
493
+ | `onResumeExpired` | `"error"` \| `"accept-partial"` | _Experimental._ Default policy when the gateway resume buffer expires. Defaults to `"error"`. |
342
494
 
343
495
  Returns a provider with model factories. Each factory accepts an optional second argument for per-model settings:
344
496
 
345
497
  ```ts
346
- workersai("@cf/moonshotai/kimi-k2.5", {
498
+ workersai("@cf/moonshotai/kimi-k2.7-code", {
347
499
  sessionAffinity: "my-unique-session-id",
348
500
  });
349
501
  ```
@@ -356,7 +508,7 @@ workersai("@cf/moonshotai/kimi-k2.5", {
356
508
  Model factories:
357
509
 
358
510
  ```ts
359
- // Chat — for generateText / streamText
511
+ // Chat — for generateText / streamText (also accepts third-party slugs when `providers` is set)
360
512
  workersai(modelId);
361
513
  workersai.chat(modelId);
362
514
 
@@ -0,0 +1,14 @@
1
+ import { o as ProviderPlugin } from "./gateway-delegate-BfaUTwDZ.mjs";
2
+
3
+ //#region src/anthropic.d.ts
4
+ /**
5
+ * Anthropic-wire provider plugin for the gateway delegate. Pass to
6
+ * `createGatewayDelegate({ providers: [anthropic] })` to handle
7
+ * `"anthropic/<model>"` slugs.
8
+ *
9
+ * Requires `@ai-sdk/anthropic` (an optional peer dependency — install it yourself).
10
+ */
11
+ declare const anthropic: ProviderPlugin;
12
+ //#endregion
13
+ export { anthropic };
14
+ //# sourceMappingURL=anthropic.d.mts.map
@@ -0,0 +1,21 @@
1
+ import { createAnthropic } from "@ai-sdk/anthropic";
2
+ //#region src/anthropic.ts
3
+ /**
4
+ * Anthropic-wire provider plugin for the gateway delegate. Pass to
5
+ * `createGatewayDelegate({ providers: [anthropic] })` to handle
6
+ * `"anthropic/<model>"` slugs.
7
+ *
8
+ * Requires `@ai-sdk/anthropic` (an optional peer dependency — install it yourself).
9
+ */
10
+ const anthropic = {
11
+ wireFormat: "anthropic",
12
+ create: ({ modelId, fetch, baseURL }) => createAnthropic({
13
+ apiKey: "unused",
14
+ fetch,
15
+ ...baseURL ? { baseURL } : {}
16
+ })(modelId)
17
+ };
18
+ //#endregion
19
+ export { anthropic };
20
+
21
+ //# sourceMappingURL=anthropic.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.mjs","names":[],"sources":["../src/anthropic.ts"],"sourcesContent":["import { createAnthropic } from \"@ai-sdk/anthropic\";\nimport type { ProviderPlugin } from \"./gateway-delegate\";\n\n/**\n * Anthropic-wire provider plugin for the gateway delegate. Pass to\n * `createGatewayDelegate({ providers: [anthropic] })` to handle\n * `\"anthropic/<model>\"` slugs.\n *\n * Requires `@ai-sdk/anthropic` (an optional peer dependency — install it yourself).\n */\nexport const anthropic: ProviderPlugin = {\n\twireFormat: \"anthropic\",\n\tcreate: ({ modelId, fetch, baseURL }) =>\n\t\t// apiKey is a placeholder — the gateway handles auth (unified billing / BYOK)\n\t\t// and the delegate strips the x-api-key header on the gateway path.\n\t\tcreateAnthropic({ apiKey: \"unused\", fetch, ...(baseURL ? { baseURL } : {}) })(modelId),\n};\n"],"mappings":";;;;;;;;;AAUA,MAAa,YAA4B;CACxC,YAAY;CACZ,SAAS,EAAE,SAAS,OAAO,cAG1B,gBAAgB;EAAE,QAAQ;EAAU;EAAO,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;CAAG,CAAC,CAAC,CAAC,OAAO;AACvF"}