@veryfront/ext-llm-openai 0.1.1186 → 0.1.1189

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 (39) hide show
  1. package/README.md +49 -2
  2. package/esm/_dnt.polyfills.d.ts +12 -0
  3. package/esm/_dnt.polyfills.d.ts.map +1 -0
  4. package/esm/_dnt.polyfills.js +15 -0
  5. package/esm/index.d.ts +1 -0
  6. package/esm/index.d.ts.map +1 -1
  7. package/esm/index.js +7 -1
  8. package/esm/openai-chat-request-builder.d.ts +5 -43
  9. package/esm/openai-chat-request-builder.d.ts.map +1 -1
  10. package/esm/openai-chat-request-builder.js +18 -4
  11. package/esm/openai-chat-stream.d.ts +8 -1
  12. package/esm/openai-chat-stream.d.ts.map +1 -1
  13. package/esm/openai-chat-stream.js +286 -104
  14. package/esm/openai-provider-options.d.ts +6 -0
  15. package/esm/openai-provider-options.d.ts.map +1 -0
  16. package/esm/openai-provider-options.js +14 -0
  17. package/esm/openai-provider.d.ts +6 -5
  18. package/esm/openai-provider.d.ts.map +1 -1
  19. package/esm/openai-provider.js +579 -129
  20. package/esm/openai-reasoning-models.d.ts +3 -6
  21. package/esm/openai-reasoning-models.d.ts.map +1 -1
  22. package/esm/openai-responses-request-builder.d.ts.map +1 -1
  23. package/esm/openai-responses-request-builder.js +240 -19
  24. package/esm/openai-responses-stream.d.ts +12 -1
  25. package/esm/openai-responses-stream.d.ts.map +1 -1
  26. package/esm/openai-responses-stream.js +1053 -113
  27. package/esm/openai-sse-buffer.d.ts +8 -0
  28. package/esm/openai-sse-buffer.d.ts.map +1 -0
  29. package/esm/openai-sse-buffer.js +47 -0
  30. package/esm/openai-stream-metadata.d.ts +6 -0
  31. package/esm/openai-stream-metadata.d.ts.map +1 -0
  32. package/esm/openai-stream-metadata.js +10 -0
  33. package/esm/openai-tool-input.d.ts +10 -0
  34. package/esm/openai-tool-input.d.ts.map +1 -0
  35. package/esm/openai-tool-input.js +30 -0
  36. package/esm/openai-web-search.d.ts +35 -0
  37. package/esm/openai-web-search.d.ts.map +1 -0
  38. package/esm/openai-web-search.js +369 -0
  39. package/package.json +3 -3
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Category:** LLM | **Contract:** `LLMProvider` | **Built-in**
4
4
 
5
- Provides OpenAI models for Veryfront agents and chat, enabling `openai/*` models for chat, embeddings, and the Responses API via the `LLMProviderRegistry`.
5
+ Provides OpenAI models for Veryfront agents and chat, enabling `openai/*` models for chat, embeddings, the Responses API, and OpenAI-hosted web search via the `LLMProviderRegistry`.
6
6
 
7
7
  ## Registration
8
8
 
@@ -43,7 +43,8 @@ const result = await ai.embed("openai/text-embedding-3-small", {
43
43
 
44
44
  ### Responses API
45
45
 
46
- For models that support OpenAI's Responses API (structured output, native tools):
46
+ For models that support OpenAI's Responses API (structured output, function
47
+ tools, and supported hosted tools):
47
48
 
48
49
  ```ts
49
50
  const response = await ai.responses("openai/gpt-4.1", {
@@ -51,6 +52,52 @@ const response = await ai.responses("openai/gpt-4.1", {
51
52
  });
52
53
  ```
53
54
 
55
+ ### Hosted Web Search
56
+
57
+ The agent-facing tool name is `web_search`. It resolves to the current
58
+ `openai.web_search` provider tool and routes that request through the Responses
59
+ API, including for models that otherwise use Chat Completions:
60
+
61
+ ```ts
62
+ import { agent } from "veryfront/agent";
63
+
64
+ export default agent({
65
+ model: "openai/gpt-4.1",
66
+ providerTools: ["web_search"],
67
+ });
68
+ ```
69
+
70
+ | Contract | Supported value |
71
+ | --------------------------- | --------------------------------------------------------------------------------------------------- |
72
+ | Agent tool name | `web_search` |
73
+ | Current provider id | `openai.web_search` |
74
+ | Low-level compatibility ids | `openai.web_search_2025_08_26`, `openai.web_search_preview`, `openai.web_search_preview_2025_03_11` |
75
+ | Optional argument | `searchContextSize`: `"low"` \| `"medium"` \| `"high"` |
76
+ | Per-request limit | One OpenAI web-search provider tool |
77
+
78
+ Other OpenAI-hosted tool types are not normalized by this extension and fail
79
+ before the provider request. OpenAI-compatible base URLs can use this path only
80
+ when the endpoint implements OpenAI's Responses and hosted web-search
81
+ contracts.
82
+
83
+ Responses requests are stateless (`store: false`). The runtime retains the
84
+ complete ordered response output in provider metadata and replays it on the
85
+ next turn. Reasoning requests explicitly include encrypted reasoning content,
86
+ and web-search requests include source URLs, so manual callers must preserve
87
+ assistant-message provider metadata between turns. Raw replay is limited to
88
+ 4,096 output items and 8 MiB. Whenever canonical calls or results survive,
89
+ their IDs, names, semantic values, multiplicity, and order must match the raw
90
+ output before transport.
91
+
92
+ Provider-executed Responses calls and results cannot be converted into Chat
93
+ Completions history. Switching a conversation containing those parts to a
94
+ Chat Completions-only route fails as a configuration error before transport;
95
+ keep that conversation on a Responses-capable route or begin a new compacted
96
+ history that no longer contains the provider-executed parts.
97
+
98
+ See [Providers: Enable OpenAI-hosted web search](../../docs/guides/providers.md#enable-openai-hosted-web-search)
99
+ for the agent setup.
100
+
54
101
  ## Supported Models
55
102
 
56
103
  Any model accessible through the OpenAI Chat Completions, Responses, or Embeddings API:
@@ -0,0 +1,12 @@
1
+ declare global {
2
+ interface Object {
3
+ /**
4
+ * Determines whether an object has a property with the specified name.
5
+ * @param o An object.
6
+ * @param v A property name.
7
+ */
8
+ hasOwn(o: object, v: PropertyKey): boolean;
9
+ }
10
+ }
11
+ export {};
12
+ //# sourceMappingURL=_dnt.polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AAeA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd;;;;WAIG;QACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;KAC5C;CACF;AAED,OAAO,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ // https://github.com/tc39/proposal-accessible-object-hasownproperty/blob/main/polyfill.js
2
+ if (!Object.hasOwn) {
3
+ Object.defineProperty(Object, "hasOwn", {
4
+ value: function (object, property) {
5
+ if (object == null) {
6
+ throw new TypeError("Cannot convert undefined or null to object");
7
+ }
8
+ return Object.prototype.hasOwnProperty.call(Object(object), property);
9
+ },
10
+ configurable: true,
11
+ enumerable: false,
12
+ writable: true,
13
+ });
14
+ }
15
+ export {};
package/esm/index.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * @module extensions/ext-llm-openai
6
6
  */
7
+ import "./_dnt.polyfills.js";
7
8
  import type { ExtensionFactory } from "veryfront/extensions";
8
9
  import { OpenAIProvider } from "./openai-provider.js";
9
10
  declare const extOpenAI: ExtensionFactory;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,QAAA,MAAM,SAAS,EAAE,gBAqBhB,CAAC;AAEF,eAAe,SAAS,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,QAAA,MAAM,SAAS,EAAE,gBAyBhB,CAAC;AAEF,eAAe,SAAS,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,CAAC"}
package/esm/index.js CHANGED
@@ -4,11 +4,13 @@
4
4
  *
5
5
  * @module extensions/ext-llm-openai
6
6
  */
7
+ import "./_dnt.polyfills.js";
7
8
  import { LLMProviderRegistryName } from "veryfront/extensions/llm";
8
9
  import { OpenAIProvider } from "./openai-provider.js";
9
10
  const extOpenAI = () => {
10
11
  const provider = new OpenAIProvider();
11
12
  let registry;
13
+ let registeredProvider = false;
12
14
  return {
13
15
  name: "ext-llm-openai",
14
16
  version: "0.1.0",
@@ -19,11 +21,15 @@ const extOpenAI = () => {
19
21
  capabilities: [],
20
22
  setup(ctx) {
21
23
  registry = ctx.require(LLMProviderRegistryName);
24
+ registeredProvider = !registry.has(provider.id);
22
25
  registry.register(provider);
26
+ ctx.provide("LLMProvider:openai", registry.get(provider.id) ?? provider);
23
27
  ctx.logger.debug("[ext-llm-openai] OpenAI provider registered");
24
28
  },
25
29
  teardown() {
26
- registry?.unregister(provider.id);
30
+ if (registeredProvider)
31
+ registry?.unregister(provider.id);
32
+ registeredProvider = false;
27
33
  registry = undefined;
28
34
  },
29
35
  };
@@ -1,48 +1,10 @@
1
- import type { OpenAICompatibleChatRequest, RuntimePromptMessage } from "veryfront/provider/shared";
2
- import { type OpenAIProviderReasoningOption } from "./openai-reasoning-models.js";
3
- export type RuntimeToolDefinition = {
4
- type: "function";
5
- name: string;
6
- description?: string;
7
- inputSchema: unknown;
8
- } | {
9
- type: "provider";
10
- name: string;
11
- id: `${string}.${string}`;
12
- args: Record<string, unknown>;
13
- };
14
- export type OpenAICompatibleLanguageOptions = {
15
- prompt: RuntimePromptMessage[];
16
- maxOutputTokens?: number;
17
- temperature?: number;
18
- topP?: number;
19
- topK?: number;
20
- stopSequences?: string[];
21
- tools?: RuntimeToolDefinition[];
22
- toolChoice?: unknown;
23
- seed?: number;
24
- presencePenalty?: number;
25
- frequencyPenalty?: number;
26
- headers?: HeadersInit;
27
- providerOptions?: Record<string, unknown>;
28
- includeRawChunks?: boolean;
29
- abortSignal?: AbortSignal;
30
- reasoning?: OpenAIProviderReasoningOption;
31
- userId?: string;
1
+ import type { ModelRuntimeCallOptions, ModelRuntimeToolDefinition, OpenAICompatibleChatRequest } from "veryfront/provider/shared";
2
+ export interface OpenAICompatibleLanguageOptions extends ModelRuntimeCallOptions {
32
3
  serviceTier?: "auto" | "default" | "flex" | "scale";
33
4
  parallelToolCalls?: boolean;
34
- responseFormat?: {
35
- type: "text";
36
- } | {
37
- type: "json";
38
- } | {
39
- type: "json_schema";
40
- name: string;
41
- schema: unknown;
42
- description?: string;
43
- strict?: boolean;
44
- };
45
- };
5
+ }
6
+ /** @deprecated Import `ModelRuntimeToolDefinition` from `veryfront/provider` instead. */
7
+ export type RuntimeToolDefinition = ModelRuntimeToolDefinition;
46
8
  type WarningCollector = {
47
9
  push(warning: {
48
10
  type: "unsupported-setting" | "other";
@@ -1 +1 @@
1
- {"version":3,"file":"openai-chat-request-builder.d.ts","sourceRoot":"","sources":["../src/openai-chat-request-builder.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,2BAA2B,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACnG,OAAO,EACL,KAAK,6BAA6B,EAGnC,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,qBAAqB,GAC7B;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB,GACC;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC;AAEJ,MAAM,MAAM,+BAA+B,GAAG;IAC5C,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,SAAS,CAAC,EAAE,6BAA6B,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EACX;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAChB;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAChB;QACA,IAAI,EAAE,aAAa,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,OAAO,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;CACL,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,IAAI,CAAC,OAAO,EAAE;QACZ,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;QACtC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,IAAI,CAAC;IACT,KAAK,IAAI,KAAK,CAAC;QACb,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;QACtC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ,CAAC;AAUF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,+BAA+B,EACxC,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,gBAAgB,GACzB,2BAA2B,CAmH7B"}
1
+ {"version":3,"file":"openai-chat-request-builder.d.ts","sourceRoot":"","sources":["../src/openai-chat-request-builder.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC5B,MAAM,2BAA2B,CAAC;AAOnC,MAAM,WAAW,+BAAgC,SAAQ,uBAAuB;IAC9E,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,yFAAyF;AACzF,MAAM,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAE/D,KAAK,gBAAgB,GAAG;IACtB,IAAI,CAAC,OAAO,EAAE;QACZ,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;QACtC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,IAAI,CAAC;IACT,KAAK,IAAI,KAAK,CAAC;QACb,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;QACtC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ,CAAC;AAUF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,+BAA+B,EACxC,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,gBAAgB,GACzB,2BAA2B,CA+H7B"}
@@ -1,5 +1,6 @@
1
1
  import { readProviderOptions, toOpenAICompatibleMessages, toOpenAICompatibleTools, unwrapToolInputSchema, } from "veryfront/provider/shared";
2
2
  import { rejectsOpenAISamplingParams, resolveOpenAIReasoningConfig, } from "./openai-reasoning-models.js";
3
+ import { defineOpenAIProviderOptions } from "./openai-provider-options.js";
3
4
  function isNativeOpenAIModel(modelId) {
4
5
  return /^(gpt-|o[134](-|$)|chatgpt-)/.test(modelId);
5
6
  }
@@ -12,6 +13,7 @@ export function buildOpenAIChatRequest(modelId, providerName, options, stream, w
12
13
  const samplingRejected = rejectsOpenAISamplingParams(modelId);
13
14
  const fixedSampling = isFixedSamplingModel(modelId);
14
15
  const dropSamplingParams = reasoningEnabled || samplingRejected || fixedSampling;
16
+ const messages = toOpenAICompatibleMessages(options.prompt);
15
17
  // OpenAI Chat Completions has no top_k surface.
16
18
  if (options.topK !== undefined) {
17
19
  warnings.push({
@@ -47,7 +49,7 @@ export function buildOpenAIChatRequest(modelId, providerName, options, stream, w
47
49
  }
48
50
  const body = {
49
51
  model: modelId,
50
- messages: toOpenAICompatibleMessages(options.prompt),
52
+ messages,
51
53
  ...(stream ? { stream: true, stream_options: { include_usage: true } } : {}),
52
54
  ...(options.maxOutputTokens !== undefined
53
55
  ? isNativeOpenAIModel(modelId)
@@ -59,7 +61,7 @@ export function buildOpenAIChatRequest(modelId, providerName, options, stream, w
59
61
  : {}),
60
62
  ...(!dropSamplingParams && options.topP !== undefined ? { top_p: options.topP } : {}),
61
63
  ...(options.stopSequences && options.stopSequences.length > 0
62
- ? { stop: options.stopSequences }
64
+ ? { stop: [...options.stopSequences] }
63
65
  : {}),
64
66
  ...(toOpenAICompatibleTools(options.tools)
65
67
  ? { tools: toOpenAICompatibleTools(options.tools) }
@@ -109,9 +111,21 @@ export function buildOpenAIChatRequest(modelId, providerName, options, stream, w
109
111
  ];
110
112
  const providerOpts = {};
111
113
  for (const bucketName of bucketNames) {
112
- Object.assign(providerOpts, normalizeNativeMaxTokens(readProviderOptions(options.providerOptions, bucketName), modelId));
114
+ defineOpenAIProviderOptions(providerOpts, normalizeNativeMaxTokens(readProviderOptions(options.providerOptions, bucketName), modelId));
115
+ }
116
+ defineOpenAIProviderOptions(body, providerOpts);
117
+ // Provider-native options may tune request behavior, but they must not
118
+ // replace the runtime-owned transport mode, model, or conversation.
119
+ body.model = modelId;
120
+ body.messages = messages;
121
+ if (stream) {
122
+ body.stream = true;
123
+ body.stream_options = { include_usage: true };
124
+ }
125
+ else {
126
+ delete body.stream;
127
+ delete body.stream_options;
113
128
  }
114
- Object.assign(body, providerOpts);
115
129
  return body;
116
130
  }
117
131
  /** Normalizes max_tokens to max_completion_tokens for native OpenAI models. */
@@ -1,2 +1,9 @@
1
- export declare function streamOpenAICompatibleParts(stream: ReadableStream<Uint8Array>): AsyncIterable<unknown>;
1
+ type OpenAICompatibleProviderKind = "openai" | "mistral" | "moonshotai";
2
+ type OpenAIChatStreamContext = {
3
+ providerKind?: OpenAICompatibleProviderKind;
4
+ providerLabel?: string;
5
+ };
6
+ export declare const MAX_OPENAI_STREAM_TOOL_CALLS = 1024;
7
+ export declare function streamOpenAICompatibleParts(stream: ReadableStream<Uint8Array>, context?: OpenAIChatStreamContext): AsyncIterable<unknown>;
8
+ export {};
2
9
  //# sourceMappingURL=openai-chat-stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openai-chat-stream.d.ts","sourceRoot":"","sources":["../src/openai-chat-stream.ts"],"names":[],"mappings":"AAwIA,wBAAuB,2BAA2B,CAChD,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACjC,aAAa,CAAC,OAAO,CAAC,CAsJxB"}
1
+ {"version":3,"file":"openai-chat-stream.d.ts","sourceRoot":"","sources":["../src/openai-chat-stream.ts"],"names":[],"mappings":"AA2BA,KAAK,4BAA4B,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;AAExE,KAAK,uBAAuB,GAAG;IAC7B,YAAY,CAAC,EAAE,4BAA4B,CAAC;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AASF,eAAO,MAAM,4BAA4B,OAAQ,CAAC;AA4KlD,wBAAuB,2BAA2B,CAChD,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,GAAE,uBAA4B,GACpC,aAAa,CAAC,OAAO,CAAC,CAuVxB"}